Kotlin 入门教程:迈向现代编程的旅程
欢迎来到 Kotlin 的世界!如果你正在寻找一种现代、简洁、安全且与 Java 完全互操作的编程语言,那么 Kotlin 是一个极佳的选择。无论是 Android 开发、后端服务、前端 Web 应用(通过 Kotlin/JS)还是桌面应用,Kotlin 都能胜任。
本教程将带你一步步了解 Kotlin 的基础知识,从环境搭建到核心语法和概念。我们将力求详细,并提供丰富的代码示例,帮助你快速入门。
第一章:初识 Kotlin – 为什么选择它?
在深入学习之前,我们先来了解一下 Kotlin 是什么,以及为什么它在开发者社区中如此受欢迎。
1.1 什么是 Kotlin?
Kotlin 是一种由 JetBrains 公司开发的静态类型编程语言,它:
- 运行在 JVM 上: 这意味着它可以无缝使用 Java 库和框架,与现有的 Java 代码完美集成。
- 可以编译成 JavaScript: 允许你在浏览器中运行 Kotlin 代码。
- 可以编译成 Native 代码: 可以在没有 JVM 的环境中运行,例如 iOS、macOS、Linux、Windows 等,实现真正的多平台开发。
- 是 Google 官方推荐的 Android 开发语言: 这极大地推动了 Kotlin 在移动开发领域的发展。
1.2 为什么选择 Kotlin?
与 Java 等语言相比,Kotlin 提供了许多优势:
- 简洁性 (Conciseness): Kotlin 使用更少的代码完成更多的工作。它消除了许多 Java 中的冗余语法,例如分号(通常可选)、
new
关键字等。 - 安全性 (Safety): Kotlin 在设计时就考虑了 null 安全性,这极大地减少了 NullPointerException (NPE) 的发生,这是 Java 中一个常见的错误源。
- 互操作性 (Interoperability): Kotlin 代码可以轻松地调用 Java 代码,反之亦然。这意味着你可以逐步将 Kotlin 引入到现有的 Java 项目中,而无需重写整个项目。
- 工具友好 (Tool-friendly): 作为由 JetBrains 开发的语言,Kotlin 与 JetBrains 自家的 IDEs (特别是 IntelliJ IDEA 和 Android Studio) 完美集成,提供出色的代码补全、重构、调试等功能。
- 现代特性 (Modern Features): Kotlin 支持许多现代编程语言的特性,如数据类、密封类、扩展函数、协程 (Coroutines) 等,这些特性使得编写异步、并发代码更加容易和直观。
- 表达力强 (Expressive): 凭借其简洁和丰富的特性,Kotlin 代码通常更具表达力,更容易理解和维护。
简单来说,Kotlin 是一种更现代、更安全、更高效的 JVM 语言,尤其适合需要与 Java 生态系统紧密结合的场景。
第二章:环境搭建与第一个 Kotlin 程序
在开始编写 Kotlin 代码之前,你需要设置开发环境。最推荐的方式是使用 JetBrains 的 IntelliJ IDEA 或 Android Studio。
2.1 安装 IntelliJ IDEA 或 Android Studio
- IntelliJ IDEA: 如果你主要进行后端、桌面或通用 Kotlin 开发,可以选择 IntelliJ IDEA Community Edition (免费) 或 Ultimate Edition (付费)。访问 JetBrains 官网下载并安装。
- Android Studio: 如果你主要进行 Android 开发,Android Studio 是基于 IntelliJ IDEA 构建的,已经内置了 Kotlin 支持。访问 Android 开发者官网下载并安装。
本教程将主要以 IntelliJ IDEA 为例,但概念同样适用于 Android Studio。
2.2 创建第一个 Kotlin 项目
- 打开 IntelliJ IDEA。
- 点击 “Create New Project” 或 “File” -> “New” -> “Project…”.
- 在项目类型列表中,选择 “Kotlin”。
- 选择项目模板,通常选择 “JVM | IDEA”。点击 “Next”。
- 填写项目名称、项目位置。确保 “JDK” 已经配置正确(需要 Java 8 或更高版本)。点击 “Finish”。
- IDE 会为你创建一个基本的项目结构。找到
src
目录下的Main.kt
文件 (或者创建一个新的.kt
文件)。
2.3 编写并运行第一个 Kotlin 程序
打开 Main.kt
文件,你会看到如下代码:
kotlin
fun main(args: Array<String>) {
println("Hello, Kotlin!")
}
fun
: 关键字,用于声明一个函数 (function)。main
: 函数的名称,这是 Kotlin 程序的入口点,类似于 Java 中的public static void main(String[] args)
。(args: Array<String>)
: 函数的参数列表。args
是参数名,Array<String>
是参数类型,表示一个字符串数组。在很多简单的程序中,参数可以省略:fun main() { ... }
。{ ... }
: 函数体,包含要执行的代码。println()
: 一个标准库函数,用于将文本输出到控制台,并在末尾添加换行符。"Hello, Kotlin!"
: 一个字符串字面值。
要运行这个程序,你可以在 main
函数旁边的绿色小箭头上点击,然后选择 “Run ‘MainKt'”。你会在底部的运行窗口中看到输出:
Hello, Kotlin!
恭喜你!你已经成功创建并运行了你的第一个 Kotlin 程序。
第三章:基本语法与数据类型
现在,让我们深入了解 Kotlin 的基本语法和构建块。
3.1 变量 (Variables)
在 Kotlin 中声明变量有两种方式:val
和 var
。
val
(Value): 用于声明不可变变量(只读变量)。一旦赋值后,就不能再改变它的值,类似于 Java 中的final
变量。var
(Variable): 用于声明可变变量。它的值可以在后续的代码中被修改。
优先使用 val
是一个好的编程习惯,因为它使代码更安全,更易于理解。
“`kotlin
fun main() {
// 声明一个不可变的字符串变量
val greeting = “Hello”
// greeting = “Hi” // 错误:val 不可重新赋值
// 声明一个可变的整数变量
var count = 10
count = 20 // 正确:var 可以重新赋值
println(greeting) // 输出:Hello
println(count) // 输出:20
}
“`
3.2 数据类型 (Data Types)
Kotlin 拥有一些基本的数据类型,它们都是对象,而不是像 Java 那样区分基本类型和包装类型(尽管在 JVM 上运行时,基本类型在内部可能会被优化以提高性能)。
- 数字类型:
Byte
: 8 位有符号整数Short
: 16 位有符号整数Int
: 32 位有符号整数 (默认整数类型)Long
: 64 位有符号整数 (L 或 l 后缀,如100L
)Float
: 32 位单精度浮点数 (F 或 f 后缀,如3.14f
)Double
: 64 位双精度浮点数 (默认浮点类型)
- 布尔类型:
Boolean
:true
或false
- 字符类型:
Char
: 单个字符,用单引号'A'
表示。
- 字符串类型:
String
: 字符序列,用双引号"Hello"
表示。Kotlin 的字符串是不可变的。
- 数组类型:
Array
: 数组类,如Array<Int>
。Kotlin 还有一些专门的数组类,如IntArray
、CharArray
等,它们没有装箱开销,更接近 Java 的基本类型数组。
类型推断 (Type Inference):
Kotlin 编译器通常可以根据赋给变量的值自动推断出变量的类型,所以很多时候你不需要显式地指定类型。
“`kotlin
fun main() {
val age = 30 // 编译器推断为 Int
val name = “Alice” // 编译器推断为 String
val isStudent = true // 编译器推断为 Boolean
val price = 19.99 // 编译器推断为 Double
val largeNumber = 10000000000L // 编译器推断为 Long
// 显式指定类型 (可选)
val score: Int = 95
val pi: Double = 3.14159
val firstLetter: Char = 'K'
println("Age: $age, Type: ${age::class.simpleName}") // 输出 Age: 30, Type: Int
println("Name: $name, Type: ${name::class.simpleName}") // 输出 Name: Alice, Type: String
}
“`
3.3 字符串模板 (String Templates)
Kotlin 强大的字符串模板功能允许你在字符串中直接引用变量或表达式,无需使用字符串拼接。
“`kotlin
fun main() {
val name = “Bob”
val age = 25
// 引用变量
val message1 = "My name is $name and I am $age years old."
println(message1) // 输出: My name is Bob and I am 25 years old.
// 引用表达式
val message2 = "Next year, I will be ${age + 1}."
println(message2) // 输出: Next year, I will be 26.
// 引用方法调用
val text = "Kotlin"
val message3 = "The length of \"$text\" is ${text.length}."
println(message3) // 输出: The length of "Kotlin" is 6.
}
“`
使用美元符号 $
后跟变量名或表达式(用花括号 {}
括起来)。
3.4 注释 (Comments)
注释用于解释代码,不会被编译器执行。Kotlin 支持单行注释和多行注释。
“`kotlin
fun main() {
// 这是一个单行注释
/*
这是一个多行注释
它可以跨越多行
*/
val data = "Some data" // 也可以在行末添加注释
}
“`
第四章:控制流 (Control Flow)
控制流语句用于根据条件执行不同的代码块或重复执行代码。
4.1 If 表达式 (If Expression)
在 Kotlin 中,if
不仅是语句,还可以作为表达式使用,这意味着它可以返回一个值。
“`kotlin
fun main() {
val a = 10
val b = 20
// 作为语句使用
if (a > b) {
println("a is greater than b")
} else {
println("a is not greater than b")
}
// 作为表达式使用
val max = if (a > b) {
println("Choose a")
a // if 块的最后一个表达式作为返回值
} else {
println("Choose b")
b // else 块的最后一个表达式作为返回值
}
println("Max is $max") // 输出: Choose b, Max is 20
// 简洁的 if 表达式
val min = if (a < b) a else b
println("Min is $min") // 输出: Min is 10
}
“`
4.2 When 表达式 (When Expression)
when
是 Kotlin 中更强大、更灵活的 switch
语句替代品,它也可以作为表达式使用。
“`kotlin
fun main() {
val day = 3
// 基本用法
when (day) {
1 -> println("Monday")
2 -> println("Tuesday")
3 -> println("Wednesday")
4 -> println("Thursday")
5 -> println("Friday")
6, 7 -> println("Weekend") // 可以匹配多个值
else -> println("Invalid day") // 其他所有情况
}
// 输出: Wednesday
// 作为表达式使用
val dayName = when (day) {
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
in 6..7 -> "Weekend" // 使用 range 匹配
else -> "Invalid day"
}
println("Day name is $dayName") // 输出: Day name is Wednesday
// when 还可以用于检查类型
val x: Any = "Hello"
when (x) {
is String -> println("x is a String of length ${x.length}") // 智能转换
is Int -> println("x is an Int")
else -> println("x is something else")
}
// 输出: x is a String of length 5
// when 也可以没有参数,用于更复杂的条件判断
val num = 75
when {
num < 0 -> println("Negative")
num % 2 == 0 -> println("Even")
else -> println("Odd")
}
// 输出: Odd
}
“`
when
表达式要求是穷尽的 (exhaustive),也就是说必须覆盖所有可能的情况。如果所有情况都已覆盖,则不需要 else
分支。例如,如果 when
用于检查一个枚举 (Enum) 的所有可能值,则不需要 else
。
4.3 循环 (Loops)
Kotlin 提供了 for
、while
和 do-while
循环。
-
For 循环:
for
循环可以迭代任何提供了迭代器 (iterator) 的对象,例如集合、数组、区间等。“`kotlin
fun main() {
// 遍历一个区间 (包括结束值)
for (i in 1..5) {
print(“$i “) // 输出: 1 2 3 4 5
}
println()// 遍历一个不包括结束值的区间 (推荐,因为集合索引通常是 0 到 size-1) for (i in 1 until 5) { print("$i ") // 输出: 1 2 3 4 } println() // 倒序遍历 for (i in 5 downTo 1) { print("$i ") // 输出: 5 4 3 2 1 } println() // 指定步长 for (i in 1..10 step 2) { print("$i ") // 输出: 1 3 5 7 9 } println() // 遍历集合 val fruits = listOf("apple", "banana", "cherry") for (fruit in fruits) { println(fruit) } // 输出: apple, banana, cherry (每行一个) // 遍历带索引的集合 for ((index, fruit) in fruits.withIndex()) { println("Item at index $index is $fruit") } // 输出: Item at index 0 is apple, Item at index 1 is banana, Item at index 2 is cherry
}
“` -
While 循环:
while
循环在条件为真时重复执行代码块。kotlin
fun main() {
var i = 0
while (i < 5) {
print("$i ") // 输出: 0 1 2 3 4
i++
}
println()
} -
Do-While 循环:
do-while
循环先执行一次代码块,然后在条件为真时重复执行。kotlin
fun main() {
var i = 0
do {
print("$i ") // 输出: 0 1 2 3 4
i++
} while (i < 5)
println()
}
4.4 Break 和 Continue (带标签)
break
用于终止最内层的循环,continue
跳过当前迭代的剩余代码,继续下一次迭代。Kotlin 允许使用标签 @labelName
来指定 break
或 continue
跳出或继续哪个循环。
kotlin
fun main() {
loop@ for (i in 1..5) {
for (j in 1..5) {
if (i == 3 && j == 3) {
break@loop // 跳出带 loop 标签的外层循环
}
print("($i, $j) ")
}
println()
}
// 输出: (1, 1) (1, 2) (1, 3) (1, 4) (1, 5)
// (2, 1) (2, 2) (2, 3) (2, 4) (2, 5)
// (3, 1) (3, 2)
}
第五章:函数 (Functions)
函数是组织代码的基本单元。在 Kotlin 中,函数用 fun
关键字声明。
5.1 函数声明与调用
“`kotlin
fun main() {
greet(“Alice”) // 调用函数 greet
val result = add(5, 3) // 调用函数 add,并将返回值赋给 result
println("5 + 3 = $result") // 输出: 5 + 3 = 8
printMessage("Hello") // 调用函数 printMessage (没有参数使用默认值)
printMessage("Goodbye", "World") // 调用函数 printMessage (使用指定参数)
}
// 函数声明
fun greet(name: String): Unit { // name 是参数名,String 是参数类型。Unit 表示没有返回值(类似 Java 的 void),通常可以省略。
println(“Hello, $name!”)
}
// 带返回值的函数
fun add(a: Int, b: Int): Int { // 指定返回类型为 Int
return a + b // 使用 return 关键字返回值
}
// 函数参数可以有默认值
fun printMessage(message: String, prefix: String = “Info”) {
println(“[$prefix] $message”)
}
“`
5.2 单表达式函数 (Single-Expression Functions)
如果函数体只包含一个表达式,你可以使用更简洁的语法,省略花括号和 return
关键字,使用 =
连接函数声明和表达式。返回类型通常可以省略(由编译器推断)。
“`kotlin
// 等同于上面的 add 函数
fun add(a: Int, b: Int): Int = a + b
// 返回类型也可以省略
fun multiply(a: Int, b: Int) = a * b // 编译器推断返回类型为 Int
fun main() {
println(add(2, 3)) // 输出: 5
println(multiply(4, 5)) // 输出: 20
}
“`
5.3 命名参数 (Named Arguments)
调用函数时,你可以使用参数名来指定参数值,这使得函数调用更易读,并且在参数多或有默认值时非常有用。
kotlin
fun main() {
// 使用命名参数调用 printMessage
printMessage(message = "Something happened", prefix = "DEBUG") // 参数顺序可以改变
printMessage(message = "Just a message") // prefix 使用默认值
}
5.4 可变数量参数 (Variable Number of Arguments)
使用 vararg
关键字可以声明一个接受可变数量参数的函数。这些参数在函数体内会被视为一个数组。
“`kotlin
fun main() {
printNumbers(1, 2, 3, 4, 5)
printNumbers(10, 20)
val numbers = intArrayOf(100, 200, 300)
// 要传递一个数组作为 vararg 参数,需要使用 spread 运算符 (*)
printNumbers(*numbers)
}
fun printNumbers(vararg numbers: Int) {
println(“Printing numbers:”)
for (number in numbers) {
print(“$number “)
}
println()
}
“`
第六章:类与对象 (Classes and Objects)
Kotlin 是面向对象的语言,支持类、对象、继承、接口等概念。
6.1 类声明
使用 class
关键字声明类。
“`kotlin
// 最简单的类声明
class EmptyClass
// 带属性的类
class Person {
// 属性声明,必须初始化或者在构造函数中初始化
var name: String = “”
var age: Int = 0
}
fun main() {
val person1 = Person() // 创建对象实例
person1.name = “Alice” // 设置属性值
person1.age = 30
println(“Name: ${person1.name}, Age: ${person1.age}”) // 输出: Name: Alice, Age: 30
val person2 = Person()
println("Name: ${person2.name}, Age: ${person2.age}") // 输出: Name: , Age: 0 (使用默认初始化值)
}
“`
6.2 构造函数 (Constructors)
Kotlin 有主构造函数 (Primary Constructor) 和次构造函数 (Secondary Constructor)。
-
主构造函数:
直接在类头中声明。它不能包含任何代码,初始化代码可以放在init
块中。“`kotlin
class Person(val name: String, var age: Int) { // 主构造函数,val/var 表示同时声明属性
init {
// init 块会在主构造函数执行后执行,用于初始化
println(“Person object created with name $name and age $age”)
}// 方法 fun sayHello() { println("Hello, my name is $name.") }
}
fun main() {
val person = Person(“Bob”, 25) // 调用主构造函数创建对象
person.sayHello() // 输出: Hello, my name is Bob.
println(person.name) // 输出: Bob (val 属性可以直接访问)
person.age = 26 // var 属性可以修改
println(person.age) // 输出: 26
}
``
val
如果在主构造函数参数前加上或
var,会自动生成对应的类属性。如果省略,则只是构造函数参数,需要在
init` 块或属性声明中手动赋值给类属性。 -
次构造函数:
使用constructor
关键字声明。如果类有主构造函数,次构造函数必须直接或间接调用主构造函数 (使用this
关键字)。“`kotlin
class Person(val name: String, var age: Int) { // 主构造函数
constructor(name: String) : this(name, 0) { // 次构造函数,调用主构造函数,并设置 age 为 0
println(“Secondary constructor called with name $name”)
}init { println("Init block called") } fun sayHello() { println("Hello, my name is $name.") }
}
fun main() {
val person1 = Person(“Charlie”, 35) // 调用主构造函数
// 输出: Init block called, Person object created with name Charlie and age 35val person2 = Person("David") // 调用次构造函数 // 输出: Secondary constructor called with name David // Init block called // Person object created with name David and age 0
}
“`
6.3 继承 (Inheritance)
Kotlin 中的类默认是 final
的,不能被继承。要允许继承,需要在类前使用 open
关键字。继承使用 :
语法。需要重写的方法或属性也需要 open
并在子类中使用 override
。
“`kotlin
open class Animal(val name: String) { // 父类需要 open
open fun makeSound() { // 需要 open 才能被子类重写
println(“$name makes a sound”)
}
}
class Dog(name: String) : Animal(name) { // Dog 继承自 Animal,调用父类构造函数
override fun makeSound() { // 重写父类方法需要 override
println(“$name barks”)
}
fun fetch() {
println("$name fetches the ball")
}
}
class Cat(name: String) : Animal(name) {
// Cat 没有重写 makeSound(), 使用父类的实现
fun scratch() {
println("$name scratches")
}
}
fun main() {
val dog = Dog(“Buddy”)
dog.makeSound() // 输出: Buddy barks
dog.fetch() // 输出: Buddy fetches the ball
val cat = Cat("Whiskers")
cat.makeSound() // 输出: Whiskers makes a sound (父类实现)
cat.scratch() // 输出: Whiskers scratches
}
“`
6.4 接口 (Interfaces)
接口使用 interface
关键字声明,可以包含抽象方法和有默认实现的非抽象方法。类实现接口使用 :
语法,并重写接口中的抽象成员。
“`kotlin
interface Drivable {
fun drive() // 抽象方法 (默认是 public abstract)
fun stop() { // 带默认实现的非抽象方法
println("Stopping")
}
}
class Car : Drivable {
override fun drive() { // 实现接口中的抽象方法
println(“Car is driving”)
}
// stop 方法使用默认实现
}
class Bicycle : Drivable {
override fun drive() {
println(“Bicycle is pedaling”)
}
override fun stop() { // 也可以重写默认实现
println("Bicycle is slowing down")
}
}
fun main() {
val myCar = Car()
myCar.drive() // 输出: Car is driving
myCar.stop() // 输出: Stopping
val myBicycle = Bicycle()
myBicycle.drive() // 输出: Bicycle is pedaling
myBicycle.stop() // 输出: Bicycle is slowing down
}
“`
6.5 可见性修饰符 (Visibility Modifiers)
Kotlin 的可见性修饰符与 Java 略有不同:
public
: 任何地方都可见 (默认)。private
: 只在声明它的文件 (顶层声明) 或类内部可见。protected
: 只在声明它的类及其子类中可见 (对顶层声明无效)。internal
: 只在同一模块 (module) 内可见。模块是一组 Kotlin 文件,编译在一起。这对于构建内部库非常有用。
“`kotlin
// example.kt
private fun topLevelPrivateFunction() {
println(“This is a private top-level function”)
}
internal class InternalClass { // 仅在同一模块内可见
internal val property = 1 // 仅在同一模块内可见
internal fun internalMethod() { // 仅在同一模块内可见
println("Internal method called")
}
}
open class BaseClass {
protected val protectedProperty = 10 // 在 BaseClass 及其子类中可见
}
class DerivedClass : BaseClass() {
fun accessProtected() {
println(“Accessing protected property: $protectedProperty”) // 在子类中可以访问
}
}
“`
6.6 数据类 (Data Classes)
data class
是 Kotlin 提供的一种非常方便的类,专门用于存储数据。编译器会自动为 data class
生成一些标准函数:
equals()
: 用于比较两个对象的内容是否相等。hashCode()
: 用于在散列集合 (如HashMap
,HashSet
) 中使用。toString()
: 生成一个包含类名和属性值的字符串表示。copy()
: 用于复制对象,可以选择修改部分属性。componentN()
: 用于解构声明。
数据类必须至少有一个参数的主构造函数。
“`kotlin
data class User(val name: String, val age: Int)
fun main() {
val user1 = User(“Alice”, 30)
val user2 = User(“Alice”, 30)
val user3 = User(“Bob”, 25)
// toString()
println(user1) // 输出: User(name=Alice, age=30)
// equals() 和 hashCode()
println(user1 == user2) // 输出: true (内容相等)
println(user1.equals(user3)) // 输出: false (内容不同)
println(user1.hashCode())
println(user2.hashCode())
println(user3.hashCode()) // user1 和 user2 的 hashCode 相同,user3 不同
// copy()
val user4 = user1.copy() // 复制 user1
val user5 = user1.copy(age = 31) // 复制 user1,并修改 age 属性
println(user4) // 输出: User(name=Alice, age=30)
println(user5) // 输出: User(name=Alice, age=31)
// 解构声明 (Destructuring Declaration)
val (name, age) = user1 // 将 user1 的属性解构到单独的变量中
println("Name: $name, Age: $age") // 输出: Name: Alice, Age: 30
}
“`
data class
极大地减少了编写这些样板代码的工作量,是 Kotlin 中非常实用的特性。
第七章:Null 安全 (Null Safety)
Kotlin 在类型系统中强制执行 null 安全,这是其最突出的特性之一,旨在消除 NullPointerException。
7.1 非空类型 (Non-nullable Types)
在 Kotlin 中,除非你明确允许,否则变量不能持有 null
值。
kotlin
// var name: String = null // 编译错误:Type mismatch: inferred type is Nothing? but String was expected
var name: String = "Alice" // 必须赋一个非 null 的值
// name = null // 编译错误:Null can not be a value of a non-null type String
7.2 可空类型 (Nullable Types)
要允许变量持有 null
值,需要在类型后面加上问号 ?
。
kotlin
var name: String? = "Bob" // 可以是 String 也可以是 null
name = null // 合法
现在问题来了,如何安全地访问一个可空类型的变量呢?Kotlin 提供了几种方式。
7.3 安全调用运算符 (Safe Call Operator) ?.
这是访问可空变量属性或方法的最安全的方式。如果对象不为 null
,则执行调用;如果为 null
,则整个表达式返回 null
。
“`kotlin
fun main() {
var name: String? = “Kotlin”
println(name?.length) // 如果 name 不为 null,则返回 name.length;否则返回 null。输出: 6
name = null
println(name?.length) // name 是 null,所以返回 null。输出: null
}
“`
这相当于一个内联的 if (name != null) name.length else null
检查。
7.4 Elvis 运算符 ?:
当一个可空表达式为 null
时,你想要提供一个默认值,这时可以使用 Elvis 运算符。
“`kotlin
fun main() {
val name: String? = null
val length: Int = name?.length ?: 0 // 如果 name?.length 返回 null,则使用 0
println(length) // 输出: 0
val name2: String? = "Kotlin"
val length2: Int = name2?.length ?: 0 // name2?.length 返回 6,所以使用 6
println(length2) // 输出: 6
}
“`
Elvis 运算符的左侧是一个可空表达式,右侧是当左侧表达式为 null
时返回的默认值。
7.5 非空断言运算符 (Non-null Assertion Operator) !!
如果你确定一个可空变量不会为 null
,但编译器无法证明这一点,你可以使用 !!
运算符。它会将可空类型转换为非空类型。使用 !!
是危险的,如果该变量实际上是 null
,它将抛出 NullPointerException
。
“`kotlin
fun main() {
var name: String? = “Kotlin”
println(name!!.length) // 我确定 name 不会为 null,所以使用 !!。输出: 6
name = null
// println(name!!.length) // 运行时会抛出 NullPointerException
}
“`
应该尽量避免使用 !!
,因为它破坏了 Kotlin 的 null 安全保证。只在你绝对确定变量不会为 null
,并且没有更好的 null 安全处理方式时才使用。
7.6 安全转换运算符 (Safe Cast Operator) as?
常规的类型转换 (as
) 如果转换失败会抛出 ClassCastException
。安全转换运算符 as?
在转换失败时返回 null
。
“`kotlin
fun main() {
val value: Any = “Hello”
val stringValue: String? = value as? String // 尝试转换为 String,成功则返回 String,失败则返回 null
println(stringValue?.length) // 输出: 5
val value2: Any = 123
val stringValue2: String? = value2 as? String // 尝试转换为 String,失败
println(stringValue2?.length) // 返回 null,所以 ?.length 返回 null。输出: null
}
“`
Kotlin 的 null 安全特性是其核心优势之一,理解并正确使用可空类型及其相关的运算符是编写健壮 Kotlin 代码的关键。
第八章:集合 (Collections)
Kotlin 标准库提供了丰富的集合类,包括列表 (List)、集合 (Set) 和映射 (Map)。Kotlin 明确区分了可变集合和不可变集合。
8.1 不可变集合 (Immutable Collections)
不可变集合是创建后不能修改的集合,例如不能添加、删除或更新元素。
-
List (只读列表):
listOf()
kotlin
val colors = listOf("Red", "Green", "Blue")
println(colors) // 输出: [Red, Green, Blue]
println(colors[0]) // 通过索引访问: Red
// colors.add("Yellow") // 编译错误:不可变列表不能添加元素 -
Set (只读集合):
setOf()
(元素唯一,无序)
kotlin
val uniqueNumbers = setOf(1, 2, 2, 3, 1)
println(uniqueNumbers) // 输出: [1, 2, 3] (重复元素被去除,顺序可能与添加顺序不同)
println(uniqueNumbers.contains(2)) // 检查元素是否存在: true -
Map (只读映射):
mapOf()
(存储键值对)
kotlin
val ages = mapOf("Alice" to 30, "Bob" to 25, "Charlie" to 35) // 使用 infix 函数 to 创建键值对
println(ages) // 输出: {Alice=30, Bob=25, Charlie=35}
println(ages["Alice"]) // 通过键访问值: 30
println(ages.get("David")) // 访问不存在的键返回 null: null
println(ages.getOrDefault("David", 0)) // 访问不存在的键返回默认值: 0
8.2 可变集合 (Mutable Collections)
可变集合允许在创建后修改元素。
-
MutableList (可变列表):
mutableListOf()
kotlin
val mutableColors = mutableListOf("Red", "Green", "Blue")
mutableColors.add("Yellow") // 添加元素
mutableColors.removeAt(0) // 删除元素
mutableColors[0] = "Cyan" // 修改元素
println(mutableColors) // 输出: [Cyan, Blue, Yellow] -
MutableSet (可变集合):
mutableSetOf()
kotlin
val mutableNumbers = mutableSetOf(1, 2, 3)
mutableNumbers.add(4)
mutableNumbers.remove(2)
println(mutableNumbers) // 输出: [1, 3, 4] (顺序不确定) -
MutableMap (可变映射):
mutableMapOf()
kotlin
val mutableAges = mutableMapOf("Alice" to 30, "Bob" to 25)
mutableAges["Charlie"] = 35 // 添加或更新键值对
mutableAges.remove("Bob") // 删除键值对
println(mutableAges) // 输出: {Alice=30, Charlie=35}
8.3 集合的常用操作
Kotlin 标准库提供了丰富的集合扩展函数,使得对集合的操作非常便捷。一些常见的操作包括:
forEach
: 遍历集合元素。map
: 将集合中的每个元素转换成另一个值,生成新的列表。filter
: 过滤集合中的元素,只保留满足条件的元素。first
,last
: 获取第一个/最后一个元素。find
: 查找第一个满足条件的元素。count
: 统计元素数量或满足条件的元素数量。sortedBy
: 根据某个属性排序。groupBy
: 根据某个属性分组。
“`kotlin
fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6)
// 过滤正数
val positives = numbers.filter { it > 0 } // it 表示当前元素
println(positives) // 输出: [1, 3, 5]
// 每个元素乘以 2
val doubled = numbers.map { it * 2 }
println(doubled) // 输出: [2, -4, 6, -8, 10, -12]
// 查找第一个负数
val firstNegative = numbers.find { it < 0 }
println(firstNegative) // 输出: -2
// 按绝对值排序
val sortedByAbs = numbers.sortedBy { kotlin.math.abs(it) }
println(sortedByAbs) // 输出: [1, -2, 3, -4, 5, -6]
}
“`
这些函数通常采用 Lambda 表达式作为参数,我们将在后续的教程中深入探讨 Lambda。
第九章:更多实用特性 (略览)
Kotlin 还有许多其他强大的特性,虽然本入门教程不会深入讲解,但值得在此提及,让你知道它们的存在。
- 扩展函数 (Extension Functions): 允许你为一个已有的类添加新的函数,而无需修改其源代码或使用继承。
- Lambda 表达式与高阶函数 (Lambdas and Higher-Order Functions): Lambda 表达式是匿名函数,高阶函数是可以接受函数作为参数或返回函数的函数。它们是 Kotlin 中函数式编程风格的基础。
- 协程 (Coroutines): 一种用于简化异步编程、并发编程的轻量级线程框架,特别适用于非阻塞操作。
- 密封类 (Sealed Classes): 用于表示受限的类继承结构,当你知道所有可能的子类时非常有用,常与
when
表达式结合使用以确保穷尽性。 - 委托 (Delegation): Kotlin 原生支持委托模式,可以方便地实现属性委托和类委托,减少样板代码。
这些特性是 Kotlin 强大和灵活的关键,在你掌握了基础知识后,可以逐步深入学习它们。
第十章:总结与下一步
恭喜你完成了 Kotlin 的入门学习!本教程覆盖了 Kotlin 的核心概念:
- Kotlin 的优势和应用领域。
- 环境搭建和运行第一个程序。
- 变量 (val/var)、基本数据类型、字符串模板。
- 控制流 (
if
,when
,for
,while
,do-while
)。 - 函数 (声明、参数、返回值、单表达式函数、命名参数、
vararg
)。 - 类与对象 (声明、属性、构造函数、继承、接口、可见性修饰符、数据类)。
- Null 安全 (
?
,?.
,?:
,!!
,as?
)。 - 集合 (不可变与可变集合、常用操作)。
接下来你可以:
- 练习: 动手编写更多 Kotlin 代码,尝试解决一些小问题,巩固学到的知识。
- 深入学习: 选择一个感兴趣的方向深入,例如:
- Android 开发: 使用 Kotlin 构建 Android 应用。
- 后端开发: 学习 Ktor 或 Spring Boot 等框架。
- 多平台开发: 探索 Kotlin Multiplatform Mobile (KMM) 或 Kotlin/JS、Kotlin/Native。
- 特定主题: 深入研究协程、Flow、高级集合操作、DSL 等。
- 阅读文档和社区资源: Kotlin 官方文档非常出色,Stack Overflow 和各种技术社区也有大量的 Kotlin 讨论和示例。
- 参与项目: 加入一个开源项目或与他人协作,通过实践学习是最有效的方式。
Kotlin 是一门充满活力和潜力的语言,掌握它将为你打开新的编程视野。祝你在 Kotlin 的学习旅程中取得成功!