Kotlin 入门教程 – wiki基地


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 项目

  1. 打开 IntelliJ IDEA。
  2. 点击 “Create New Project” 或 “File” -> “New” -> “Project…”.
  3. 在项目类型列表中,选择 “Kotlin”。
  4. 选择项目模板,通常选择 “JVM | IDEA”。点击 “Next”。
  5. 填写项目名称、项目位置。确保 “JDK” 已经配置正确(需要 Java 8 或更高版本)。点击 “Finish”。
  6. 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 中声明变量有两种方式:valvar

  • 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: truefalse
  • 字符类型:
    • Char: 单个字符,用单引号 'A' 表示。
  • 字符串类型:
    • String: 字符序列,用双引号 "Hello" 表示。Kotlin 的字符串是不可变的。
  • 数组类型:
    • Array: 数组类,如 Array<Int>。Kotlin 还有一些专门的数组类,如 IntArrayCharArray 等,它们没有装箱开销,更接近 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 提供了 forwhiledo-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 来指定 breakcontinue 跳出或继续哪个循环。

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
    }
    ``
    如果在主构造函数参数前加上
    valvar,会自动生成对应的类属性。如果省略,则只是构造函数参数,需要在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 35

    val 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?)。
  • 集合 (不可变与可变集合、常用操作)。

接下来你可以:

  1. 练习: 动手编写更多 Kotlin 代码,尝试解决一些小问题,巩固学到的知识。
  2. 深入学习: 选择一个感兴趣的方向深入,例如:
    • Android 开发: 使用 Kotlin 构建 Android 应用。
    • 后端开发: 学习 Ktor 或 Spring Boot 等框架。
    • 多平台开发: 探索 Kotlin Multiplatform Mobile (KMM) 或 Kotlin/JS、Kotlin/Native。
    • 特定主题: 深入研究协程、Flow、高级集合操作、DSL 等。
  3. 阅读文档和社区资源: Kotlin 官方文档非常出色,Stack Overflow 和各种技术社区也有大量的 Kotlin 讨论和示例。
  4. 参与项目: 加入一个开源项目或与他人协作,通过实践学习是最有效的方式。

Kotlin 是一门充满活力和潜力的语言,掌握它将为你打开新的编程视野。祝你在 Kotlin 的学习旅程中取得成功!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部