Files
From_Kotlin_to_Kotlin-Multi…/Learning/引入Kotlin.md
T
2026-09-12 21:15:20 +08:00

18 KiB
Raw Blame History

引入 Kotlin

预计时间 1d

个人认为,Kotlin 是最好的 JVM 语言。

如果你会写 Java 或 Cpp/Python

请看 为会写其他高级语言的朋友们设计的引入

推荐 IDE

这边推荐 Jetbrains 的 IntelliJ IDEA

当然,各种类型的 VS Code,Neo Vim 也是很不错的,这边不提供具体教学。

Hello World!

先创建一个完全空的 Kotlin 项目/文件,在里面写:

fun main() {
    println("Hello World!")
}

编译运行。要么点击上方的绿色开始键,要么是 main 函数左侧的绿色开始键。

结果:

Hello World

进程已结束,退出代码为 0

这是一切的开始。

变量

变量很简单,对于一个数字变量,可以直接这么写:

val a = 1
var b = 2

这里,val 的意思是,a 是一个不可变的变量,var 的意思是,b 是一个可变的变量。

更准确地说,val 是只读变量:你不能让 a 再指向另一个对象,但如果它指向的对象本身可变,对象内部仍然可能改变。初学阶段可以先把它理解成“不能重新赋值的变量”。

类型推断与显式类型

Kotlin 会根据右边的值自动推断类型,这叫类型推断:

val intValue = 1          // Int
val longValue = 1L        // Long
val doubleValue = 3.14    // Double
val floatValue = 3.14f    // Float
val text = "Kotlin"       // String
val flag = true           // Boolean
val letter = 'K'          // Char

如果需要,也可以显式写出类型:

val a: Int = 1
val b: String = "abc"
val c: Double = 3.0

Kotlin 的类型都使用大写开头,例如 Int、Long、Double、Boolean、String、Char。

数字还支持常见的非十进制写法:

val hex = 0xFF
val binary = 0b1010
val underscore = 1_000_000

字符串模板

Kotlin 的字符串可以直接嵌入变量或表达式:

val name = "Kotlin"
val version = 2
println("Hello, $name!")
println("Version: ${version + 1}")

$name 用于简单变量,${...} 用于表达式。

常量、延迟初始化与懒加载

如果值在编译期就能确定,并且是不可变的,可以使用 const val。它通常写在顶层或 object / companion object 中:

const val MAX_COUNT = 100
const val DEFAULT_NAME = "guest"

普通类或函数中不能定义 const 变量。类中的只读属性通常用 val:

class Demo {
    val normal: Int = 1

    lateinit var text: String

    val lazyValue: Int by lazy {
        println("第一次访问时才会计算")
        42
    }
}
  • lateinit var:允许稍后初始化,适合不能在构造函数中确定的值。直接使用未初始化的 lateinit 变量会抛异常。
  • by lazy:第一次访问时才执行 lambda 并得到值,之后缓存结果。适合初始化成本较高的只读属性。

可空类型

Kotlin 把“可能为空”写进类型里:

var name: String = "Kotlin"
// name = null // 编译错误

var nullableName: String? = null
nullableName = "Kotlin"

String? 表示这个变量可能是 String,也可能是 null。要访问可空对象,需要安全调用:

val length = nullableName?.length ?: 0
  • ?.:如果对象为 null,整个表达式返回 null。
  • ?::Elvis 运算符,如果左边为 null,使用右边的值。
  • !!:强制认为对象非空,如果真的是 null 会抛异常。除非你很确定,否则少用。

函数

函数的作用,是把一段逻辑取个名字,之后可以重复调用。Kotlin 的函数比 Java 更自由:函数可以直接写在文件顶层,不必先放进某个类里。

基本写法

Kotlin 函数使用 fun 关键字:

fun add(a: Int, b: Int): Int {
    return a + b
}
  • fun:声明这是一个函数。
  • add:函数名。
  • a: Int, b: Int:参数列表。每个参数都必须写成 参数名: 类型。
  • : Int:返回值类型。表示这个函数会返回一个 Int。
  • { ... }:函数体。
  • return:返回最终结果,可以是表达式,也可以是一个变量。

调用函数时直接写函数名和参数:

val result = add(1, 2)
println(result) // 3

main 函数

Kotlin 程序从 main 函数开始执行:

fun main() {
    println("Hello World!")
}

main 是顶层函数,不需要写在类里面。它没有参数,也没有返回值时,返回类型可以省略。

