编码 · 编程语言
Kotlin 入门教程
简短回答
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| 类型 | 示例 | 说明 |
|---|---|---|
Int | 42 | 32 位有符号整数 |
Long | 42L | 64 位有符号整数 |
Double | 3.14 | 64 位浮点数 |
Boolean | true | true 或 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 32data 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。