cv.cm

编码 · 编程语言

Kotlin 入门教程

阅读约 10 分钟

简短回答

Kotlin 是 JetBrains 推出的现代静态类型语言,运行在 JVM 上,也是 Android 开发的首选语言。它语法简洁,与 Java 完全互操作,其类型系统能在编译期避免大多数空指针错误。本页通过可运行的示例讲解环境搭建、变量、空安全、流程控制、函数、集合、类、协程和错误处理。

Kotlin 是什么,用来做什么

Kotlin 是 JetBrains 创建的静态类型通用编程语言。它运行在 Java 虚拟机(JVM)上,也可以编译成 JavaScript、WebAssembly 和原生机器码。Kotlin 与 Java 完全互操作:Kotlin 文件可以调用任何 Java 库,Java 代码也可以调用 Kotlin。

自 2019 年起,Google 将 Kotlin 推荐为 Android 应用的首选语言,因此大多数新的 Android 代码都是 Kotlin。它在服务端也很常见,常搭配 Spring Boot、Ktor 等框架;借助 Kotlin Multiplatform,团队还可以在 Android、iOS、桌面和 Web 之间共享业务逻辑。

安装 Kotlin 并运行 Hello, world

最简单的入门方式是使用自带 Kotlin 的 IntelliJ IDEA 或 Android Studio。若使用命令行,则需要安装编译器(例如通过 SDKMAN! 或 Homebrew)和 Java 运行环境(JDK)。Kotlin 程序从顶层的 main 函数开始执行,不需要定义类。

// hello.kt
fun main() {
    println("Hello, world!")
}
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
# Hello, world!

实际项目通常使用 Gradle(配合 Kotlin DSL)等构建工具。你也可以在在线的 Kotlin Playground 中免安装试运行代码。

变量与基本类型

用 val 声明只读变量,用 var 声明可变变量。编译器会自动推断类型,你也可以在冒号后显式写出类型。优先使用 val,这样代码更容易理解。

val name = "Ada"          // String, cannot be reassigned
var count: Int = 0        // explicit type, can change
count += 1
val pi = 3.14159          // Double
val big = 9_000_000_000L  // Long
println("$name has $count item(s), pi is ${"%.2f".format(pi)}")
// Ada has 1 item(s), pi is 3.14
类型示例说明
Int4232 位有符号整数
Long42L64 位有符号整数
Double3.1464 位浮点数
Booleantruetrue 或 false
Char'k'单个字符
String"text"不可变;支持 $name 字符串模板

Kotlin 不会做隐式的数值类型拓宽:Int 不会自动转换为 Long。请显式调用 toLong()、toDouble() 或 toString()。

空安全

空安全是 Kotlin 的标志性特性。像 String 这样的普通类型永远不能为 null。若要允许为空,需要加上 ?,写成 String?,此后编译器会强制你在使用该值之前处理空值情况。

fun greet(name: String?) {
    // println(name.length)        // compile error: name may be null
    println(name?.length)           // safe call: prints null if name is null
    val len = name?.length ?: 0     // Elvis operator: default value
    if (name != null) {
        println(name.uppercase())   // smart cast: name is String here
    }
    println("length = $len")
}
fun main() {
    greet("kotlin")   // 6, KOTLIN, length = 6
    greet(null)       // null, length = 0
}

流程控制:if、when 与循环

在 Kotlin 中,if 和 when 都是表达式,可以返回值。when 取代了 Java 和 C 中的 switch 语句,能够匹配具体值、范围和类型。

val score = 82
val grade = if (score >= 50) "pass" else "fail"
val letter = when (score) {
    in 90..100 -> "A"
    in 80..89 -> "B"
    in 70..79 -> "C"
    else -> "F"
}
println("$grade $letter")         // pass B
for (i in 1..3) print("$i ")      // 1 2 3
println()
for (i in 10 downTo 0 step 5) print("$i ")  // 10 5 0
println()
var n = 3
while (n > 0) { print(n); n-- }   // 321
println()

1..3 是包含两端的闭区间;0 until 3(或 0..<3)不包含终点,这通常正是遍历索引时需要的。

函数与 lambda

函数用 fun 声明。参数可以有默认值,调用时可以使用具名参数,函数体只有一个表达式时可以用 = 简写。

fun area(width: Double, height: Double = 1.0): Double = width * height
fun main() {
    println(area(3.0, 2.0))          // 6.0
    println(area(width = 4.0))       // 4.0 (default height)
    val square = { x: Int -> x * x } // a lambda stored in a variable
    println(square(5))               // 25
    val nums = listOf(1, 2, 3, 4, 5)
    val evensSquared = nums.filter { it % 2 == 0 }.map { it * it }
    println(evensSquared)            // [4, 16]
}

当 lambda 只有一个参数时,可以用 it 引用它。当函数的最后一个参数是 lambda 时,可以把它写在括号外面,所以 filter { ... } 读起来就像内置的代码块。

集合与数据类