没有返回值

如果函数没有返回值,可以省略返回类型,也可以显式写 Unit:

fun printMessage(message: String) {
    println(message)
}

fun printMessage2(message: String): Unit {
    println(message)
}

Unit 相当于 Java / C++ 里的 void。实际写代码时,大多数情况直接省略即可。

Unit 函数里也可以写 return,但后面不跟值:

fun printPositive(number: Int) {
    if (number <= 0) {
        return
    }
    println(number)
}

这里 return 的含义是终止函数。

单表达式函数

如果函数体只有一个表达式,可以省掉大括号,直接写等号:

fun add(a: Int, b: Int): Int = a + b

fun square(x: Int) = x * x

square 的返回类型可以由编译器推断出来。不过如果是给别人用的公开函数,建议还是把返回类型写清楚,代码会更易读。

默认参数

Kotlin 支持给参数设置默认值:

fun greet(name: String = "Kotlin") {
    println("Hello, $name!")
}

greet()          // Hello, Kotlin!
greet("World")   // Hello, World!

默认参数的好处是,不需要为了几种常见情况写很多重载函数。调用时如果不传这个参数,就使用默认值。

写出参数名

调用函数时,可以写出参数名:

fun createUser(name: String, age: Int, city: String = "Unknown") {
    println("$name, $age, $city")
}

createUser(age = 18, name = "Alice")

命名参数的意义是,调用处更清楚每个值代表什么。参数很多时尤其有用。命名参数还可以和默认参数配合,只传需要修改的部分。

变长参数

如果参数个数不确定,可以使用 vararg:

fun sum(vararg numbers: Int): Int {
    var result = 0
    for (number in numbers) {
        result += number
    }
    return result
}

println(sum(1, 2, 3))
println(sum(10, 20, 30, 40))

vararg 参数在函数内部可以当作数组一样遍历。一个函数通常只有一个 vararg,而且一般放在参数列表最后。

如果已经有一个数组,可以用 * 展开后传进去:

val numbers = intArrayOf(1, 2, 3)
println(sum(*numbers))

局部函数

函数内部也可以定义函数,这叫局部函数:

fun outer() {
    fun inner(x: Int): Int {
        return x * 2
    }

    println(inner(3)) // 6
}

局部函数只在外部函数内部可见,适合把一段只在这里使用的逻辑拆出来,避免重复。

函数也可以作为值

Kotlin 里函数不只能调用,还可以像变量一样传递。函数类型写成:

(参数类型, 参数类型) -> 返回类型

例如:

val square: (Int) -> Int = { x -> x * x }

这里的 square 是一个变量,它的类型是 (Int) -> Int,表示接收一个 Int,返回一个 Int。具体 lambda 写法后面会详细讲。

控制流

控制流决定代码按什么顺序执行。Kotlin 的 if、when 都是表达式,也就是它们可以直接产生一个值。

if 表达式

普通写法:

val a = 10
val b = 20

if (a > b) {
    println("a 更大")
} else {
    println("b 更大")
}

因为 if 可以返回值,所以可以这样写:

val max = if (a > b) a else b
println(max) // 20

也可以写代码块,最后一行作为结果:

val result = if (a > b) {
    println("a 更大")
    a
} else {
    println("b 更大")
    b
}

Kotlin 没有三目运算符 ? :,因为 if ... else ... 已经能当表达式使用。

when 表达式

when 类似其他语言的 switch,但更灵活:

fun describe(obj: Any): String {
    return when (obj) {
        1 -> "One"
        "Hello" -> "Greeting"
        is Long -> "Long"
        !is String -> "Not a string"
        else -> "Unknown"
    }
}

特点:

  • 不需要 break。
  • 每个分支用 ->。
  • 可以匹配具体值、区间、类型、条件。
  • when 也可以作为表达式,直接把结果赋给变量。
val score = 85

val level = when (score) {
    in 90..100 -> "优秀"
    in 80 until 90 -> "良好"
    in 60 until 80 -> "及格"
    else -> "不及格"
}

如果 when 作为表达式使用,并且没有覆盖所有情况,就必须写 else。

for 循环

遍历区间:

for (i in 1..5) {
    println(i) // 1 2 3 4 5
}

.. 表示闭区间,包含左右两端。

左闭右开区间用 until:

for (i in 1 until 5) {
    println(i) // 1 2 3 4
}

降序和步长:

for (i in 5 downTo 1) {
    println(i)
}

