重学Kotlin(一)变量与类型

103 阅读8分钟

重学Kotlin(一)变量与类型

一、变量声明

在 Kotlin 中,变量声明的方式和 Java 有一些不同,它更简洁、安全,也有明确的可变性区分。下面我帮你整理成一份 全面的 Kotlin 变量声明指南

1. 可变与不可变变量

Kotlin 中有两种关键字:

关键字描述示例
val不可变(只读变量,相当于 Java 的 finalval x: Int = 10
var可变(可修改)var y: Int = 5

例子

val a: Int = 10   // a 是不可变的,不能重新赋值
var b: Int = 5    // b 是可变的,可以重新赋值
b = 20            // 正常
// a = 15         // 编译错误!

小结:尽量用 val,只有在需要改变值时才用 var,提高代码安全性。

2. 类型声明方式

Kotlin 支持两种方式声明类型:

(1)显式声明类型
val name: String = "Alice"
var age: Int = 20
(2)类型推断(推荐)

如果编译器能推断出类型,可以省略:

val name = "Alice"   // 自动推断为 String
var age = 20         // 自动推断为 Int

3. 延迟初始化

有些场景下,变量在声明时不能立即初始化,可以使用:

(1)lateinit(只适用于 var 且非基本类型)

lateinit var userName: String

fun initUser() {
    userName = "Bob"
}

fun printUser() {
    println(userName)
}

注意:lateinit 不能用于 val 或基本类型 (Int, Double 等)

(2)by lazy(延迟初始化且只读)

val lazyValue: String by lazy {
    println("Computing...")
    "Hello"
}

fun main() {
    println(lazyValue) // 第一次访问时计算
    println(lazyValue) // 直接使用缓存
}

4. 可空类型

Kotlin 中默认变量 不允许为 null,如果需要 null,需要使用 ?

var name: String? = null  // 可空类型
name = "Alice"
name = null              // 合法

使用可空类型时,可以用安全调用 ?. 或非空断言 !!

println(name?.length)   // 安全调用,如果 name 为 null 返回 null
println(name!!.length)  // 非空断言,如果 name 为 null 会抛异常

5. 常量(编译期常量)

  • 使用 const val 声明 编译期常量(只能在顶层或对象中):
const val MAX_COUNT = 100

6. 总结

场景关键字/方式
不可变变量val
可变变量var
延迟初始化可变lateinit var
延迟初始化只读val ... by lazy { }
可空变量?
编译期常量const val
类型推断可以省略类型,Kotlin 自动推断

二、基本数据类型

类型说明Kotlin 类型对应的 Java 基本类型Java 包装类型
8 位有符号整数BytebyteByte
16 位有符号整数ShortshortShort
32 位有符号整数IntintInteger
64 位有符号整数LonglongLong
单精度浮点数FloatfloatFloat
双精度浮点数DoubledoubleDouble
单个字符CharcharCharacter
布尔值BooleanbooleanBoolean

1. Kotlin 与 Java 的区别

在 Java 中:

  • 基本类型(primitive types)int, long, float, double, boolean, char 等。这些类型在 JVM 层面上 不是对象,存储在栈上,操作高效。
  • 还有 引用类型(reference types / class)Integer, Long, Boolean 等,是对象,存储在堆上。
  • 问题
    1. 基本类型不能直接调用方法(需要装箱成对象)。
    2. Java 类型体系不统一(有基本类型和对象类型的差异)。
    3. 泛型只能用对象类型(例如 List<Integer>,不能用 List<int>)。

在 Kotlin 中:

  • Kotlin 没有 Java 风格的基本类型,只有统一的 类类型

    val a: Int = 5
    val b: Boolean = true
    

    在语法上,这看起来都是对象类型,可以直接调用方法

    val a: Int = 5
    println(a.toString())  // 正常调用
    
  • Kotlin 的类型系统 统一了基本类型和对象类型,开发者不必区分 intInteger,Kotlin 的 Int 就是一个类。

    println(Int::class)  // 输出 class kotlin.Int
    

2. Kotlin 运行时优化(JVM 编译器做的事)

虽然 Kotlin 在语法上只有对象类型,但编译器在编译到 JVM 字节码时,会做 自动优化(under the hood)

  • 对于局部变量、数组等,Kotlin 会尽可能使用 JVM 的基本类型(primitive types),避免性能损失:

    val x: Int = 123
    
    • JVM 上可能会直接用 int 存储。
  • 对于泛型或需要对象的场景,Kotlin 会自动装箱:

    val list: List<Int> = listOf(1, 2, 3) // 会用 Integer
    
  • 总结

    • 语法统一(统一对象类型) → 简化开发、避免基本类型和对象类型切换。
    • 编译优化 → 在 JVM 层面仍然保持性能,不损失效率。

3. Kotlin 没有基本类型的好处

优点说明
统一类型系统所有类型都是对象,方法调用统一,泛型支持直观。
减少装箱/拆箱的困扰编译器自动优化,开发者无需手动转换。
安全性更高不存在基本类型和对象类型之间混用产生的空指针问题(Kotlin 的 Int 永远非空,Int? 才可能为空)。
简化泛型使用泛型参数可以直接使用 IntBoolean 等类型,不用考虑 Java 的限制。
  • 举个例子
fun sum(a: Int, b: Int): Int = a + b

fun main() {
    val x: Int = 10
    val y: Int = 20
    println(sum(x, y))  // 30
}
  • Kotlin 里 Int 是对象,直接调用 toString() 等方法都可以。
  • 编译到 JVM 字节码时,局部变量 xy 仍可能是 int,性能无损

4.类型转化

Java 中,基本类型之间可以自动转换(隐式转换):

int i = 10;
long l = i;   //  自动提升

但在 Kotlin 中,这样写会报错:

val i: Int = 10
val l: Long = i  //  编译错误:类型不匹配

原因: Kotlin 中所有类型(包括 Int、Long、Double)都是 对象类型, 没有 Java 的“自动提升”。

这样做是为了:

  • 避免自动转换带来的精度丢失或隐式错误;
  • 保证类型安全;
  • 让所有类型行为一致(对象思维)。

总结

  1. Kotlin 语法上没有基本类型,统一使用对象类型(Int、Boolean 等都是类)。
  2. 编译器会自动在 JVM 上优化,局部变量和数组仍可能用基本类型存储。
  3. 这样设计主要是为了类型系统统一、泛型安全、避免装箱/拆箱烦恼。
  4. 相较java的基本数据类型转化,kotlin没有隐式转换,必须调用相关函数显式的转化

三、String类型

Kotlin 的 String 本质上还是Java 的 java.lang.String, 但 Kotlin 语法层面对它进行了 扩展(增强方法 + 空安全 + 模板语法)

1.字符串模板(Kotlin 独有)

这是 Java 没有的语法糖。

Kotlin 示例:

val name = "Tom"
val age = 20
println("My name is $name, I am $age years old.")

输出:My name is Tom, I am 20 years old.

也可以嵌入表达式:

println("Next year I’ll be ${age + 1}")

Java 中只能这样写:

System.out.println("My name is " + name + ", I am " + age + " years old.");

2.字符串比较

操作JavaKotlin
按内容比较s1.equals(s2)s1 == s2(自动调用 equals
按引用比较s1 == s2s1 === s2
忽略大小写equalsIgnoreCase()equals(other, ignoreCase = true)

示例:

val s1 = "abc"
val s2 = "ABC"
println(s1 == s2)                     // false
println(s1.equals(s2, ignoreCase = true)) // true

3.字符串拼接与效率

对比项JavaKotlin
简单拼接+ 运算符同样支持 +
编译优化StringBuilder 拼接优化通过 StringBuilder 优化(底层同 Java)
多行文本需要 \n 拼接✅ 支持原生多行字符串(""" """

Kotlin 多行字符串示例:

val text = """
    Line1
    Line2
    Line3
""".trimIndent()

Java 15+ 才引入类似的文本块(Text Block):

String text = """
    Line1
    Line2
    Line3
    """;

四、数组

1.数组创建

val arr = arrayOf(1, 2, 3, 4, 5)
  • arrayOf() 是一个泛型函数,返回 Array<T> 类型。
  • 例如 Array<Int>Array<String> 等。

指定类型:

val arr: Array<String> = arrayOf("A", "B", "C")

指定大小 + 初始值生成数组

val squares = Array(5) { i -> i * i }
println(squares.joinToString()) // 0, 1, 4, 9, 16
  • Array(size) { index -> 初始化表达式 },这里是个内联函数后续再分析
  • 注意这里返回的依然是 Array<Int>,而不是 IntArray

2.基本类型数组(原生类型数组)

为了避免装箱/拆箱的性能开销,Kotlin 为基本类型提供了专门的数组类型:

Kotlin 数组类型对应 Java 类型创建方式
IntArrayint[]intArrayOf(1, 2, 3)
LongArraylong[]longArrayOf(1L, 2L)
DoubleArraydouble[]doubleArrayOf(1.0, 2.0)
FloatArrayfloat[]floatArrayOf(1f, 2f)
CharArraychar[]charArrayOf('a', 'b')
ByteArraybyte[]byteArrayOf(1, 2)
BooleanArrayboolean[]booleanArrayOf(true, false)

3.数组的常用操作

val arr = arrayOf(10, 20, 30)
println(arr[0])   // 访问元素:10
arr[1] = 50       // 修改元素
println(arr.size) // 长度:3
遍历:
for (item in arr) {
    println(item)
}

// 或者使用索引
for (i in arr.indices) {
    println("arr[$i] = ${arr[i]}")
}

4.常用函数

Kotlin 提供了丰富的扩展函数:

函数作用示例
first() / last()取首尾元素arr.first()
sum()求和(数值类型)nums.sum()
average()平均值nums.average()
max() / min()最大/最小nums.maxOrNull()
contains()是否包含arr.contains(3)
sorted()排序后返回新数组arr.sorted()
reversed()反转arr.reversed()
map {}映射arr.map { it * 2 }
forEach {}遍历arr.forEach { println(it) }

5.与 Java 的互操作

在 Kotlin 调用 Java 代码时:

  • IntArrayint[]
  • Array<Int>Integer[]

⚠️ 区别:

val a1: Array<Int> = arrayOf(1, 2, 3) // 装箱类型 Integer[]
val a2: IntArray = intArrayOf(1, 2, 3) // 原生类型 int[]

当你调用 Java 方法时,若它期望 int[],必须传 IntArray

6.二维数组示例

val matrix = Array(3) { IntArray(3) } // 3x3 矩阵
matrix[0][1] = 5
println(matrix[0].joinToString()) // 输出 0, 5, 0

五、区间

1.区间的基本语法

(1).. 表示「闭区间」

即:包含起点和终点

val range = 1..5
println(range.toList()) // [1, 2, 3, 4, 5]

相当于数学上的 [1, 5]

(2)until 表示「半开区间」

即:包含起点,不包含终点

val range = 1 until 5
println(range.toList()) // [1, 2, 3, 4]

相当于数学上的 [1, 5)

常用于循环: 因为索引常常是从 0 开始、到 size-1 结束:

for (i in 0 until list.size) { ... }
(3)downTo 表示「递减区间」
val range = 5 downTo 1
println(range.toList()) // [5, 4, 3, 2, 1]
(4)step 表示「步长」
val range = 1..10 step 2
println(range.toList()) // [1, 3, 5, 7, 9]

step 可用于任意区间(包括递减区间):

for (i in 10 downTo 0 step 3) {
    print("$i ") // 10 7 4 1
}

2.常见类型的区间

类型示例说明
IntRange1..5整型区间
LongRange1L..5L长整型区间
CharRange'a'..'z'字符区间
UIntRange1u..10u无符号整型区间
ULongRange1uL..10uL无符号长整型区间

字符区间很常用:

for (ch in 'a'..'f') print(ch) // abcdef

3.区间的常用方法

Kotlin 的区间类都实现了 ClosedRange<T> 接口,常用方法有:

方法说明示例
first起点(1..5).first → 1
last终点(1..5).last → 5
contains(x)in是否包含3 in 1..5 → true
isEmpty()是否为空(5..1).isEmpty() → true
reversed()反转区间(1..5).reversed() → 5 downTo 1

4.for 循环中的区间

for (i in 1..5) print("$i ")   // 1 2 3 4 5
for (i in 1 until 5) print("$i ") // 1 2 3 4
for (i in 5 downTo 1) print("$i ") // 5 4 3 2 1

5.in!in 判断运算符

区间经常和 in 一起用:

val x = 3
if (x in 1..5) {
    println("x 在区间内")
}

if (x !in 10..20) {
    println("x 不在区间内")
}

也支持字符比较:

if ('k' in 'a'..'z') println("是小写字母")

6.区间的本质

在 Kotlin 中:

val range = 1..5

实际上是:

val range = IntRange(1, 5)

.. 操作符其实是调用了一个函数:

operator fun Int.rangeTo(that: Int): IntRange

所以:

  • 1..5 → 调用 1.rangeTo(5)
  • 'a'..'z' → 调用 'a'.rangeTo('z')

7.区间的高级用法示例

(1)检查索引合法性
if (index in 0 until list.size) {
    println("合法索引")
}
(2)日期或自定义类也能比较(前提是实现 Comparable
val start = LocalDate.of(2025, 1, 1)
val end = LocalDate.of(2025, 12, 31)
val today = LocalDate.now()

if (today in start..end) {
    println("在今年内")
}

六、集合框架

1.Kotlin 集合框架总览

Kotlin 的集合主要分为两大类:

类型可变性常见实现对应 Java 类
只读集合 (List, Set, Map)不可修改(只读视图)listOf(), setOf(), mapOf()Collections.unmodifiableList()
可变集合 (MutableList, MutableSet, MutableMap)可添加、删除、修改mutableListOf(), mutableSetOf(), mutableMapOf()ArrayList, HashSet, HashMap

Kotlin 并没有自己实现底层数据结构,而是封装了 Java 集合类,并提供了更安全的接口。

2.常用创建方式

// 只读集合
val list = listOf("A", "B", "C")
val set = setOf(1, 2, 3)
val map = mapOf("key1" to 1, "key2" to 2)

// 可变集合
val mutableList = mutableListOf("A", "B", "C")
mutableList.add("D")

val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.remove(2)

val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3

3.核心操作(函数式风格)

Kotlin 集合支持 链式操作

val nums = listOf(1, 2, 3, 4, 5)

// 过滤 + 映射
val result = nums.filter { it % 2 == 0 }     // [2, 4]
                 .map { it * it }             // [4, 16]

// 求和、平均
nums.sum()     // 15
nums.average() // 3.0

// 分组
nums.groupBy { it % 2 }  // {1=[1, 3, 5], 0=[2, 4]}

// 去重、排序
nums.distinct()           // 去重
nums.sortedDescending()   // 排序

4.只读 vs 可变:编译期安全

Kotlin 的设计理念是:默认不可变,除非必要时可变

val list = listOf(1, 2, 3)
list.add(4) // ❌ 编译错误:List 没有 add()

val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4) // ✅

⚠️ 需要注意: “只读集合”并不意味着数据真的不可变,只是接口层面只读。如果底层仍然指向可变集合,数据仍可能被修改。


5.Java 兼容性

Kotlin 集合完全兼容 Java:

val javaList = java.util.ArrayList<String>()
val kotlinList: MutableList<String> = javaList  // ✅ 自动映射

Kotlin 编译器会在编译期自动做类型桥接。

6.集合扩展函数(Kotlin 独有)

Kotlin 给所有集合类型都定义了很多扩展函数,如:

分类常见函数
查找find, first, last, indexOf
统计count, sum, average, maxBy
转换map, flatMap, zip, associate
过滤filter, filterNot, dropWhile
聚合reduce, fold
拆分partition, chunked, windowed

例如:

val names = listOf("Tom", "Jerry", "Spike")
val shortNames = names.filter { it.length <= 3 }
val upper = names.map { it.uppercase() }