标准库区分只读集合和可变集合:listOf、setOf、mapOf 创建只读集合,而 mutableListOf、mutableSetOf、mutableMapOf 创建可修改的集合。

val fruits = mutableListOf("apple", "banana")
fruits.add("cherry")
val ages = mapOf("Ann" to 31, "Bo" to 25)
println(fruits.size)              // 3
println(ages["Ann"])              // 31
println(ages["Zed"])              // null (map lookups are nullable)
data class User(val name: String, val age: Int)
val u = User("Ann", 31)
val older = u.copy(age = 32)
println(u)                        // User(name=Ann, age=31)
println(u == User("Ann", 31))     // true (structural equality)
val (n, a) = older                // destructuring
println("$n $a")                  // Ann 32

data class(数据类)会自动获得 equals、hashCode、toString、copy 和解构功能。Kotlin 中的 == 比较内容(会调用 equals),=== 则比较引用。

类、扩展函数与协程

类默认是 final 的;加上 open 才允许被继承。扩展函数可以在不修改原类型的前提下为它添加方法,sealed(密封)类则用来表示封闭的一组情形,when 可以对其做穷尽性检查。

fun String.shout(): String = uppercase() + "!"
sealed class Shape
data class Circle(val r: Double) : Shape()
data class Rect(val w: Double, val h: Double) : Shape()
fun area(s: Shape): Double = when (s) {
    is Circle -> Math.PI * s.r * s.r
    is Rect -> s.w * s.h
}   // no else needed: the compiler knows all cases
fun main() {
    println("hi".shout())                   // HI!
    println(area(Rect(2.0, 3.0)))           // 6.0
}

对于异步和并发代码,Kotlin 使用 kotlinx.coroutines 库提供的协程。suspend 函数可以在不阻塞线程的情况下暂停,launch、async 等构建器会在作用域内启动协程。Android 应用就是这样在主线程之外发起网络请求的。

错误处理

Kotlin 与 Java 一样有 try、catch 和 finally,但所有异常都是非受检异常:你不必声明或捕获它们。try 同样是表达式。对于可预期的失败,标准库提供了 runCatching 和 Result,而 toIntOrNull() 之类的函数会返回 null 而不是抛出异常。

fun parse(s: String): Int = try {
    s.toInt()
} catch (e: NumberFormatException) {
    -1
}
fun main() {
    println(parse("42"))              // 42
    println(parse("x"))               // -1
    println("7".toIntOrNull())        // 7
    println("seven".toIntOrNull())    // null
    val r = runCatching { "abc".toInt() }
    println(r.isFailure)              // true
}

一个完整的小程序:词频统计

这个程序统计文本中每个单词出现的次数,并输出前三名。它用到了字符串扩展、集合函数和数据类。

data class WordCount(val word: String, val count: Int)
fun topWords(text: String, n: Int): List<WordCount> =
    text.lowercase()
        .split(Regex("[^a-z']+"))
        .filter { it.isNotBlank() }
        .groupingBy { it }
        .eachCount()
        .map { (w, c) -> WordCount(w, c) }
        .sortedWith(compareByDescending<WordCount> { it.count }.thenBy { it.word })
        .take(n)
fun main() {
    val text = "The cat and the dog. The dog sat, and the cat ran."
    for ((word, count) in topWords(text, 3)) {
        println("$word: $count")
    }
}
// the: 4
// and: 2
// cat: 2

接下来学什么

  • kotlinlang.org 上的官方 Kotlin 文档,从「Basic syntax」和「Kotlin tour」开始。
  • Kotlin Koans:一组可在浏览器中运行的小练习。
  • Android Developers 上的 Kotlin 开发指南,或服务端 Kotlin 的 Ktor 与 Spring 指南。
  • 需要写异步代码时,阅读 kotlinx.coroutines 指南。

常见问题

Kotlin 比 Java 好吗?

Kotlin 更简洁,并提供空安全、数据类和协程,但它与 Java 运行在同一个 JVM 上,使用相同的库。许多团队在一个项目中混用两种语言,所以选择更多取决于风格和开发效率,而非能力差异。

学 Kotlin 之前需要先学 Java 吗?

不需要。你可以把 Kotlin 作为第一门 JVM 语言来学。了解一些 Java 有助于阅读较早的 Android 代码或 Java 库的文档。

val 和 var 有什么区别?

val 声明只读引用,初始化后不能再赋值;var 声明可重新赋值的引用。指向可变列表的 val,其列表内容仍然可以修改。

Kotlin 能开发 iOS 应用吗?

可以,通过 Kotlin Multiplatform 把共享的 Kotlin 代码编译成 iOS 原生代码。界面通常仍用 Swift 编写,不过 Compose Multiplatform 也可以共享界面代码。

Kotlin 中的 ?: 运算符有什么作用?

它是 Elvis 运算符:a ?: b 在 a 不为 null 时返回 a,否则返回 b。它常与安全调用搭配使用,如 name?.length ?: 0。

全部编码教程