for (i in 1..10 step 2) {
    println(i) // 1 3 5 7 9
}

遍历集合:

val items = listOf("apple", "banana", "orange")

for (item in items) {
    println(item)
}

同时拿到下标和值:

for ((index, value) in items.withIndex()) {
    println("$index: $value")
}

while 与 do-while

while 先判断条件,再执行:

var i = 0

while (i < 3) {
    println(i)
    i++
}

do-while 先执行一次,再判断条件:

var j = 0

do {
    println(j)
    j++
} while (j < 3)

break 与 continue

break 结束整个循环,continue 跳过本次循环,进入下一次:

for (i in 1..10) {
    if (i == 3) {
        continue
    }
    if (i == 8) {
        break
    }
    println(i)
}

嵌套循环中,可以用标签指定跳出哪一层:

outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (i == 2 && j == 2) {
            break@outer
        }
        println("$i, $j")
    }
}

初学阶段知道有标签即可,平时不一定会经常用到。

集合

集合是日常写程序最常用的工具之一。Kotlin 把集合分成“只读”和“可变”两类。

创建集合

val list = listOf(1, 2, 3, 4, 5, 6) // 只读 List
val mutableList = mutableListOf(1, 2, 3) // 可变 List

val set = setOf(1, 2, 2, 3) // 只读 Set,自动去重
val mutableSet = mutableSetOf(1, 2, 3)

val map = mapOf("a" to 1, "b" to 2) // 只读 Map
val mutableMap = mutableMapOf<String, Int>()

val array = arrayOf("1", "2", "3") // 数组
val ints = intArrayOf(1, 2, 3) // 基本类型数组

listOf、setOf、mapOf 创建的是只读集合,不能添加、删除元素。需要修改时用 mutableListOf、mutableSetOf、mutableMapOf。

访问与修改

val list = listOf(10, 20, 30)

println(list[0])      // 10
println(list.first()) // 10
println(list.last())  // 30
println(list.size)    // 3

可变列表可以修改:

val list = mutableListOf(10, 20, 30)

list.add(40)
list.remove(20)
list[0] = 100

println(list) // [100, 30, 40]

Map 的访问:

val map = mutableMapOf("a" to 1, "b" to 2)

println(map["a"]) // 1
map["c"] = 3
println(map) // {a=1, b=2, c=3}

遍历集合

val list = listOf("a", "b", "c")

for (item in list) {
    println(item)
}

list.forEach { item ->
    println(item)
}

遍历 Map:

val map = mapOf("a" to 1, "b" to 2)

for ((key, value) in map) {
    println("$key = $value")
}

常用集合操作

Kotlin 的集合操作很简洁,很多操作会返回一个新集合,而不是修改原集合:

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

val doubled = numbers.map { it * 2 }
// [2, 4, 6, 8, 10]

val evens = numbers.filter { it % 2 == 0 }
// [2, 4]

val total = numbers.sum()
// 15

val hasBig = numbers.any { it > 4 }
// true

val allPositive = numbers.all { it > 0 }
// true

val firstBig = numbers.find { it > 3 }
// 4

val sorted = numbers.sortedDescending()
// [5, 4, 3, 2, 1]

val text = numbers.joinToString()
// "1, 2, 3, 4, 5"

链式调用:

val result = numbers
    .filter { it % 2 == 0 }
    .map { it * 10 }

println(result) // [20, 40]

这种写法在 Kotlin 中非常常见。filter 负责筛选,map 负责转换,最后得到新的结果。

Lambda 与高阶函数

Lambda 是一段可以当作值传递的代码。高阶函数则是“接收函数作为参数”或者“返回函数”的函数。

Lambda 基本写法

val add = { a: Int, b: Int ->
    a + b
}

println(add(1, 2)) // 3

Lambda 用大括号包起来,箭头左边是参数,箭头右边是函数体。函数体最后一行会作为返回值,不需要写 return。

没有参数:

val hello = {
    println("Hello")
}

hello()

只有一个参数时,可以省略参数名,用 it 代替:

val square: (Int) -> Int = { it * it }

println(square(5)) // 25

也可以自己命名参数:

val square2: (Int) -> Int = { x -> x * x }

Lambda 的类型写法:

val func: (Int, String) -> String = { number, text ->
    text + number
}

(Int, String) -> String 表示:接收一个 Int 和一个 String,返回一个 String。

高阶函数

如果函数的参数或返回值是函数,它就是高阶函数:

