第1章:Kotlin是什么(What is Kotlin?)
一、导读
1.1 本章学习目标
第1章是《Kotlin in Action》的开篇之作,它为读者描绘了 Kotlin 语言的全貌。作为 Kotlin 核心开发团队成员撰写的权威指南,本章不仅介绍了 Kotlin 的基本特性,更重要的是帮助 Java 开发者理解 Kotlin 的设计哲学——它如何解决 Java 的痛点,以及如何在保持与 Java 完全互操作的同时,提供更简洁、更安全的编程体验。
本章的核心学习目标包括:
- 理解 Kotlin 的设计目标:Kotlin 为什么被创造出来?它要解决 Java 的哪些问题?
- 掌握 Kotlin 与 Java 的互操作性:Kotlin 如何无缝调用 Java 代码,Java 又如何调用 Kotlin 代码?
- 认识空安全(Null Safety)的威力:Kotlin 的类型系统如何从编译期消除 NullPointerException?
- 体验 Kotlin 的简洁语法:对比 Java,Kotlin 如何用更少的代码表达同样的逻辑?
- 了解 Kotlin 的应用场景:从 Android 开发到服务端应用,Kotlin 适合做什么?
- 搭建 Kotlin 开发环境:配置 IntelliJ IDEA 或命令行环境,编写第一个 Kotlin 程序。
1.2 为什么第一章如此重要
很多技术书籍的第一章往往是"走过场"式的介绍,但《Kotlin in Action》的第1章不同。作者 Dmitry Jemerov 和 Svetlana Isakova 都是 JetBrains 的 Kotlin 核心团队成员,他们在这一章中融入了大量语言设计背后的思考。理解这些"为什么",比单纯记住语法"是什么"更重要。
Kotlin 并不是又一个"更好的 Java"。它的设计哲学是:务实的创新。每一个语法糖背后都有一个真实的痛点,每一个特性都经过了深思熟虑。比如空类型系统不仅仅是为了避免 NPE,更是为了让你在设计 API 时就能清晰地表达"这个值可能不存在"的语义。
如果你是 Java 开发者,这一章会让你看到 Java 的局限性以及 Kotlin 如何优雅地解决这些问题。如果你是从其他语言转过来的,这一章会帮你快速建立 Kotlin 的心智模型。
1.3 核心问题引导
在阅读本章时,请带着以下问题思考:
Kotlin 和 Java 的关系到底是什么?它是 Java 的替代品还是补充?
为什么 Kotlin 能做到与 Java 100% 互操作?底层是如何实现的?
String? 和 String 的区别仅仅是"能不能为 null"吗?它对 API 设计有什么影响?
Kotlin 的 val 和 var 与 Java 的 final 变量有什么本质区别?
为什么说 Kotlin 的集合 API 比 Java 的更"函数式"?
Kotlin 是如何在不引入新语法的 JVM 字节码的情况下实现这么多特性的?
二、核心概念详解
2.1 Kotlin 的诞生背景与设计哲学
2.1.1 为什么需要 Kotlin
2010 年前后,Java 是世界上最流行的编程语言之一,但它也积累了越来越多的痛点:
- 冗长的语法:实现一个简单的功能需要大量的样板代码(getter、setter、equals、hashCode、toString)
- 空指针异常:Tony Hoare 称之为"十亿美元的错误",Java 的类型系统无法在编译期防止 NPE
- 缺乏函数式编程支持:Java 7 及之前版本没有 Lambda 表达式,集合操作需要大量的循环代码
- 扩展困难:无法为已有的类添加方法,工具类遍布各处
- 类型推断薄弱:每个变量都需要显式声明类型,代码冗余
JetBrains 作为 IntelliJ IDEA 的开发者,每天与 Java 打交道。他们尝试了 Scala,但 Scala 的学习曲线太陡峭,编译速度太慢。于是他们决定创造一门新语言——既要有现代语言的特性,又要保持与 Java 的完全兼容,还要有极快的编译速度。这就是 Kotlin。
2.1.2 Kotlin 的设计原则
Kotlin 的设计遵循几个核心原则:
简洁性(Conciseness):减少样板代码,让开发者专注于业务逻辑。一个数据类只需要一行代码就能替代 Java 中几十行的 POJO。
安全性(Safety):通过类型系统在编译期捕获错误。空类型系统是最典型的例子——如果一个变量可能为 null,编译器会强制你处理这种情况。
互操作性(Interoperability):与 Java 100% 兼容。Kotlin 代码可以无缝调用 Java 库,Java 代码也可以无缝调用 Kotlin 代码。Kotlin 编译后生成标准的 JVM 字节码,运行在 JVM 上。
工具友好(Tool-friendliness):Kotlin 的设计考虑了 IDE 的支持。类型推断、智能转换(smart cast)等特性不仅让代码更简洁,也让 IDE 能提供更好的代码补全和重构支持。
2.2 第一个 Kotlin 程序
让我们从一个经典的 Hello World 开始:
fun main(args: Array<String>) {
println("Hello, World!")
}对比 Java 版本:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}差异一目了然:
- Kotlin 不需要
class包裹——main函数可以是顶层函数 fun关键字声明函数,取代了 Java 的返回类型在前、方法名在后的语法println是顶层函数,不需要System.out的前缀- 不需要分号结尾
2.2.1 编译和运行
Kotlin 程序可以通过多种方式运行:
# 使用 kotlinc 编译
kotlinc main.kt -include-runtime -d main.jar
java -jar main.jar
# 使用 kotlin 脚本直接运行
kotlinc -script main.kts
# 使用 Gradle 构建
./gradlew runKotlin 编译器(kotlinc)将 .kt 文件编译为 .class 字节码文件,这些字节码与 Java 编译器生成的字节码完全相同,可以在任何 JVM 上运行。
2.3 变量与类型系统
2.3.1 val 与 var
Kotlin 使用 val 和 var 声明变量:
val answer: Int = 42 // 只读变量,不可重新赋值
var counter: Int = 0 // 可变变量,可以重新赋值
counter = 1 // OK
// answer = 43 // 编译错误:val 不能重新赋值val 对应 Java 的 final 变量,但 Kotlin 的 val 更加灵活。它可以是属性(property),带有自定义的 getter:
val isEmpty: Boolean
get() = size == 0 // 每次访问都重新计算更重要的是,Kotlin 有强大的类型推断能力:
val answer = 42 // 推断为 Int
val name = "Kotlin" // 推断为 String
val list = listOf(1, 2, 3) // 推断为 List<Int>编译器能从右侧的表达式推断出变量的类型,因此大多数时候不需要显式声明类型。
2.3.2 空安全(Null Safety)
空安全是 Kotlin 类型系统最重要的特性之一。在 Kotlin 中,类型系统区分可空类型和非空类型:
val nonNull: String = "Kotlin" // 非空类型,不能为 null
val nullable: String? = null // 可空类型,可以为 null
// nonNull = null // 编译错误!
// println(nullable.length) // 编译错误!需要处理 null 情况这个看似简单的区分有着深远的影响。在 Java 中,任何引用类型的变量都可能为 null,你必须通过文档或注释来了解一个方法是否可能返回 null。在 Kotlin 中,类型签名本身就告诉你这个值是否可能为 null。
Kotlin 提供了多种优雅的方式来处理可空值:
// 安全调用操作符 ?.
val length: Int? = nullable?.length // 如果 nullable 为 null,返回 null
// Elvis 操作符 ?:
val lengthOrDefault: Int = nullable?.length ?: 0 // 如果为 null,使用默认值
// 非空断言 !!
val lengthUnsafe: Int = nullable!!.length // 如果为 null,抛出 NPE(谨慎使用)
// let 函数
nullable?.let { value ->
println("Value is $value, length is ${value.length}")
} // 只在非 null 时执行2.3.3 智能转换(Smart Casts)
Kotlin 编译器能够追踪类型检查的结果,并在后续代码中自动进行类型转换:
fun process(value: Any) {
if (value is String) {
// 在这个分支中,value 自动被视为 String 类型
println(value.uppercase()) // 不需要显式转换
println(value.length)
}
if (value !is Int) return
// 这里 value 自动被视为 Int 类型
println(value * 2)
}这与 Java 形成鲜明对比——Java 需要显式的类型转换(cast),而且如果转换错误会在运行时抛出 ClassCastException。Kotlin 的智能转换在编译期就完成了类型检查,既安全又简洁。
2.4 类与对象
2.4.1 数据类(Data Class)
在 Java 中,创建一个简单的数据载体类需要大量的样板代码:
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person(name=" + name + ", age=" + age + ")";
}
}在 Kotlin 中,一行代码搞定:
data class Person(val name: String, val age: Int)编译器会自动生成 equals()、hashCode()、toString()、copy() 和 componentN() 函数。数据类还支持解构声明:
val person = Person("Alice", 30)
val (name, age) = person // 解构声明
println("$name, $age years old")2.4.2 属性与访问器
Kotlin 的属性(property)是字段(field)、getter 和 setter 的组合:
class Person {
var name: String = ""
set(value) {
println("Name changed from $field to $value")
field = value
}
val isEmpty: Boolean
get() = name.isEmpty()
}属性声明自动生成 getter(和 setter,如果是 var)。你可以自定义访问器的逻辑,同时使用 field 关键字访问底层的存储字段。
2.4.3 接口与默认方法
Kotlin 的接口可以包含抽象方法声明和带实现的方法:
interface Clickable {
fun click() // 抽象方法
fun showOff() = "I'm clickable" // 带默认实现的方法
}
interface Focusable {
fun focus()
fun showOff() = "I'm focusable"
}
// 实现多个接口时,需要覆盖冲突的默认方法
class Button : Clickable, Focusable {
override fun click() = println("Button clicked")
override fun focus() = println("Button focused")
override fun showOff() = super<Clickable>.showOff() // 指定调用哪个接口的方法
}2.5 与 Java 的互操作性
2.5.1 在 Kotlin 中调用 Java
Kotlin 可以无缝调用任何 Java 代码:
val list = java.util.ArrayList<String>()
list.add("Kotlin")
list.add("Java")
val size = list.size // Kotlin 将 getSize() 转换为属性访问语法
// 处理 Java 的可空性
val javaString: String! = getJavaString() // 平台类型(platform type)
// String! 表示"来自 Java 的类型,可空性未知"
// Kotlin 不会强制检查,但如果为 null 且你当作非空使用,会在运行时抛出 NPE2.5.2 在 Java 中调用 Kotlin
Java 代码也可以调用 Kotlin 代码,但有一些注意事项:
// Kotlin 代码
class Person(val name: String, val age: Int)
fun Person.isAdult(): Boolean = age >= 18// Java 调用
Person person = new Person("Alice", 30);
String name = person.getName(); // Kotlin 的属性变成 Java 的 getter
int age = person.getAge();Kotlin 的顶层函数会被编译为静态方法,放在一个名为 FileNameKt 的类中。可以使用 @JvmName 注解来改变生成的类名。
2.6 Kotlin 的集合操作
Kotlin 的集合 API 设计深受函数式编程的影响:
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 函数式风格的集合操作
val result = numbers
.filter { it % 2 == 0 } // 保留偶数
.map { it * it } // 求平方
.sortedDescending() // 降序排序
.take(3) // 取前3个
.sum() // 求和
// 对比 Java 7 的写法
int sum = 0;
for (int n : numbers) {
if (n % 2 == 0) {
sum += n * n;
}
}Kotlin 区分只读集合(List)和可变集合(MutableList),这与空安全的设计理念一致——默认不可变,需要可变时显式声明。
val readOnly: List<String> = listOf("a", "b", "c")
val mutable: MutableList<String> = mutableListOf("a", "b", "c")
// readOnly.add("d") // 编译错误:List 没有 add 方法
mutable.add("d") // OK三、实战案例
3.1 案例一:用 Kotlin 重写 Java 的 Person 管理器
让我们通过一个完整的例子来对比 Java 和 Kotlin 的写法差异。
Java 版本:
public class PersonManager {
private Map<String, Person> people = new HashMap<>();
public void addPerson(String name, int age) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
people.put(name, new Person(name, age));
}
public Person findPerson(String name) {
return people.get(name); // 可能返回 null
}
public List<Person> findAdults() {
List<Person> adults = new ArrayList<>();
for (Person p : people.values()) {
if (p.getAge() >= 18) {
adults.add(p);
}
}
return adults;
}
public int getTotalAge() {
int total = 0;
for (Person p : people.values()) {
total += p.getAge();
}
return total;
}
}Kotlin 版本:
data class Person(val name: String, val age: Int)
class PersonManager {
private val people = mutableMapOf<String, Person>()
fun addPerson(name: String, age: Int) {
require(name.isNotBlank()) { "Name cannot be blank" }
require(age in 0..150) { "Invalid age: $age" }
people[name] = Person(name, age)
}
fun findPerson(name: String): Person? = people[name]
fun findAdults(): List<Person> =
people.values.filter { it.age >= 18 }
fun getTotalAge(): Int =
people.values.sumOf { it.age }
}关键差异分析:
数据类:Kotlin 的 data class 一行替代了 Java 的整个 POJO 类
空安全:findPerson 的返回类型 Person? 明确告知调用者可能返回 null
集合操作:filter、sumOf 等高阶函数替代了循环
范围检查:age in 0..150 比 age < 0 || age > 150 更直观
`require` 函数:参数校验的惯用方式,失败时抛出 IllegalArgumentException
3.2 案例二:构建一个简单的 DSL
Kotlin 的 Lambda 和扩展函数使得构建 DSL 变得非常自然:
class HtmlBuilder {
private val elements = mutableListOf<String>()
fun head(block: HeadBuilder.() -> Unit) {
val head = HeadBuilder().apply(block)
elements.add("<head>${head.build()}</head>")
}
fun body(block: BodyBuilder.() -> Unit) {
val body = BodyBuilder().apply(block)
elements.add("<body>${body.build()}</body>")
}
fun build(): String = elements.joinToString("\n")
}
class HeadBuilder {
private var title: String = ""
fun title(title: String) {
this.title = title
}
fun build(): String = "<title>$title</title>"
}
class BodyBuilder {
private val content = mutableListOf<String>()
fun h1(text: String) = content.add("<h1>$text</h1>")
fun p(text: String) = content.add("<p>$text</p>")
fun build(): String = content.joinToString("\n")
}
fun html(block: HtmlBuilder.() -> Unit): String =
HtmlBuilder().apply(block).build()
fun main() {
val result = html {
head {
title("My Page")
}
body {
h1("Welcome to Kotlin")
p("This is a DSL example")
}
}
println(result)
}输出:
<head><title>My Page</title></head>
<body>
<h1>Welcome to Kotlin</h1>
<p>This is a DSL example</p>
</body>这个例子展示了 Kotlin 的"带接收者的 Lambda"(lambda with receiver)特性。HtmlBuilder.() -> Unit 表示 Lambda 的接收者类型是 HtmlBuilder,因此在 Lambda 内部可以直接调用 HtmlBuilder 的方法,无需通过参数传递。
3.3 案例三:空安全的实际应用
data class Address(val street: String, val city: String)
data class Company(val name: String, val address: Address?)
data class Employee(val name: String, val company: Company?)
fun getEmployeeCity(employee: Employee): String? {
// 链式安全调用:任何一环为 null,整个表达式返回 null
return employee.company?.address?.city
}
fun printCity(employee: Employee) {
val city = employee.company?.address?.city
// 使用 when 表达式处理不同的情况
when {
city == null -> println("City unknown")
city == "Beijing" -> println("Capital city: $city")
else -> println("City: $city")
}
// 使用 let 只在非 null 时执行操作
city?.let {
println("Processing employee in $it")
// 这里 it 是智能转换后的非空 String
sendNotification(it)
}
}四、本章小结
第1章为读者建立了 Kotlin 语言的整体认知框架:
Kotlin 的设计哲学:简洁、安全、互操作、工具友好。每一个特性都有明确的设计目标。
空安全类型系统:String 和 String? 的区分是 Kotlin 最重要的创新之一,它从编译期消除了 NullPointerException。
简洁的语法:数据类、属性访问器、类型推断、Lambda 表达式等特性大幅减少了样板代码。
与 Java 的互操作:Kotlin 编译为标准的 JVM 字节码,与 Java 代码完全兼容。平台类型(platform type)处理 Java 侧的可空性不确定性。
函数式编程支持:高阶函数、集合的函数式操作、不可变集合的默认使用,让代码更简洁、更安全。
本章最重要的启示是:Kotlin 不是"语法糖"的堆砌,而是一门经过精心设计的现代语言。它的每个特性都在解决真实的工程问题。理解这些"为什么",才能在使用 Kotlin 时做出正确的设计决策。
五、思考与练习
思考题
Kotlin 的空类型系统与 Java 的 @Nullable 注解有什么本质区别?为什么编译器能提供更强的保证?
val 和 Java 的 final 变量有什么区别?val 属性带有自定义 getter 时,"不可变"的含义是什么?
Kotlin 的接口默认方法与 Java 8 的默认方法有什么异同?当多个接口的默认方法冲突时,Kotlin 是如何处理的?
为什么说 Kotlin 的集合 API 设计鼓励不可变性?这种设计对并发编程有什么好处?
编程练习
用 Kotlin 重写一个你熟悉的 Java 工具类(如字符串处理工具),体会 Kotlin 的简洁性。
创建一个数据类 Book,包含 title、author、year 属性。利用数据类的 copy() 方法创建一个修改了 year 的副本。
编写一个函数,接收 List<String?>,返回所有非 null 且非空字符串的大写形式。使用 filter、map 和安全调用操作符。
用 Kotlin 的 when 表达式实现一个简单的 HTTP 状态码描述器,处理 200、301、404、500 等常见状态码。