fun applyTwice(x: Int, operation: (Int) -> Int): Int {
    return operation(operation(x))
}

val result = applyTwice(3) { it * 2 }
println(result) // 12

这里 operation 的类型是 (Int) -> Int,表示它接收一个 Int,返回一个 Int。

尾随 lambda

如果函数的最后一个参数是 lambda,可以把 lambda 写在圆括号外面:

fun repeatTwice(action: () -> Unit) {
    action()
    action()
}

repeatTwice {
    println("Hello")
}

如果 lambda 是唯一参数,圆括号也可以省略:

repeatTwice {
    println("Kotlin")
}

如果函数还有其它参数,最后一个 lambda 仍然可以写在外面:

val sum = listOf(1, 2, 3).fold(0) { acc, item ->
    acc + item
}

fold 的第一个参数是初始值 0,后面的 lambda 负责累加。

返回函数

函数也可以返回另一个函数:

fun makeAdder(x: Int): (Int) -> Int {
    return { y -> x + y }
}

val add5 = makeAdder(5)
println(add5(3)) // 8

返回的 lambda 可以捕获外部变量 x,这种能力让 Kotlin 在写回调和 DSL 时非常方便。

可空函数类型

函数类型的变量也可以为空:

var callback: (() -> Unit)? = null

callback?.invoke()

callback = { println("Done") }
callback?.invoke()

?.invoke() 表示如果 callback 不为空,就调用它。

空安全

Kotlin 把“这个值可能为 null”写进类型系统里。这样可以减少很多运行时的空指针异常。

可空类型

普通类型不能赋值为 null:

var name: String = "Kotlin"
// name = null // 编译错误

如果确实可能为空,要在类型后面加 ?:

var nullableName: String? = null
nullableName = "Kotlin"

String 和 String? 是两种不同的类型。String? 表示它可能是 String,也可能是 null。

安全调用

访问可空对象时,使用 ?.:

val name: String? = null
val length = name?.length

如果 name 是 null,整个表达式返回 null,不会抛异常。所以 length 的类型是 Int?。

Elvis 运算符

?: 表示如果左边为 null,就使用右边的值:

val name: String? = null
val length = name?.length ?: 0

println(length) // 0

Elvis 运算符经常和安全调用一起使用:

val displayName = user?.name ?: "匿名用户"

非空断言

!! 表示“我确定它不是 null”:

val name: String? = "Kotlin"
val length = name!!.length

如果 name 真的是 null,这里会抛出异常。除非你非常确定,否则不要随便用 !!。

let 处理可空值

let 可以让一段代码只在对象非空时执行:

val name: String? = "Kotlin"

name?.let {
    println("长度是 ${it.length}")
}

如果 name 是 null,let 里面的代码不会执行。

安全转换

类型转换时,可以用 as?,转换失败会返回 null,而不是抛异常:

val obj: Any = "hello"
val text: String? = obj as? String

对应的 as 如果转换失败,会抛异常。初学阶段知道 as? 更安全即可。

类与接口

这部分只讲最基础的概念。类里面的成员函数、继承细节、接口默认实现等内容,本引入不展开。

类

最简单的类:

class MyClass

带主构造函数的类:

class Person(val name: String, var age: Int)
  • val name:只读属性,创建后不能重新赋值。
  • var age:可变属性,可以修改。

创建对象:

val person = Person("Alice", 20)

println(person.name)
person.age = 21
println(person.age)

如果需要在创建对象时执行一些初始化逻辑,可以用 init 块:

class Person(val name: String) {
    init {
        println("创建了 $name")
    }
}

类里面也可以定义函数,但本引入先不展开成员函数。

继承

Kotlin 的类默认不能被继承。要允许继承,需要加 open:

open class Animal(val name: String)

class Dog(name: String) : Animal(name)

子类继承父类时,需要在类名后面写出父类构造调用。

接口

接口用 interface 定义:

interface Greeter {
    fun greet(): String
}

接口可以只声明函数,也可以提供默认实现。这里先知道接口长什么样即可。

数据类

如果类的主要作用是保存数据,可以用 data class:

data class User(val id: Int, val name: String)

val user = User(1, "Alice")
println(user) // User(id=1, name=Alice)

val copied = user.copy(name = "Bob")
println(copied) // User(id=1, name=Bob)

data class 会自动生成 toString、equals、hashCode、copy 等方法,很适合表示简单数据。是一个替代 Lombook 的好东西。

接下来可以看

目录

下一篇:深入 Kotlin

进入 KMP