kotlin面试题-第二部

4 阅读18分钟

第三章:Kotlin 类和对象面试题答案


1. 类基础

3.1 Kotlin的类声明方式是什么?

答案:

Kotlin的类声明方式:

1. 基本语法

class ClassName {
    // 类体
}

2. 空类

// 空类
class EmptyClass

// 或
class EmptyClass {
}

3. 带属性的类

class Person {
    var name: String = ""
    var age: Int = 0
}

4. 带方法的类

class Calculator {
    fun add(a: Int, b: Int): Int {
        return a + b
    }
    
    fun multiply(a: Int, b: Int): Int {
        return a * b
    }
}

5. 主构造函数

// 主构造函数(简洁形式)
class Person(val name: String, val age: Int)

// 等价于
class Person(name: String, age: Int) {
    val name: String = name
    val age: Int = age
}

6. 主构造函数带初始化

class Person(val name: String, val age: Int) {
    init {
        require(age >= 0) { "Age cannot be negative" }
        println("Person created: $name")
    }
}

7. 次构造函数

class Person(val name: String, val age: Int) {
    // 次构造函数
    constructor(name: String) : this(name, 0)
    
    constructor() : this("Unknown", 0)
}

8. 与Java对比

KotlinJava等价
class Personpublic class Person { }
class Person(val name: String)public class Person { private String name; public Person(String name) { this.name = name; } }
空类public class EmptyClass { }

9. 实际应用

简单类

class User {
    var name: String = ""
    var email: String = ""
}

带构造函数

class User(val name: String, val email: String) {
    init {
        require(email.contains("@")) { "Invalid email" }
    }
}

10. 最佳实践

  1. 使用主构造函数:优先使用主构造函数
  2. 简洁声明:利用Kotlin的简洁语法
  3. 初始化逻辑:使用init块处理初始化
  4. 属性声明:在主构造函数中声明属性

3.2 Kotlin的构造函数有哪些类型?

答案:

Kotlin有两种构造函数:主构造函数和次构造函数。

1. 主构造函数(Primary Constructor)

主构造函数是类头的一部分:

// 主构造函数
class Person(val name: String, val age: Int)

// 等价于
class Person(name: String, age: Int) {
    val name: String = name
    val age: Int = age
}

2. 主构造函数特性

  • 在类名后声明
  • 可以声明属性(val/var
  • 可以有默认参数
  • 可以有可见性修饰符
// 带默认参数
class Person(val name: String, val age: Int = 0)

// 私有主构造函数
class Person private constructor(val name: String)

3. 次构造函数(Secondary Constructor)

次构造函数在类体内声明:

class Person(val name: String, val age: Int) {
    // 次构造函数
    constructor(name: String) : this(name, 0)
    
    constructor() : this("Unknown", 0)
}

4. 次构造函数规则

  • 必须调用主构造函数(如果存在)
  • 或调用另一个次构造函数
  • 使用constructor关键字
class Person(val name: String, val age: Int) {
    constructor(name: String) : this(name, 0)  // 调用主构造函数
    
    constructor() : this("Unknown")  // 调用次构造函数
}

5. 主构造函数 vs 次构造函数

特性主构造函数次构造函数
位置类头类体
声明类名后constructor关键字
属性声明可以(val/var不能
默认参数支持不支持
调用自动调用需要显式调用
数量最多1个可以有多个

6. 实际应用

主构造函数(推荐)

class User(val name: String, val email: String, val age: Int = 0) {
    init {
        require(age >= 0) { "Age cannot be negative" }
    }
}

次构造函数(特殊情况)

class User(val name: String, val email: String) {
    constructor(name: String) : this(name, "$name@example.com")
    constructor() : this("Unknown")
}

7. 最佳实践

  1. 优先主构造函数:大多数情况使用主构造函数
  2. 使用默认参数:替代多个次构造函数
  3. 次构造函数用于特殊情况:需要特殊初始化逻辑时使用

3.3 主构造函数和次构造函数的区别是什么?

答案:

主构造函数和次构造函数的主要区别:

1. 声明位置

主构造函数

// 在类头声明
class Person(val name: String, val age: Int)

次构造函数

// 在类体声明
class Person(val name: String, val age: Int) {
    constructor(name: String) : this(name, 0)
}

2. 属性声明

主构造函数

// 可以直接声明属性
class Person(val name: String, val age: Int)

次构造函数

// 不能声明属性,只能调用主构造函数
class Person(val name: String, val age: Int) {
    constructor(name: String) : this(name, 0)  // 不能声明属性
}

3. 调用方式

主构造函数

// 自动调用
val person = Person("Kotlin", 30)

次构造函数

// 需要显式调用
val person = Person("Kotlin")  // 调用次构造函数

4. 默认参数

主构造函数

// 支持默认参数
class Person(val name: String, val age: Int = 0)

次构造函数

// 不支持默认参数
class Person(val name: String, val age: Int) {
    constructor(name: String) : this(name, 0)  // 需要手动处理
}

5. 数量限制

  • 主构造函数:最多1个
  • 次构造函数:可以有多个

6. 初始化顺序

class Person(val name: String, val age: Int) {
    init {
        println("Init block 1")
    }
    
    constructor(name: String) : this(name, 0) {
        println("Secondary constructor")
    }
    
    init {
        println("Init block 2")
    }
}

// 调用次构造函数时:
// 1. 先调用主构造函数
// 2. 执行所有init块
// 3. 执行次构造函数体

7. 实际应用

主构造函数(推荐)

class User(val name: String, val email: String, val age: Int = 0)

次构造函数(特殊情况)

class User(val name: String, val email: String) {
    // 从JSON创建
    constructor(json: JsonObject) : this(
        json.getString("name"),
        json.getString("email")
    )
}

8. 最佳实践

  1. 优先主构造函数:使用主构造函数和默认参数
  2. 次构造函数用于特殊情况:需要特殊初始化时使用
  3. 避免过度使用次构造函数:可以用默认参数替代

3.4 Kotlin的init代码块是什么?

答案:

init代码块用于在对象创建时执行初始化逻辑。

1. 基本语法

class Person(val name: String, val age: Int) {
    init {
        // 初始化代码
        require(age >= 0) { "Age cannot be negative" }
        println("Person created: $name")
    }
}

2. 多个init块

可以有多个init块,按顺序执行:

class Person(val name: String, val age: Int) {
    init {
        println("Init block 1")
    }
    
    val description = "Person: $name"
    
    init {
        println("Init block 2")
        println(description)
    }
}

3. 执行顺序

class Person(val name: String, val age: Int) {
    // 1. 主构造函数参数初始化
    // 2. 属性初始化
    val firstName = name.split(" ")[0]
    
    // 3. init块执行
    init {
        println("Name: $name")
    }
    
    // 4. 次构造函数体执行(如果使用次构造函数)
}

4. 实际应用

参数验证

class User(val email: String, val age: Int) {
    init {
        require(email.contains("@")) { "Invalid email" }
        require(age >= 0) { "Age cannot be negative" }
    }
}

初始化计算

class Rectangle(val width: Int, val height: Int) {
    val area: Int
    
    init {
        area = width * height
        println("Rectangle created: ${width}x${height}, area=$area")
    }
}

5. 与Java对比

Kotlin

class Person(val name: String) {
    init {
        println("Created: $name")
    }
}

Java

public class Person {
    private String name;
    
    public Person(String name) {
        this.name = name;
        System.out.println("Created: " + name);
    }
}

6. 最佳实践

  1. 参数验证:在init块中验证参数
  2. 初始化逻辑:执行初始化计算
  3. 日志记录:记录对象创建
  4. 避免复杂逻辑:保持init块简洁

3.5 Kotlin的类属性有哪些特性?

答案:

Kotlin类属性的特性:

1. 属性声明

class Person {
    // 可变属性
    var name: String = ""
    
    // 不可变属性
    val age: Int = 0
    
    // 延迟初始化
    lateinit var email: String
    
    // 懒加载
    val description: String by lazy {
        "Person description"
    }
}

2. 自动getter/setter

Kotlin自动生成getter和setter:

class Person {
    var name: String = ""
    // 自动生成:
    // fun getName(): String = name
    // fun setName(value: String) { name = value }
}

3. 自定义getter/setter

class Person {
    var name: String = ""
        get() = field.uppercase()  // 自定义getter
        set(value) {
            field = value.trim()  // 自定义setter
        }
}

4. 只读属性

class Person(val name: String) {
    // val属性只有getter,没有setter
    val age: Int = 0
}

5. 计算属性

class Rectangle(val width: Int, val height: Int) {
    val area: Int
        get() = width * height  // 计算属性
}

6. 延迟初始化

class Person {
    lateinit var name: String  // 延迟初始化(仅var)
    
    fun init() {
        name = "Kotlin"
    }
}

7. 懒加载

class Person {
    val expensiveValue: String by lazy {
        // 第一次访问时计算
        calculateExpensiveValue()
    }
}

8. 与Java对比

KotlinJava等价
var name: Stringprivate String name; + getter/setter
val age: Intprivate final int age; + getter
自定义getter/setter手动实现getter/setter

9. 最佳实践

  1. 优先使用val:除非需要修改
  2. 利用自动getter/setter:减少样板代码
  3. 自定义访问器:需要特殊逻辑时自定义
  4. 延迟初始化:使用lateinit或lazy

3.6 Kotlin的初始化顺序是什么?

答案:

Kotlin对象初始化的执行顺序:

1. 基本顺序

1. 主构造函数参数初始化
2. 类体中属性初始化(按声明顺序)
3. init块执行(按声明顺序)
4. 次构造函数体执行(如果使用次构造函数)

2. 详细示例

class Person(val name: String, val age: Int) {
    // 1. 主构造函数参数:name, age已初始化
    
    // 2. 属性初始化
    val firstName = name.split(" ")[0]  // 可以使用name
    val lastName = name.split(" ").getOrElse(1) { "" }
    
    // 3. init块执行
    init {
        println("Init 1: $name, $age")
    }
    
    val fullName = "$firstName $lastName"
    
    init {
        println("Init 2: $fullName")
    }
    
    // 4. 次构造函数(如果调用)
    constructor(name: String) : this(name, 0) {
        println("Secondary constructor")
    }
}

3. 执行流程

使用主构造函数

val person = Person("John Doe", 30)
// 执行顺序:
// 1. name = "John Doe", age = 30
// 2. firstName = "John"
// 3. lastName = "Doe"
// 4. Init 1: John Doe, 30
// 5. fullName = "John Doe"
// 6. Init 2: John Doe

使用次构造函数

val person = Person("John Doe")
// 执行顺序:
// 1. 调用主构造函数:name = "John Doe", age = 0
// 2. firstName = "John"
// 3. lastName = "Doe"
// 4. Init 1: John Doe, 0
// 5. fullName = "John Doe"
// 6. Init 2: John Doe
// 7. Secondary constructor

4. 属性依赖

class Person(val name: String) {
    // 可以依赖前面的属性
    val firstName = name.split(" ")[0]
    val lastName = name.split(" ").getOrElse(1) { "" }
    
    // 可以依赖前面的属性
    val fullName = "$firstName $lastName"
    
    // ❌ 不能依赖后面的属性
    // val test = fullName  // 如果fullName在后面定义,会编译错误
}

5. init块中的属性访问

class Person(val name: String) {
    val age = 25
    
    init {
        // ✅ 可以访问已初始化的属性
        println("Name: $name, Age: $age")
        
        // ❌ 不能访问未初始化的属性
        // println(email)  // 如果email在后面定义,会编译错误
    }
    
    val email = "$name@example.com"
}

6. 继承中的初始化顺序

open class Base(val name: String) {
    init {
        println("Base init: $name")
    }
}

class Derived(name: String) : Base(name) {
    init {
        println("Derived init: $name")
    }
}

// 执行顺序:
// 1. Derived主构造函数参数
// 2. Base主构造函数参数
// 3. Base属性初始化
// 4. Base init块
// 5. Derived属性初始化
// 6. Derived init块

7. 最佳实践

  1. 理解顺序:了解初始化顺序避免错误
  2. 属性依赖:注意属性之间的依赖关系
  3. init块位置:合理放置init块
  4. 避免循环依赖:避免属性之间的循环依赖

2. 可见性修饰符

3.7 Kotlin的可见性修饰符有哪些?

答案:

Kotlin的可见性修饰符:

1. 四种可见性

  • public:公共(默认)
  • private:私有
  • protected:受保护
  • internal:内部(模块内可见)

2. public(默认)

// public是默认的,可以不写
class Person {
    public val name: String = ""  // public可以省略
    val age: Int = 0  // 默认public
}

// 等价于
class Person {
    val name: String = ""
    val age: Int = 0
}

3. private

class Person {
    private val name: String = ""  // 只在类内可见
    private fun getName(): String = name
}

// 外部不能访问
// val person = Person()
// person.name  // ❌ 编译错误

4. protected

open class Person {
    protected val name: String = ""  // 子类可见
}

class User : Person() {
    fun display() {
        println(name)  // ✅ 子类可以访问
    }
}

5. internal

// 同一模块内可见
internal class Person {
    internal val name: String = ""
}

// 同一模块的其他文件可以访问
// 不同模块不能访问

6. 可见性范围

修饰符类内子类同一模块其他模块
public
private
protected
internal

7. 顶层声明

// 顶层函数和类
public fun publicFunction() { }  // 默认public
private fun privateFunction() { }  // 文件内可见
internal fun internalFunction() { }  // 模块内可见

8. 与Java对比

KotlinJava
publicpublic
privateprivate
protectedprotected
internalpackage-private(类似)
默认public(Kotlin)vs package-private(Java)

9. 最佳实践

  1. 最小可见性:使用最小必要的可见性
  2. internal用于模块:模块内部使用internal
  3. private保护实现:实现细节使用private
  4. protected用于继承:需要子类访问时使用protected

3.8 Kotlin和Java的可见性修饰符对比

答案:

Kotlin和Java可见性修饰符的对比:

1. 基本对比

KotlinJava说明
publicpublic公共访问
privateprivate私有访问
protectedprotected受保护(子类可见)
internalpackage-private模块/包内可见
默认package-private(Java)Kotlin默认public,Java默认package-private

2. 默认可见性

Kotlin

// 默认public
class Person {
    val name: String = ""  // public
}

Java

// 默认package-private
class Person {
    String name = "";  // package-private
}

3. internal vs package-private

Kotlin internal

// 模块内可见(编译单元)
internal class Person { }

Java package-private

// 包内可见
class Person { }  // package-private

4. protected差异

Kotlin

open class Person {
    protected val name: String = ""  // 子类可见,同包不可见
}

Java

public class Person {
    protected String name = "";  // 子类和同包可见
}

5. 实际应用

Kotlin

// 模块内部API
internal class InternalAPI { }

// 公共API
public class PublicAPI { }  // public可以省略

Java

// 包内部API
class PackagePrivateAPI { }

// 公共API
public class PublicAPI { }

6. 最佳实践

  1. 理解差异:了解Kotlin和Java的差异
  2. 合理使用:根据需求选择合适的可见性
  3. 模块设计:使用internal设计模块内部API

3.9 internal修饰符的作用是什么?

答案:

internal修饰符的作用:

1. 基本概念

internal表示"模块内可见",是Kotlin特有的可见性修饰符。

2. 模块定义

模块(Module)是:

  • 一起编译的一组Kotlin文件
  • 一个IntelliJ IDEA模块
  • 一个Maven/Gradle项目
  • 一个Ant任务编译的一组文件

3. 使用场景

// 模块内部API
internal class InternalService {
    internal fun process() { }
}

// 公共API
public class PublicService {
    public fun process() { }
}

4. 与Java package-private对比

特性Kotlin internalJava package-private
可见范围模块内包内
跨包同一模块内可以不同包不可以
跨模块不可以不可以

5. 实际应用

模块设计

// 模块A
internal class DatabaseHelper { }  // 模块A内部使用

public class UserService { }  // 模块A对外API

// 模块B
// 可以访问UserService
// 不能访问DatabaseHelper

6. 最佳实践

  1. 模块内部API:使用internal标记模块内部API
  2. 公共API:对外API使用public
  3. 封装实现:隐藏实现细节

3. 属性

3.10 Kotlin的属性(Property)是什么?

答案:

Kotlin的属性(Property)是类中声明的变量,自动提供getter和setter。

1. 基本概念

属性是Kotlin中替代Java字段+getter/setter的方式:

class Person {
    var name: String = ""  // 属性
    val age: Int = 0       // 只读属性
}

2. 自动getter/setter

Kotlin自动生成getter和setter:

class Person {
    var name: String = ""
    // 自动生成:
    // fun getName(): String = name
    // fun setName(value: String) { name = value }
}

// 使用
val person = Person()
person.name = "Kotlin"  // 调用setter
println(person.name)    // 调用getter

3. 自定义getter/setter

class Person {
    var name: String = ""
        get() = field.uppercase()
        set(value) {
            field = value.trim()
        }
}

4. 只读属性

class Person {
    val name: String = "Kotlin"  // 只有getter,没有setter
    // person.name = "Java"  // ❌ 编译错误
}

5. 计算属性

class Rectangle(val width: Int, val height: Int) {
    val area: Int
        get() = width * height  // 计算属性,不存储
}

6. 与Java对比

Kotlin

class Person {
    var name: String = ""
}

Java

public class Person {
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

7. 最佳实践

  1. 利用自动getter/setter:减少样板代码
  2. 自定义访问器:需要特殊逻辑时自定义
  3. 优先使用val:除非需要修改

3.11 Kotlin的getter和setter是什么?

答案:

Kotlin的getter和setter:

1. 自动生成

Kotlin自动为属性生成getter和setter:

class Person {
    var name: String = ""
    // 自动生成:
    // fun getName(): String = name
    // fun setName(value: String) { name = value }
}

2. 自定义getter

class Person {
    var name: String = ""
        get() = field.uppercase()  // 自定义getter
}

3. 自定义setter

class Person {
    var name: String = ""
        set(value) {
            field = value.trim()  // 自定义setter
        }
}

4. field关键字

field是属性的幕后字段(backing field):

class Person {
    var name: String = ""
        get() = field.uppercase()  // field指向实际存储
        set(value) {
            field = value.trim()
        }
}

5. 只读属性的getter

class Person {
    val name: String = "Kotlin"
    // 自动生成:
    // fun getName(): String = name
    // 没有setter
}

6. 计算属性

class Rectangle(val width: Int, val height: Int) {
    val area: Int
        get() = width * height  // 计算属性,没有field
}

7. 实际应用

验证setter

class Person {
    var age: Int = 0
        set(value) {
            require(value >= 0) { "Age cannot be negative" }
            field = value
        }
}

格式化getter

class Person {
    var name: String = ""
        get() = field.uppercase()
}

8. 最佳实践

  1. 利用自动生成:大多数情况不需要自定义
  2. 验证逻辑:在setter中添加验证
  3. 格式化逻辑:在getter中添加格式化
  4. 使用field:访问实际存储时使用field

3.12 Kotlin的延迟初始化(lateinit)是什么?

答案:

lateinit用于延迟初始化非空、非基本类型的var属性。

1. 基本概念

lateinit允许在声明时不初始化属性,但必须在首次使用前初始化:

class Person {
    lateinit var name: String  // 延迟初始化
    
    fun init() {
        name = "Kotlin"  // 必须在使用前初始化
    }
    
    fun display() {
        if (::name.isInitialized) {  // 检查是否初始化
            println(name)
        }
    }
}

2. 使用条件

lateinit只能用于:

  • var属性(不能用于val
  • 非空类型(不能用于可空类型)
  • 非基本类型(不能用于Int、Double等)
class Person {
    lateinit var name: String        // ✅ 可以
    // lateinit val name: String     // ❌ 不能用于val
    // lateinit var age: Int         // ❌ 不能用于基本类型
    // lateinit var email: String?   // ❌ 不能用于可空类型
}

3. 初始化检查

class Person {
    lateinit var name: String
    
    fun check() {
        if (::name.isInitialized) {
            println(name)
        } else {
            println("Not initialized")
        }
    }
}

4. 未初始化异常

如果访问未初始化的lateinit属性,会抛出UninitializedPropertyAccessException

class Person {
    lateinit var name: String
}

val person = Person()
println(person.name)  // 抛出UninitializedPropertyAccessException

5. 实际应用

Android开发

class MainActivity : AppCompatActivity() {
    lateinit var button: Button
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        button = findViewById(R.id.button)  // 在onCreate中初始化
    }
}

依赖注入

class UserService {
    lateinit var repository: UserRepository
    
    fun init(repository: UserRepository) {
        this.repository = repository
    }
}

6. 与可空类型对比

// lateinit方式
class Person {
    lateinit var name: String
    fun getName(): String = name  // 返回非空String
}

// 可空类型方式
class Person {
    var name: String? = null
    fun getName(): String? = name  // 返回可空String
}

7. 最佳实践

  1. 确定会初始化:确保在使用前初始化
  2. 检查初始化:使用::property.isInitialized检查
  3. 避免滥用:不要过度使用lateinit
  4. 替代方案:考虑使用可空类型或lazy

3.13 lateinit和lazy的区别是什么?

答案:

lateinitlazy的区别:

1. 基本区别

特性lateinitlazy
类型varval
初始化时机手动初始化首次访问时
线程安全不保证默认线程安全
可空性非空类型非空类型
基本类型不支持支持

2. lateinit

class Person {
    lateinit var name: String  // var,手动初始化
    
    fun init() {
        name = "Kotlin"  // 手动初始化
    }
}

3. lazy

class Person {
    val name: String by lazy {  // val,延迟初始化
        "Kotlin"  // 首次访问时计算
    }
}

4. 初始化时机

lateinit

class Person {
    lateinit var name: String
    
    fun init() {
        name = "Kotlin"  // 手动调用时初始化
    }
}

lazy

class Person {
    val name: String by lazy {
        println("Initializing name")
        "Kotlin"  // 首次访问时初始化
    }
}

val person = Person()
println(person.name)  // 此时才初始化

5. 线程安全

lateinit

// 不保证线程安全
lateinit var name: String

lazy

// 默认线程安全(LazyThreadSafetyMode.SYNCHRONIZED)
val name: String by lazy {
    "Kotlin"
}

// 非线程安全
val name: String by lazy(LazyThreadSafetyMode.NONE) {
    "Kotlin"
}

6. 使用场景

lateinit适用于:

  • 确定会初始化的var属性
  • 需要在外部初始化
  • Android的View、依赖注入等

lazy适用于:

  • 计算成本高的val属性
  • 需要延迟计算的属性
  • 单例模式

7. 实际应用

lateinit

class MainActivity : AppCompatActivity() {
    lateinit var button: Button
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        button = findViewById(R.id.button)
    }
}

lazy

class Database {
    val connection: Connection by lazy {
        // 昂贵的初始化操作
        createConnection()
    }
}

8. 最佳实践

  1. lateinit用于var:需要修改的属性
  2. lazy用于val:只读的延迟计算属性
  3. 明确初始化:lateinit确保在使用前初始化
  4. 性能考虑:lazy用于昂贵计算

3.14 Kotlin的委托属性(Delegated Properties)是什么?

答案:

委托属性(Delegated Properties)使用by关键字将属性的getter/setter委托给另一个对象。

1. 基本语法

class Person {
    var name: String by Delegate()
}

2. 标准委托

Kotlin提供标准委托:

lazy

val name: String by lazy {
    "Kotlin"
}

observable

var name: String by Delegates.observable("") { prop, old, new ->
    println("$old -> $new")
}

3. 自定义委托

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "Value"
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        // 设置值
    }
}

4. 实际应用

lazy委托

val expensiveValue: String by lazy {
    calculateExpensiveValue()
}

observable委托

var name: String by Delegates.observable("") { _, old, new ->
    println("Changed: $old -> $new")
}

5. 最佳实践

  1. 使用标准委托:优先使用lazy、observable等
  2. 自定义委托:需要特殊逻辑时自定义
  3. 理解原理:理解委托的实现机制

4. 继承

3.15 Kotlin的继承机制是什么?

答案:

Kotlin的继承机制:

1. 基本语法

// 基类必须标记为open
open class Animal {
    open fun makeSound() {
        println("Animal sound")
    }
}

// 继承
class Dog : Animal() {
    override fun makeSound() {
        println("Woof")
    }
}

2. 默认final

Kotlin的类默认是final,不能继承:

class Animal { }  // 默认final,不能继承

// class Dog : Animal()  // ❌ 编译错误

// 需要标记open
open class Animal { }  // 可以继承
class Dog : Animal()  // ✅

3. open关键字

open class Animal {
    open fun makeSound() { }  // 可以重写
    fun eat() { }             // 不能重写(默认final)
}

class Dog : Animal() {
    override fun makeSound() { }  // ✅ 可以重写
    // override fun eat() { }     // ❌ 编译错误
}

4. override关键字

open class Animal {
    open fun makeSound() { }
}

class Dog : Animal() {
    override fun makeSound() {  // 必须使用override
        println("Woof")
    }
}

5. 与Java对比

KotlinJava
默认final默认可继承
open classpublic class
override@Override
必须显式标记默认可继承

6. 实际应用

open class Vehicle {
    open fun start() {
        println("Vehicle started")
    }
}

class Car : Vehicle() {
    override fun start() {
        println("Car started")
    }
}

7. 最佳实践

  1. 明确继承意图:使用open明确表示可继承
  2. 使用override:重写方法必须使用override
  3. final by default:默认final提高安全性

3.16 Kotlin的类默认是final的,如何实现继承?

答案:

实现继承的方法:

1. 标记基类为open

// 基类必须标记为open
open class Animal {
    open fun makeSound() {
        println("Animal sound")
    }
}

// 子类继承
class Dog : Animal() {
    override fun makeSound() {
        println("Woof")
    }
}

2. 标记方法为open

open class Animal {
    open fun makeSound() { }  // 可以重写
    fun eat() { }              // 不能重写
}

class Dog : Animal() {
    override fun makeSound() { }  // ✅
    // override fun eat() { }     // ❌ 编译错误
}

3. 抽象类

// 抽象类默认可继承
abstract class Animal {
    abstract fun makeSound()  // 必须实现
    fun eat() { }             // 可以有实现
}

class Dog : Animal() {
    override fun makeSound() {  // 必须实现
        println("Woof")
    }
}

4. 接口

interface Animal {
    fun makeSound()  // 默认可以重写
}

class Dog : Animal {
    override fun makeSound() {
        println("Woof")
    }
}

5. 最佳实践

  1. 使用open:需要继承时标记open
  2. 使用abstract:需要强制实现时使用抽象类
  3. 使用接口:多继承时使用接口

3.17 Kotlin的open关键字的作用是什么?

答案:

open关键字的作用:

1. 允许继承

open class Animal { }  // 可以继承
class Dog : Animal()   // ✅

class Person { }      // 不能继承
// class User : Person()  // ❌ 编译错误

2. 允许重写

open class Animal {
    open fun makeSound() { }  // 可以重写
    fun eat() { }             // 不能重写
}

class Dog : Animal() {
    override fun makeSound() { }  // ✅
    // override fun eat() { }     // ❌
}

3. 与Java对比

Kotlin

open class Animal { }  // 需要显式标记

Java

public class Animal { }  // 默认可继承

4. 最佳实践

  1. 明确意图:使用open明确表示可继承
  2. 最小开放:只标记需要继承的类和方法
  3. 安全性:默认final提高安全性

3.18 Kotlin的方法重写(override)是什么?

答案:

方法重写(override)的规则:

1. 基本语法

open class Animal {
    open fun makeSound() {
        println("Animal sound")
    }
}

class Dog : Animal() {
    override fun makeSound() {  // 必须使用override
        println("Woof")
    }
}

2. override是必须的

open class Animal {
    open fun makeSound() { }
}

class Dog : Animal() {
    override fun makeSound() { }  // override必须写
    // fun makeSound() { }        // ❌ 编译错误,缺少override
}

3. 防止意外重写

open class Animal {
    fun eat() { }  // 默认final,不能重写
}

class Dog : Animal() {
    // override fun eat() { }  // ❌ 编译错误
}

4. 与Java对比

Kotlin

override fun makeSound() { }  // override是关键字

Java

@Override
public void makeSound() { }  // @Override是注解

5. 最佳实践

  1. 必须使用override:重写方法必须使用override
  2. 防止意外重写:默认final防止意外重写
  3. 明确意图:override明确表示重写

5. 接口

3.19 Kotlin的接口是什么?

答案:

Kotlin的接口:

1. 基本语法

interface Animal {
    fun makeSound()
    fun eat()
}

class Dog : Animal {
    override fun makeSound() {
        println("Woof")
    }
    
    override fun eat() {
        println("Eating")
    }
}

2. 接口特性

  • 可以包含抽象方法
  • 可以包含默认实现
  • 可以包含属性
  • 支持多继承

3. 默认实现

interface Animal {
    fun makeSound()  // 抽象方法
    
    fun eat() {      // 默认实现
        println("Eating")
    }
}

class Dog : Animal {
    override fun makeSound() {
        println("Woof")
    }
    // eat()可以不实现,使用默认实现
}

4. 属性

interface Animal {
    val name: String  // 抽象属性
    val age: Int      // 抽象属性
    
    val description: String  // 可以有getter
        get() = "$name is $age years old"
}

class Dog(override val name: String, override val age: Int) : Animal

5. 多继承

interface Flyable {
    fun fly()
}

interface Swimmable {
    fun swim()
}

class Duck : Flyable, Swimmable {
    override fun fly() { }
    override fun swim() { }
}

6. 与Java对比

特性KotlinJava
默认实现支持Java 8+支持
属性支持不支持
多继承支持支持

7. 最佳实践

  1. 定义契约:使用接口定义契约
  2. 默认实现:提供合理的默认实现
  3. 多继承:利用接口实现多继承

3.20 Kotlin接口和Java接口的区别是什么?

答案:

主要区别:

1. 默认实现

Kotlin

interface Animal {
    fun makeSound()  // 抽象方法
    fun eat() {      // 默认实现
        println("Eating")
    }
}

Java

interface Animal {
    void makeSound();  // 抽象方法
    default void eat() {  // Java 8+默认方法
        System.out.println("Eating");
    }
}

2. 属性支持

Kotlin

interface Animal {
    val name: String  // 属性
}

Java

interface Animal {
    // 不能有属性,只能有常量
    String NAME = "Animal";
}

3. 实际应用

两者功能类似,Kotlin语法更简洁。


3.21 Kotlin接口可以包含什么?

答案:

Kotlin接口可以包含:

1. 抽象方法

interface Animal {
    fun makeSound()  // 抽象方法
}

2. 默认实现方法

interface Animal {
    fun makeSound()  // 抽象方法
    fun eat() {      // 默认实现
        println("Eating")
    }
}

3. 抽象属性

interface Animal {
    val name: String  // 抽象属性
}

4. 属性getter

interface Animal {
    val name: String
    val description: String
        get() = "Animal: $name"
}

5. 实际应用

interface User {
    val name: String
    val email: String
    
    fun display(): String {
        return "$name ($email)"
    }
}

6. 抽象类

3.22 Kotlin的抽象类是什么?

答案:

抽象类:

1. 基本语法

abstract class Animal {
    abstract fun makeSound()  // 抽象方法,必须实现
    fun eat() {               // 普通方法,可以有实现
        println("Eating")
    }
}

class Dog : Animal() {
    override fun makeSound() {
        println("Woof")
    }
}

2. 特性

  • 可以包含抽象方法
  • 可以包含普通方法
  • 可以包含属性
  • 不能实例化
  • 默认可继承(不需要open)

3. 与接口对比

特性抽象类接口
实例化不能不能
继承单继承多继承
构造函数可以有不能有
状态可以有不能有

4. 实际应用

abstract class Shape {
    abstract fun area(): Double
    fun description(): String {
        return "Shape with area ${area()}"
    }
}

class Circle(val radius: Double) : Shape() {
    override fun area(): Double {
        return Math.PI * radius * radius
    }
}

3.23 抽象类和接口的区别是什么?

答案:

主要区别:

1. 继承数量

  • 抽象类:单继承
  • 接口:多继承

2. 构造函数

  • 抽象类:可以有构造函数
  • 接口:不能有构造函数

3. 状态

  • 抽象类:可以有状态(属性)
  • 接口:不能有状态(只有属性声明)

4. 使用场景

  • 抽象类:有共同实现、需要状态时
  • 接口:定义契约、多继承时

5. 最佳实践

  1. 优先接口:优先使用接口
  2. 抽象类用于实现:有共同实现时使用抽象类
  3. 组合使用:可以组合使用

7. 数据类

3.24 Kotlin的数据类(Data Class)是什么?

答案:

数据类(Data Class)是专门用于存储数据的类。

1. 基本语法

data class Person(val name: String, val age: Int)

2. 自动生成的方法

数据类自动生成:

  • equals()
  • hashCode()
  • toString()
  • copy()
  • componentN()(解构声明)

3. 示例

data class Person(val name: String, val age: Int)

val person1 = Person("Kotlin", 30)
val person2 = Person("Kotlin", 30)

println(person1 == person2)  // true(equals)
println(person1)             // Person(name=Kotlin, age=30)(toString)
val person3 = person1.copy(age = 31)  // copy

4. 要求

  • 主构造函数至少有一个参数
  • 主构造函数参数必须标记为valvar
  • 不能是抽象、开放、密封或内部类

5. 实际应用

data class User(
    val id: Int,
    val name: String,
    val email: String
)

val user = User(1, "Kotlin", "kotlin@example.com")
val updated = user.copy(email = "new@example.com")

6. 最佳实践

  1. 用于数据存储:存储数据时使用数据类
  2. 利用自动方法:利用自动生成的方法
  3. 不可变优先:优先使用val属性

3.25 数据类的特点是什么?

答案:

数据类的特点:

1. 自动生成方法

自动生成equals、hashCode、toString、copy、componentN。

2. 解构声明

data class Person(val name: String, val age: Int)

val person = Person("Kotlin", 30)
val (name, age) = person  // 解构声明

3. copy方法

val person = Person("Kotlin", 30)
val updated = person.copy(age = 31)  // 创建副本并修改

4. 最佳实践

利用数据类的特性简化代码。


3.26 数据类和普通类的区别是什么?

答案:

主要区别:

1. 自动生成方法

  • 数据类:自动生成equals、hashCode、toString等
  • 普通类:需要手动实现

2. 用途

  • 数据类:存储数据
  • 普通类:业务逻辑

3. 限制

  • 数据类:有特定要求(主构造函数参数等)
  • 普通类:无特殊限制

4. 最佳实践

根据用途选择合适的类型。


3.27 数据类的copy()方法是什么?

答案:

copy()方法用于创建数据类实例的副本,可以修改部分属性。

1. 基本用法

data class Person(val name: String, val age: Int)

val person = Person("Kotlin", 30)
val updated = person.copy(age = 31)  // 创建副本,修改age

2. 修改多个属性

val updated = person.copy(
    name = "Java",
    age = 31
)

3. 实际应用

val user = User(1, "Kotlin", "old@example.com")
val updated = user.copy(email = "new@example.com")

4. 最佳实践

使用copy创建不可变对象的修改版本。


3.28 数据类的equals()、hashCode()、toString()方法

答案:

数据类自动生成这些方法:

1. equals()

data class Person(val name: String, val age: Int)

val p1 = Person("Kotlin", 30)
val p2 = Person("Kotlin", 30)
println(p1 == p2)  // true(比较内容)

2. hashCode()

val p1 = Person("Kotlin", 30)
val p2 = Person("Kotlin", 30)
println(p1.hashCode() == p2.hashCode())  // true

3. toString()

val person = Person("Kotlin", 30)
println(person)  // Person(name=Kotlin, age=30)

4. 最佳实践

利用自动生成的方法,无需手动实现。


3.29 数据类支持解构声明吗?

答案:

是的,数据类支持解构声明。

1. 基本用法

data class Person(val name: String, val age: Int)

val person = Person("Kotlin", 30)
val (name, age) = person  // 解构声明
println("$name is $age years old")

2. 部分解构

val (name, _) = person  // 忽略age

3. 实际应用

val users = listOf(
    Person("Alice", 25),
    Person("Bob", 30)
)

for ((name, age) in users) {
    println("$name: $age")
}

4. 最佳实践

利用解构声明简化代码。


8. 枚举类

3.30 Kotlin的枚举类(Enum Class)是什么?

答案:

枚举类用于定义一组常量。

1. 基本语法

enum class Color {
    RED, GREEN, BLUE
}

2. 带值的枚举

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

3. 带方法的枚举

enum class Color {
    RED {
        override fun description() = "Warm color"
    },
    GREEN {
        override fun description() = "Nature color"
    };
    
    abstract fun description(): String
}

4. 实际应用

enum class Status {
    LOADING, SUCCESS, ERROR
}

fun process(status: Status) {
    when (status) {
        Status.LOADING -> showLoading()
        Status.SUCCESS -> showContent()
        Status.ERROR -> showError()
    }
}

5. 最佳实践

  1. 定义常量集合:使用枚举定义常量
  2. 配合when使用:枚举与when配合使用
  3. 添加方法:需要行为时添加方法

3.31 枚举类和Java枚举的区别是什么?

答案:

主要区别:

1. 语法

Kotlin

enum class Color {
    RED, GREEN, BLUE
}

Java

enum Color {
    RED, GREEN, BLUE
}

2. 功能

两者功能基本相同,Kotlin语法更简洁。

3. 最佳实践

理解两者差异,合理使用。


3.32 枚举类的使用场景有哪些?

答案:

枚举类的使用场景:

1. 状态定义

enum class LoadingState {
    IDLE, LOADING, SUCCESS, ERROR
}

2. 选项定义

enum class Theme {
    LIGHT, DARK, AUTO
}

3. 类型定义

enum class UserType {
    ADMIN, USER, GUEST
}

4. 最佳实践

用于定义固定的常量集合。


9. 嵌套类和内部类

3.33 Kotlin的嵌套类是什么?

答案:

嵌套类(Nested Class)是定义在类内部的类。

1. 基本语法

class Outer {
    class Nested {
        fun display() {
            println("Nested class")
        }
    }
}

// 使用
val nested = Outer.Nested()

2. 特点

  • 不能访问外部类的成员
  • 类似Java的静态内部类
  • 不需要外部类实例

3. 实际应用

class View {
    class LayoutParams {
        // 布局参数
    }
}

val params = View.LayoutParams()

4. 最佳实践

用于逻辑相关的类组织。


3.34 Kotlin的内部类(inner class)是什么?

答案:

内部类(Inner Class)使用inner关键字,可以访问外部类成员。

1. 基本语法

class Outer {
    private val value = 10
    
    inner class Inner {
        fun display() {
            println(value)  // 可以访问外部类成员
        }
    }
}

// 使用
val outer = Outer()
val inner = outer.Inner()

2. 特点

  • 可以访问外部类成员
  • 需要外部类实例
  • 类似Java的非静态内部类

3. 与嵌套类对比

特性嵌套类内部类
关键字inner
访问外部成员不能可以
需要外部实例不需要需要
类似Java静态内部类非静态内部类

4. 实际应用

class View {
    private var width = 0
    
    inner class LayoutParams {
        fun setWidth(w: Int) {
            width = w  // 可以访问外部类成员
        }
    }
}

5. 最佳实践

  1. 需要访问外部成员:使用内部类
  2. 不需要访问:使用嵌套类
  3. 避免过度使用:保持代码简洁

3.35 嵌套类和内部类的区别是什么?

答案:

主要区别:

1. 访问外部成员

  • 嵌套类:不能访问
  • 内部类:可以访问

2. 实例化

  • 嵌套类Outer.Nested()
  • 内部类outer.Inner()

3. 使用场景

  • 嵌套类:逻辑相关但不需要访问外部
  • 内部类:需要访问外部类成员

4. 最佳实践

根据需求选择合适的类型。


3.36 嵌套类和Java的静态内部类的区别是什么?

答案:

主要区别:

1. 语法

Kotlin嵌套类

class Outer {
    class Nested { }
}

Java静态内部类

public class Outer {
    static class Nested { }
}

2. 功能

两者功能相同,Kotlin嵌套类等价于Java静态内部类。


第四章:Kotlin 集合面试题答案

1. 集合基础

4.1 Kotlin的集合类型有哪些?

答案:

Kotlin的集合类型:

1. 基本分类

Kotlin集合分为两大类:

  • 可变集合(Mutable):可以修改
  • 不可变集合(Immutable):不能修改

2. 三种基本类型

List(列表)

// 不可变
val list: List<String> = listOf("a", "b", "c")

// 可变
val mutableList: MutableList<String> = mutableListOf("a", "b", "c")

Set(集合)

// 不可变
val set: Set<String> = setOf("a", "b", "c")

// 可变
val mutableSet: MutableSet<String> = mutableSetOf("a", "b", "c")

Map(映射)

// 不可变
val map: Map<String, Int> = mapOf("a" to 1, "b" to 2)

// 可变
val mutableMap: MutableMap<String, Int> = mutableMapOf("a" to 1, "b" to 2)

3. 层次结构

Collection
├── List
│   ├── List(不可变)
│   └── MutableList(可变)
├── Set
│   ├── Set(不可变)
│   └── MutableSet(可变)
└── Map(独立接口)
    ├── Map(不可变)
    └── MutableMap(可变)

4. 实际应用

// List:有序、可重复
val names = listOf("Alice", "Bob", "Alice")

// Set:无序、不重复
val uniqueNames = setOf("Alice", "Bob", "Alice")  // ["Alice", "Bob"]

// Map:键值对
val ages = mapOf("Alice" to 25, "Bob" to 30)

5. 与Java对比

KotlinJava
ListList(接口)
MutableListArrayListLinkedList
SetSet(接口)
MutableSetHashSetTreeSet
MapMap(接口)
MutableMapHashMapTreeMap

6. 最佳实践

  1. 优先不可变集合:默认使用不可变集合
  2. 需要修改时使用可变集合:明确使用Mutable
  3. 选择合适的类型:根据需求选择List/Set/Map

4.2 List、Set、Map的区别是什么?

答案:

List、Set、Map的主要区别:

1. List(列表)

特点:

  • 有序:元素有顺序
  • 可重复:可以有重复元素
  • 索引访问:可以通过索引访问
val list = listOf("a", "b", "a", "c")
println(list[0])        // "a"
println(list.size)      // 4
println(list)           // [a, b, a, c]

2. Set(集合)

特点:

  • 无序:元素无顺序(HashSet)或有序(LinkedHashSet)
  • 不重复:不能有重复元素
  • 无索引:不能通过索引访问
val set = setOf("a", "b", "a", "c")
println(set.size)       // 3
println(set)            // [a, b, c](去重)
// println(set[0])      // ❌ 编译错误,没有索引

3. Map(映射)

特点:

  • 键值对:存储键值对
  • 键唯一:键不能重复
  • 值可重复:值可以重复
  • 键访问:通过键访问值
val map = mapOf("a" to 1, "b" to 2, "c" to 1)
println(map["a"])       // 1
println(map.size)       // 3
println(map.keys)       // [a, b, c]
println(map.values)     // [1, 2, 1]

4. 对比总结

特性ListSetMap
顺序有序无序/有序无序/有序
重复允许不允许键不重复,值可重复
索引支持不支持不支持(通过键访问)
访问方式list[index]遍历map[key]
使用场景有序列表去重、查找键值映射

5. 实际应用

List:有序列表

val tasks = listOf("Task 1", "Task 2", "Task 3")
tasks.forEachIndexed { index, task ->
    println("$index: $task")
}

Set:去重

val numbers = listOf(1, 2, 2, 3, 3, 3)
val unique = numbers.toSet()  // [1, 2, 3]

Map:键值映射

val userAges = mapOf(
    "Alice" to 25,
    "Bob" to 30,
    "Charlie" to 25
)
val aliceAge = userAges["Alice"]  // 25

6. 最佳实践

  1. List用于有序数据:需要顺序和索引时使用
  2. Set用于去重:需要唯一性时使用
  3. Map用于映射:需要键值关系时使用
  4. 根据需求选择:根据实际需求选择合适的类型

4.3 可变集合和不可变集合的区别是什么?

答案:

可变集合和不可变集合的区别:

1. 基本区别

不可变集合(Immutable)

val list: List<String> = listOf("a", "b", "c")
// list.add("d")  // ❌ 编译错误,不能修改
// list[0] = "x"  // ❌ 编译错误,不能修改

可变集合(Mutable)

val list: MutableList<String> = mutableListOf("a", "b", "c")
list.add("d")     // ✅ 可以添加
list[0] = "x"     // ✅ 可以修改
list.remove("b")  // ✅ 可以删除

2. 类型对比

不可变可变
ListMutableList
SetMutableSet
MapMutableMap

3. 创建方式

不可变集合

val list = listOf("a", "b", "c")
val set = setOf("a", "b", "c")
val map = mapOf("a" to 1, "b" to 2)

可变集合

val list = mutableListOf("a", "b", "c")
val set = mutableSetOf("a", "b", "c")
val map = mutableMapOf("a" to 1, "b" to 2)

4. 转换

// 不可变转可变
val immutable = listOf("a", "b", "c")
val mutable = immutable.toMutableList()

// 可变转不可变
val mutable = mutableListOf("a", "b", "c")
val immutable = mutable.toList()

5. 实际应用

不可变集合(推荐)

// 函数式编程风格
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }  // 返回新列表,不修改原列表

可变集合(必要时)

// 需要修改时使用
val list = mutableListOf(1, 2, 3)
list.add(4)
list.remove(2)

6. 最佳实践

  1. 优先不可变集合:默认使用不可变集合
  2. 需要修改时使用可变:明确使用Mutable
  3. 函数式风格:使用不可变集合支持函数式编程
  4. 线程安全:不可变集合天然线程安全

4.4 Kotlin的集合和Java集合的区别是什么?

答案:

Kotlin集合和Java集合的主要区别:

1. 不可变集合

Kotlin

val list: List<String> = listOf("a", "b", "c")
// list.add("d")  // ❌ 编译错误

Java

List<String> list = Arrays.asList("a", "b", "c");
// list.add("d");  // 运行时抛出UnsupportedOperationException

2. 创建方式

Kotlin

val list = listOf("a", "b", "c")
val set = setOf("a", "b", "c")
val map = mapOf("a" to 1, "b" to 2)

Java

List<String> list = Arrays.asList("a", "b", "c");
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c"));
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);

3. 函数式操作

Kotlin

val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }

Java

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubled = numbers.stream()
    .map(x -> x * 2)
    .collect(Collectors.toList());

4. 空安全

Kotlin

val list: List<String>? = getList()
val size = list?.size ?: 0

Java

List<String> list = getList();
int size = list != null ? list.size() : 0;

5. 类型推断

Kotlin

val list = listOf(1, 2, 3)  // 推断为List<Int>

Java

List<Integer> list = Arrays.asList(1, 2, 3);  // 需要显式类型

6. 最佳实践

  1. 利用Kotlin特性:使用Kotlin的简洁语法
  2. 函数式操作:使用map、filter等函数
  3. 不可变优先:优先使用不可变集合

2. List

4.5 Kotlin的List创建方式有哪些?

答案:

List的创建方式:

1. listOf()

// 不可变List
val list = listOf("a", "b", "c")
val empty = listOf<String>()  // 空列表

2. mutableListOf()

// 可变List
val list = mutableListOf("a", "b", "c")
list.add("d")

3. arrayListOf()

// ArrayList(可变)
val list = arrayListOf("a", "b", "c")

4. emptyList()

// 空列表
val empty: List<String> = emptyList()

5. listOfNotNull()

// 过滤null
val list = listOfNotNull("a", null, "b", null, "c")  // [a, b, c]

6. 构建器

// buildList(Kotlin 1.6+)
val list = buildList {
    add("a")
    add("b")
    add("c")
}

7. 从数组创建

val array = arrayOf("a", "b", "c")
val list = array.toList()

8. 从范围创建

val list = (1..10).toList()  // [1, 2, 3, ..., 10]

9. 实际应用

// 常用方式
val names = listOf("Alice", "Bob", "Charlie")
val numbers = mutableListOf(1, 2, 3)
val empty: List<String> = emptyList()

10. 最佳实践

  1. 优先listOf:大多数情况使用listOf
  2. 需要修改时使用mutableListOf:需要修改时使用
  3. 空列表使用emptyList:明确表示空列表

4.6 List的常用操作有哪些?

答案:

List的常用操作:

1. 访问元素

val list = listOf("a", "b", "c")
list[0]           // "a"
list.first()      // "a"
list.last()       // "c"
list.getOrNull(10)  // null(安全访问)

2. 添加元素(可变List)

val list = mutableListOf("a", "b")
list.add("c")           // 添加
list.add(0, "x")        // 在索引0插入
list += "d"             // 添加

3. 删除元素(可变List)

val list = mutableListOf("a", "b", "c")
list.remove("b")        // 删除元素
list.removeAt(0)        // 删除索引0
list -= "c"             // 删除

4. 查找

val list = listOf("a", "b", "c")
list.contains("a")      // true
list.indexOf("b")       // 1
list.find { it == "b" }  // "b"

5. 过滤

val list = listOf(1, 2, 3, 4, 5)
list.filter { it > 3 }  // [4, 5]
list.filterNot { it > 3 }  // [1, 2, 3]

6. 映射

val list = listOf(1, 2, 3)
list.map { it * 2 }     // [2, 4, 6]
list.mapIndexed { index, value -> "$index: $value" }

7. 排序

val list = listOf(3, 1, 2)
list.sorted()           // [1, 2, 3]
list.sortedDescending() // [3, 2, 1]
list.sortedBy { it }    // [1, 2, 3]

8. 聚合

val list = listOf(1, 2, 3, 4, 5)
list.sum()              // 15
list.average()           // 3.0
list.max()              // 5
list.min()              // 1

9. 实际应用

val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers
    .filter { it > 2 }
    .map { it * 2 }
    .sum()  // 24

10. 最佳实践

  1. 利用函数式操作:使用map、filter等
  2. 链式调用:组合多个操作
  3. 安全访问:使用getOrNull等安全方法

4.7 List的过滤、映射、排序操作

答案:

List的过滤、映射、排序操作:

1. 过滤(filter)

val list = listOf(1, 2, 3, 4, 5)
val evens = list.filter { it % 2 == 0 }  // [2, 4]
val odds = list.filterNot { it % 2 == 0 }  // [1, 3, 5]

2. 映射(map)

val list = listOf(1, 2, 3)
val doubled = list.map { it * 2 }  // [2, 4, 6]
val strings = list.map { it.toString() }  // ["1", "2", "3"]

3. 排序(sorted)

val list = listOf(3, 1, 2)
val sorted = list.sorted()  // [1, 2, 3]
val desc = list.sortedDescending()  // [3, 2, 1]

4. 组合操作

val list = listOf(1, 2, 3, 4, 5)
val result = list
    .filter { it > 2 }      // [3, 4, 5]
    .map { it * 2 }          // [6, 8, 10]
    .sortedDescending()      // [10, 8, 6]

5. 最佳实践

  1. 链式调用:组合多个操作
  2. 利用函数式:使用函数式编程风格
  3. 性能考虑:大量数据时考虑使用序列

3. Set

4.8 Kotlin的Set创建方式有哪些?

答案:

Set的创建方式:

1. setOf()

val set = setOf("a", "b", "c")
val empty = setOf<String>()

2. mutableSetOf()

val set = mutableSetOf("a", "b", "c")
set.add("d")

3. hashSetOf()

val set = hashSetOf("a", "b", "c")

4. linkedSetOf()

val set = linkedSetOf("a", "b", "c")  // 保持插入顺序

5. sortedSetOf()

val set = sortedSetOf("c", "a", "b")  // 自动排序

6. 从List创建

val list = listOf("a", "b", "a", "c")
val set = list.toSet()  // [a, b, c](去重)

7. 最佳实践

  1. 优先setOf:大多数情况使用setOf
  2. 需要修改时使用mutableSetOf:需要修改时使用
  3. 去重使用toSet:从List去重时使用

4.9 Set的常用操作有哪些?

答案:

Set的常用操作:

1. 基本操作

val set = setOf("a", "b", "c")
set.contains("a")      // true
set.size               // 3
set.isEmpty()          // false

2. 集合操作

val set1 = setOf("a", "b", "c")
val set2 = setOf("b", "c", "d")

set1.union(set2)           // [a, b, c, d](并集)
set1.intersect(set2)       // [b, c](交集)
set1.subtract(set2)        // [a](差集)

3. 过滤和映射

val set = setOf(1, 2, 3, 4, 5)
set.filter { it > 3 }      // [4, 5]
set.map { it * 2 }         // [2, 4, 6, 8, 10]

4. 实际应用

// 去重
val list = listOf(1, 2, 2, 3, 3, 3)
val unique = list.toSet()  // [1, 2, 3]

// 集合运算
val admins = setOf("Alice", "Bob")
val users = setOf("Bob", "Charlie")
val onlyAdmins = admins.subtract(users)  // [Alice]

5. 最佳实践

  1. 利用集合操作:使用union、intersect等
  2. 去重功能:使用Set去重
  3. 查找操作:使用contains快速查找

4. Map

4.10 Kotlin的Map创建方式有哪些?

答案:

Map的创建方式:

1. mapOf()

val map = mapOf("a" to 1, "b" to 2, "c" to 3)
val empty = mapOf<String, Int>()

2. mutableMapOf()

val map = mutableMapOf("a" to 1, "b" to 2)
map["c"] = 3

3. hashMapOf()

val map = hashMapOf("a" to 1, "b" to 2)

4. linkedMapOf()

val map = linkedMapOf("a" to 1, "b" to 2)  // 保持插入顺序

5. 构建器

val map = buildMap {
    put("a", 1)
    put("b", 2)
    put("c", 3)
}

6. 从Pair列表创建

val pairs = listOf("a" to 1, "b" to 2)
val map = pairs.toMap()

7. 最佳实践

  1. 优先mapOf:大多数情况使用mapOf
  2. 需要修改时使用mutableMapOf:需要修改时使用
  3. 使用to创建Pair:使用to关键字创建键值对

4.11 Map的常用操作有哪些?

答案:

Map的常用操作:

1. 访问元素

val map = mapOf("a" to 1, "b" to 2)
map["a"]              // 1
map.getValue("a")     // 1(如果不存在抛异常)
map.getOrDefault("c", 0)  // 0(默认值)
map.getOrElse("c") { 0 }  // 0(计算默认值)

2. 添加/修改(可变Map)

val map = mutableMapOf("a" to 1)
map["b"] = 2          // 添加
map.put("c", 3)       // 添加
map["a"] = 10         // 修改

3. 删除(可变Map)

val map = mutableMapOf("a" to 1, "b" to 2)
map.remove("a")       // 删除键"a"
map -= "b"            // 删除键"b"

4. 遍历

val map = mapOf("a" to 1, "b" to 2)
map.forEach { (key, value) ->
    println("$key: $value")
}

for ((key, value) in map) {
    println("$key: $value")
}

5. 键和值

val map = mapOf("a" to 1, "b" to 2)
map.keys              // [a, b]
map.values            // [1, 2]
map.entries           // 键值对集合

6. 过滤和映射

val map = mapOf("a" to 1, "b" to 2, "c" to 3)
map.filter { it.value > 1 }  // {b=2, c=3}
map.mapKeys { it.key.uppercase() }  // {A=1, B=2, C=3}
map.mapValues { it.value * 2 }  // {a=2, b=4, c=6}

7. 实际应用

val userAges = mapOf(
    "Alice" to 25,
    "Bob" to 30,
    "Charlie" to 25
)

val adults = userAges.filter { it.value >= 18 }
val names = userAges.keys
val totalAge = userAges.values.sum()

8. 最佳实践

  1. 安全访问:使用getOrDefault或getOrElse
  2. 遍历使用解构:使用(key, value)解构
  3. 利用函数式操作:使用filter、map等

4.12 Map的遍历方式有哪些?

答案:

Map的遍历方式:

1. forEach

val map = mapOf("a" to 1, "b" to 2)
map.forEach { (key, value) ->
    println("$key: $value")
}

2. for-in循环

val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) {
    println("$key: $value")
}

3. entries遍历

val map = mapOf("a" to 1, "b" to 2)
for (entry in map.entries) {
    println("${entry.key}: ${entry.value}")
}

4. keys/values遍历

val map = mapOf("a" to 1, "b" to 2)
for (key in map.keys) {
    println(key)
}
for (value in map.values) {
    println(value)
}

5. 实际应用

val userAges = mapOf("Alice" to 25, "Bob" to 30)
userAges.forEach { (name, age) ->
    println("$name is $age years old")
}

6. 最佳实践

  1. 使用解构:优先使用(key, value)解构
  2. forEach简洁:使用forEach更简洁
  3. 根据需要选择:根据需求选择合适的遍历方式

5. 集合操作

4.13 Kotlin的集合操作符有哪些?

答案:

Kotlin提供了丰富的集合操作符:

1. 转换操作

  • map:转换每个元素
  • mapIndexed:带索引的转换
  • flatMap:扁平化转换
  • flatten:扁平化

2. 过滤操作

  • filter:过滤
  • filterNot:反向过滤
  • filterIndexed:带索引过滤
  • take:取前N个
  • drop:跳过前N个

3. 查找操作

  • find:查找第一个
  • first:第一个元素
  • last:最后一个元素
  • firstOrNull:第一个或null
  • lastOrNull:最后一个或null

4. 聚合操作

  • sum:求和
  • average:平均值
  • max:最大值
  • min:最小值
  • count:计数
  • reduce:归约
  • fold:折叠

5. 排序操作

  • sorted:排序
  • sortedBy:按条件排序
  • sortedDescending:降序
  • reversed:反转

6. 分组操作

  • groupBy:分组
  • partition:分割
  • chunked:分块

7. 实际应用

val numbers = listOf(1, 2, 3, 4, 5)
numbers.map { it * 2 }           // [2, 4, 6, 8, 10]
numbers.filter { it > 3 }       // [4, 5]
numbers.sum()                   // 15
numbers.groupBy { it % 2 }     // {1=[1,3,5], 0=[2,4]}

8. 最佳实践

  1. 链式调用:组合多个操作
  2. 利用函数式:使用函数式编程风格
  3. 性能考虑:大量数据时使用序列

4.14 filter、map、flatMap的区别是什么?

答案:

三个操作符的区别:

1. filter

过滤元素,返回满足条件的元素:

val list = listOf(1, 2, 3, 4, 5)
val evens = list.filter { it % 2 == 0 }  // [2, 4]

2. map

转换每个元素,一对一映射:

val list = listOf(1, 2, 3)
val doubled = list.map { it * 2 }  // [2, 4, 6]

3. flatMap

转换并扁平化,一对多映射:

val list = listOf("abc", "def")
val chars = list.flatMap { it.toList() }  // [a, b, c, d, e, f]

// 等价于
val chars = list.map { it.toList() }.flatten()

4. 对比总结

操作符输入输出映射关系
filterList<T>List<T>过滤,数量可能减少
mapList<T>List<R>一对一转换
flatMapList<T>List<R>一对多转换并扁平化

5. 实际应用

val words = listOf("hello", "world")
val letters = words.flatMap { it.toList() }  // [h, e, l, l, o, w, o, r, l, d]

val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter { it % 2 == 0 }   // [2, 4]
val doubled = evens.map { it * 2 }           // [4, 8]

6. 最佳实践

  1. filter用于过滤:选择满足条件的元素
  2. map用于转换:一对一转换
  3. flatMap用于扁平化:一对多转换并扁平化

4.15 reduce、fold的区别是什么?

答案:

reducefold的区别:

1. reduce

从第一个元素开始归约,没有初始值:

val list = listOf(1, 2, 3, 4, 5)
val sum = list.reduce { acc, x -> acc + x }  // 15
// 过程:((((1+2)+3)+4)+5)

2. fold

从初始值开始归约,有初始值:

val list = listOf(1, 2, 3, 4, 5)
val sum = list.fold(0) { acc, x -> acc + x }  // 15
// 过程:(((((0+1)+2)+3)+4)+5)

3. 区别对比

特性reducefold
初始值无(使用第一个元素)
空集合抛出异常返回初始值
使用场景非空集合归约需要初始值的归约

4. 实际应用

reduce

val numbers = listOf(1, 2, 3, 4, 5)
val product = numbers.reduce { acc, x -> acc * x }  // 120

fold

val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.fold(0) { acc, x -> acc + x }  // 15
val product = numbers.fold(1) { acc, x -> acc * x }  // 120

5. 最佳实践

  1. reduce用于非空集合:确定集合非空时使用
  2. fold用于需要初始值:需要初始值或可能为空时使用
  3. 空集合处理:空集合时fold更安全

4.16 groupBy、partition的区别是什么?

答案:

groupBypartition的区别:

1. groupBy

按条件分组,返回Map:

val numbers = listOf(1, 2, 3, 4, 5)
val grouped = numbers.groupBy { it % 2 }
// {1=[1,3,5], 0=[2,4]}

2. partition

按条件分割,返回Pair:

val numbers = listOf(1, 2, 3, 4, 5)
val (evens, odds) = numbers.partition { it % 2 == 0 }
// evens = [2, 4]
// odds = [1, 3, 5]

3. 区别对比

特性groupBypartition
返回值Map<K, List<T>>Pair<List<T>, List<T>>
分组数量多个组两个组(true/false)
使用场景多组分类二分分类

4. 实际应用

groupBy

val users = listOf(
    User("Alice", 25),
    User("Bob", 30),
    User("Charlie", 25)
)
val byAge = users.groupBy { it.age }
// {25=[Alice, Charlie], 30=[Bob]}

partition

val numbers = listOf(1, 2, 3, 4, 5)
val (evens, odds) = numbers.partition { it % 2 == 0 }

5. 最佳实践

  1. groupBy用于多组:需要多个分组时使用
  2. partition用于二分:只需要true/false两组时使用
  3. 根据需求选择:根据实际需求选择合适的操作

4.17 take、drop、slice的区别是什么?

答案:

三个操作符的区别:

1. take

取前N个元素:

val list = listOf(1, 2, 3, 4, 5)
list.take(3)  // [1, 2, 3]

2. drop

跳过前N个元素:

val list = listOf(1, 2, 3, 4, 5)
list.drop(3)  // [4, 5]

3. slice

取指定索引范围的元素:

val list = listOf(1, 2, 3, 4, 5)
list.slice(1..3)      // [2, 3, 4]
list.slice(listOf(0, 2, 4))  // [1, 3, 5]

4. 对比总结

操作符作用示例
take取前N个take(3) → 前3个
drop跳过前N个drop(3) → 跳过前3个
slice取指定范围slice(1..3) → 索引1到3

5. 实际应用

val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.take(5)        // [1, 2, 3, 4, 5]
list.drop(5)        // [6, 7, 8, 9, 10]
list.slice(2..5)    // [3, 4, 5, 6]

6. 最佳实践

  1. take用于取前N个:需要前N个元素时使用
  2. drop用于跳过:需要跳过前N个时使用
  3. slice用于范围:需要指定范围时使用

4.18 distinct、sorted的区别是什么?

答案:

distinctsorted的区别:

1. distinct

去重,移除重复元素:

val list = listOf(1, 2, 2, 3, 3, 3)
list.distinct()  // [1, 2, 3]

2. sorted

排序,按顺序排列:

val list = listOf(3, 1, 2)
list.sorted()  // [1, 2, 3]

3. 组合使用

val list = listOf(3, 1, 2, 2, 3)
list.distinct().sorted()  // [1, 2, 3](先去重后排序)

4. 实际应用

val numbers = listOf(3, 1, 2, 2, 3, 1)
val unique = numbers.distinct()      // [3, 1, 2]
val sorted = numbers.sorted()       // [1, 1, 2, 2, 3, 3]
val uniqueSorted = numbers.distinct().sorted()  // [1, 2, 3]

5. 最佳实践

  1. distinct用于去重:需要唯一元素时使用
  2. sorted用于排序:需要排序时使用
  3. 组合使用:可以组合使用

4.19 集合的转换操作(toList、toSet、toMap)

答案:

集合之间的转换操作:

1. toList()

转换为List:

val set = setOf("a", "b", "c")
val list = set.toList()  // [a, b, c]

2. toSet()

转换为Set(去重):

val list = listOf("a", "b", "a", "c")
val set = list.toSet()  // [a, b, c](去重)

3. toMap()

转换为Map:

val pairs = listOf("a" to 1, "b" to 2)
val map = pairs.toMap()  // {a=1, b=2}

4. 实际应用

// List转Set(去重)
val list = listOf(1, 2, 2, 3, 3, 3)
val unique = list.toSet()  // [1, 2, 3]

// Set转List
val set = setOf("a", "b", "c")
val list = set.toList()  // [a, b, c]

// Pair列表转Map
val pairs = listOf("a" to 1, "b" to 2)
val map = pairs.toMap()

5. 最佳实践

  1. 利用转换:使用转换操作简化代码
  2. 去重使用toSet:从List去重时使用toSet
  3. 保持类型:根据需求选择合适的类型

4.20 集合的聚合操作(sum、average、max、min)

答案:

集合的聚合操作:

1. sum()

求和:

val numbers = listOf(1, 2, 3, 4, 5)
numbers.sum()  // 15

2. average()

平均值:

val numbers = listOf(1, 2, 3, 4, 5)
numbers.average()  // 3.0

3. max() / maxOrNull()

最大值:

val numbers = listOf(1, 2, 3, 4, 5)
numbers.maxOrNull()  // 5

4. min() / minOrNull()

最小值:

val numbers = listOf(1, 2, 3, 4, 5)
numbers.minOrNull()  // 1

5. count()

计数:

val numbers = listOf(1, 2, 3, 4, 5)
numbers.count()  // 5
numbers.count { it > 3 }  // 2

6. 实际应用

val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.sum()           // 15
val avg = numbers.average()       // 3.0
val max = numbers.maxOrNull()     // 5
val min = numbers.minOrNull()     // 1
val count = numbers.count { it > 3 }  // 2

7. 最佳实践

  1. 使用聚合操作:利用内置的聚合函数
  2. 空集合处理:使用OrNull版本处理空集合
  3. 条件计数:使用count进行条件计数

第五章:Kotlin 空安全面试题答案

1. 可空类型

5.1 Kotlin的可空类型(Nullable Types)是什么?

答案:

可空类型(Nullable Types)是Kotlin类型系统的核心特性,用于在编译时防止空指针异常(NullPointerException,NPE)。

1. 基本概念

Kotlin的类型系统区分可空类型和非空类型:

// 非空类型(默认)
var name: String = "Kotlin"
// name = null  // ❌ 编译错误,不能为null

// 可空类型(使用?标记)
var name: String? = "Kotlin"
name = null  // ✅ 允许为null

2. 类型标记

使用?标记可空类型:

// 基本类型可空
var age: Int? = null
var price: Double? = null
var active: Boolean? = null

// 对象类型可空
var user: User? = null
var list: List<String>? = null

3. 设计目的

Java的问题

// Java中所有引用类型都可以为null,容易产生NPE
String name = null;
int length = name.length();  // 运行时抛出NullPointerException

Kotlin的解决方案

// Kotlin类型系统在编译时检查
var name: String? = null
val length = name.length  // ❌ 编译错误,不能直接调用
val length = name?.length  // ✅ 需要使用安全调用

4. 类型系统优势

  1. 编译时检查:在编译时就能发现潜在的空指针问题
  2. 明确意图:可空类型明确表示值可能为null
  3. 强制处理:必须显式处理可空类型
  4. 减少崩溃:大大减少运行时NPE

5. 示例

// 非空类型
fun processName(name: String) {
    println(name.length)  // 安全,name不能为null
}

// 可空类型
fun processName(name: String?) {
    // name.length  // ❌ 编译错误
    println(name?.length ?: 0)  // ✅ 安全处理
}

// 调用
processName("Kotlin")  // ✅ 可以
processName(null)      // ✅ 可以(参数可空)

6. 与Java对比

特性KotlinJava
类型区分可空/非空所有引用可空
检查时机编译时运行时
默认类型非空可空
NPE风险
强制处理

7. 实际应用

函数参数

// 可空参数
fun greet(name: String?) {
    val greeting = name?.let { "Hello, $it" } ?: "Hello, Guest"
    println(greeting)
}

greet("Kotlin")  // Hello, Kotlin
greet(null)      // Hello, Guest

函数返回值

// 可空返回值
fun findUser(id: Int): User? {
    return users.find { it.id == id }  // 可能返回null
}

val user = findUser(123)
user?.let { println(it.name) }  // 安全处理

属性

class User {
    var name: String? = null  // 可空属性
    var age: Int = 0          // 非空属性
}

8. 最佳实践

  1. 优先使用非空类型:除非确实需要null
  2. 明确标记可空:使用?明确标记
  3. 安全处理:使用空安全操作符处理可空类型
  4. 避免滥用:不要过度使用可空类型

5.2 可空类型和非空类型的区别是什么?

答案:

可空类型和非空类型的主要区别:

1. 定义区别

非空类型(默认)

var name: String = "Kotlin"
// name = null  // ❌ 编译错误

可空类型(使用?)

var name: String? = "Kotlin"
name = null  // ✅ 允许

2. 使用区别

非空类型

var name: String = "Kotlin"
println(name.length)  // ✅ 直接使用,不需要空检查

可空类型

var name: String? = null
// println(name.length)  // ❌ 编译错误
println(name?.length ?: 0)  // ✅ 需要安全调用

3. 赋值区别

// 非空类型不能赋值为null
var name: String = "Kotlin"
// name = null  // ❌ 编译错误

// 可空类型可以赋值为null
var name: String? = "Kotlin"
name = null  // ✅ 允许

// 非空类型不能接收可空值(需要显式处理)
var name: String = "Kotlin"
var nullableName: String? = null
// name = nullableName  // ❌ 编译错误
name = nullableName ?: "Default"  // ✅ 需要提供默认值

4. 函数调用区别

非空类型参数

fun process(name: String) {
    println(name.length)  // 安全,name不能为null
}

process("Kotlin")  // ✅
// process(null)   // ❌ 编译错误

可空类型参数

fun process(name: String?) {
    name?.let { println(it.length) }  // 需要安全处理
}

process("Kotlin")  // ✅
process(null)      // ✅

5. 返回值区别

非空返回值

fun getName(): String {
    return "Kotlin"  // 必须返回非null值
    // return null  // ❌ 编译错误
}

可空返回值

fun findName(id: Int): String? {
    return names.find { it.id == id }?.name  // 可以返回null
}

6. 对比总结

特性非空类型可空类型
标记无标记?标记
可为null
直接调用可以不能
安全调用不需要需要
编译检查严格强制处理
使用场景默认可能为null时

7. 类型推断

// 非空类型推断
val name = "Kotlin"  // 推断为String

// 可空类型推断
val name = null  // 推断为Nothing?
val name: String? = null  // 显式声明

8. 实际应用

推荐做法

// 优先使用非空类型
val userName: String = getUserName()  // 如果确定不为null

// 确实可能为null时使用可空类型
val optionalName: String? = findOptionalName()

// 安全处理
val displayName = optionalName ?: "Unknown"

9. 最佳实践

  1. 默认非空:优先使用非空类型
  2. 明确标记:确实需要null时使用可空类型
  3. 安全处理:正确处理可空类型
  4. 避免滥用:不要过度使用可空类型

5.3 如何声明可空类型?

答案:

声明可空类型的方法:

1. 基本声明

使用?标记类型:

// 变量声明
var name: String? = null
var age: Int? = null
var price: Double? = null
var active: Boolean? = null

// 常量声明
val name: String? = null

2. 函数参数

// 可空参数
fun greet(name: String?) {
    name?.let { println("Hello, $it") }
}

// 调用
greet("Kotlin")  // ✅
greet(null)      // ✅

3. 函数返回值

// 可空返回值
fun findUser(id: Int): User? {
    return users.find { it.id == id }
}

// 使用
val user = findUser(123)
user?.let { println(it.name) }

4. 属性声明

class User {
    var name: String? = null      // 可空属性
    var email: String? = null     // 可空属性
    var age: Int = 0              // 非空属性
}

5. 集合类型

// 可空元素集合
val list: List<String?> = listOf("a", null, "c")

// 可空集合
val list: List<String>? = null

// 可空元素且可空集合
val list: List<String?>? = null

6. 泛型可空

// 泛型参数可空
fun <T> process(value: T?): T? {
    return value?.let { transform(it) }
}

7. 延迟初始化

class User {
    // 方式1:可空类型
    var name: String? = null
    
    // 方式2:lateinit(仅用于var)
    lateinit var email: String
    
    // 方式3:lazy(用于val)
    val description: String by lazy {
        "User description"
    }
}

8. 类型推断

// 明确声明
val name: String? = null

// 类型推断(null推断为Nothing?)
val name = null  // 推断为Nothing?
val name: String? = null  // 需要显式声明

9. 最佳实践

  1. 明确声明:使用?明确标记可空类型
  2. 优先非空:除非确实需要null
  3. 延迟初始化:使用lateinitlazy替代可空类型
  4. 安全处理:声明后正确使用空安全操作符

5.4 可空类型的安全调用(Safe Call)是什么?

答案:

安全调用(Safe Call)使用?.操作符,在对象为null时返回null而不是抛出异常。

1. 基本语法

val result = object?.method()
val property = object?.property

2. 基本用法

val name: String? = null
val length = name?.length  // 如果name为null,返回null;否则返回length

// 不使用安全调用(编译错误)
// val length = name.length  // ❌ 编译错误

// 使用安全调用
val length = name?.length  // ✅ 返回Int?

3. 安全调用链

val user: User? = getUser()
val email = user?.profile?.email  // 如果任何部分为null,返回null

// 等价于Java的
// String email = user != null && user.profile != null 
//     ? user.profile.email : null;

4. 与Elvis操作符配合

val name: String? = null
val length = name?.length ?: 0  // 如果为null,返回0

val user: User? = null
val email = user?.profile?.email ?: "no-email"

5. 与let配合

val name: String? = "Kotlin"
name?.let { 
    println("Name: $it")
    process(it)
}

// 如果name为null,整个代码块不执行

6. 实际应用

属性访问

val user: User? = getUser()
val name = user?.name  // String?
val age = user?.age    // Int?

方法调用

val list: List<String>? = getList()
val size = list?.size  // Int?
val first = list?.first()  // String?

嵌套访问

val user: User? = getUser()
val street = user?.address?.street  // String?
val city = user?.address?.city      // String?

7. 与Java对比

Kotlin

val user: User? = getUser()
val email = user?.profile?.email ?: "no-email"

Java

User user = getUser();
String email = user != null && user.getProfile() != null 
    ? user.getProfile().getEmail() : "no-email";

8. 返回值类型

安全调用的返回值是可空类型:

val name: String? = "Kotlin"
val length: Int? = name?.length  // Int?(可空)

// 配合Elvis操作符
val length: Int = name?.length ?: 0  // Int(非空)

9. 最佳实践

  1. 优先使用安全调用:避免NPE
  2. 配合Elvis操作符:提供默认值
  3. 配合let使用:执行多个操作
  4. 避免过度使用:确定非空时不需要安全调用

2. 空安全操作符

5.5 ?. 安全调用操作符的用法

答案:

?.安全调用操作符的详细用法:

1. 基本语法

对象?.属性
对象?.方法()

2. 属性访问

val user: User? = getUser()
val name = user?.name      // 如果user为null,返回null
val age = user?.age        // 如果user为null,返回null

3. 方法调用

val list: List<String>? = getList()
val size = list?.size           // Int?
val first = list?.first()       // String?
val isEmpty = list?.isEmpty()   // Boolean?

4. 链式调用

val user: User? = getUser()
val street = user?.address?.street?.uppercase()  // 任何环节为null,返回null

5. 与Elvis操作符

val name: String? = null
val length = name?.length ?: 0  // 如果name或length为null,返回0

6. 与let配合

val user: User? = getUser()
user?.let { 
    println("User: ${it.name}")
    processUser(it)
}  // 如果user为null,整个代码块不执行

7. 实际应用示例

API调用

val response: Response? = apiCall()
val data = response?.body?.data  // 安全访问嵌套属性

集合操作

val list: List<String>? = getList()
val processed = list?.map { it.uppercase() }  // List<String>?
val filtered = list?.filter { it.isNotEmpty() }  // List<String>?

8. 返回值类型

// 安全调用返回可空类型
val name: String? = "Kotlin"
val length: Int? = name?.length  // Int?

// 配合Elvis返回非空类型
val length: Int = name?.length ?: 0  // Int

9. 最佳实践

  1. 优先使用?.:避免NPE
  2. 配合Elvis:提供默认值
  3. 避免过度嵌套:考虑提取变量
  4. 配合let:执行多个操作

5.6 ?: Elvis操作符的用法

答案:

Elvis操作符?:用于在值为null时提供默认值。

1. 基本语法

值 ?: 默认值

2. 基本用法

val name: String? = null
val displayName = name ?: "Unknown"  // 如果name为null,返回"Unknown"

val age: Int? = null
val displayAge = age ?: 0  // 如果age为null,返回0

3. 与安全调用配合

val user: User? = null
val name = user?.name ?: "Guest"  // 如果user或name为null,返回"Guest"

val length = name?.length ?: 0  // 如果name或length为null,返回0

4. 表达式支持

Elvis操作符右侧可以是表达式:

val name: String? = null
val displayName = name ?: throw IllegalArgumentException("Name required")

val count: Int? = null
val total = count ?: calculateDefault()  // 表达式

5. return和throw

Elvis操作符可以配合return和throw使用:

fun process(name: String?) {
    val validName = name ?: return  // 如果name为null,直接返回
    println("Processing: $validName")
}

fun requireName(name: String?): String {
    return name ?: throw IllegalArgumentException("Name required")
}

6. 实际应用

默认值

val user: User? = getUser()
val name = user?.name ?: "Unknown"
val email = user?.email ?: "no-email@example.com"

集合默认值

val list: List<String>? = getList()
val items = list ?: emptyList()  // 如果为null,返回空列表

数值默认值

val price: Double? = getPrice()
val total = price ?: 0.0  // 如果为null,返回0.0

7. 嵌套使用

val user: User? = getUser()
val street = user?.address?.street ?: "Unknown Street"
val city = user?.address?.city ?: "Unknown City"

8. 与Java对比

Kotlin

val name: String? = null
val displayName = name ?: "Unknown"

Java

String name = null;
String displayName = name != null ? name : "Unknown";

9. 最佳实践

  1. 提供有意义默认值:不要滥用默认值
  2. 配合安全调用:组合使用更强大
  3. 使用表达式:右侧可以是表达式
  4. 配合return/throw:提前返回或抛出异常

5.7 !! 非空断言操作符的用法

答案:

!!非空断言操作符用于将可空类型强制转换为非空类型,如果值为null会抛出NPE。

1. 基本语法

值!!

2. 基本用法

val name: String? = "Kotlin"
val length = name!!.length  // 强制转换为String,返回Int

// 如果name为null,会抛出NullPointerException
val name2: String? = null
// val length2 = name2!!.length  // 运行时抛出NPE

3. 风险说明

危险用法

val name: String? = getUserName()
val length = name!!.length  // ⚠️ 如果name为null,抛出NPE

4. 使用场景

确定非空时使用

val name: String? = "Kotlin"
// 经过检查确定非空
if (name != null) {
    val length = name.length  // 智能转换,不需要!!
}

// 如果确实确定非空
val length = name!!.length  // 可以使用,但不推荐

5. 最佳实践

不推荐使用

// ❌ 不推荐
val user: User? = getUser()
val name = user!!.name  // 可能抛出NPE

推荐做法

// ✅ 推荐:使用安全调用
val user: User? = getUser()
val name = user?.name ?: "Unknown"

// ✅ 推荐:使用let
user?.let {
    println(it.name)
}

// ✅ 推荐:使用智能转换
if (user != null) {
    println(user.name)  // 智能转换
}

6. 何时使用

应该尽量避免使用!!,仅在以下情况考虑:

  1. 确定非空:有100%把握值不为null
  2. 测试代码:测试中明确期望NPE
  3. 遗留代码:与Java互操作时临时使用

7. 实际应用

不推荐

fun process(user: User?) {
    val name = user!!.name  // ⚠️ 危险
    val email = user!!.email  // ⚠️ 危险
}

推荐

fun process(user: User?) {
    user?.let {
        val name = it.name  // 安全
        val email = it.email  // 安全
        // 处理
    }
}

8. 与安全调用对比

操作符行为风险
?.null时返回null无风险
!!null时抛出NPE高风险

9. 最佳实践

  1. 避免使用!!:优先使用安全调用
  2. 使用安全操作符?.?:let
  3. 智能转换:利用Kotlin的智能转换
  4. 明确处理:明确处理null情况

5.8 ?.let的用法和作用

答案:

?.let是安全调用操作符与let函数的组合,用于在对象非空时执行代码块。

1. 基本语法

对象?.let { 
    // 代码块,it指向对象
}

2. 基本用法

val name: String? = "Kotlin"
name?.let { 
    println("Name: $it")  // it指向name
    process(it)
}

// 如果name为null,整个代码块不执行

3. 作用

  1. 空安全:只在对象非空时执行
  2. 作用域:提供一个作用域,it指向对象
  3. 返回值:let返回代码块的返回值
  4. 链式调用:可以链式调用

4. 返回值

val name: String? = "Kotlin"
val length: Int? = name?.let { it.length }  // Int?

// 配合Elvis
val length: Int = name?.let { it.length } ?: 0  // Int

5. 实际应用

单次使用

val user: User? = getUser()
user?.let {
    println("User: ${it.name}")
    sendEmail(it.email)
    updateLastLogin(it)
}

链式调用

val user: User? = getUser()
val result = user?.let { it.name }
              ?.let { it.uppercase() }
              ?.let { "Hello, $it" }

6. 与普通if对比

使用?.let

val name: String? = getName()
name?.let {
    println(it)
    process(it)
}

使用if(等价)

val name: String? = getName()
if (name != null) {
    println(name)
    process(name)
}

7. 优势

  1. 简洁:代码更简洁
  2. 作用域:明确作用域
  3. 链式:支持链式调用
  4. 函数式:符合函数式编程风格

8. 最佳实践

  1. 替代if-null检查:更简洁
  2. 多个操作:执行多个操作时使用
  3. 链式调用:需要转换时链式调用
  4. 配合Elvis:提供默认值

3. 空安全实践

5.9 如何避免空指针异常?

答案:

避免空指针异常的方法:

1. 使用非空类型(默认)

// 优先使用非空类型
val name: String = "Kotlin"  // 不能为null,避免NPE

2. 正确使用可空类型

// 确实可能为null时使用可空类型
val name: String? = getName()

// 安全处理
val length = name?.length ?: 0

3. 使用安全调用操作符

val user: User? = getUser()
val email = user?.profile?.email ?: "no-email"

4. 使用Elvis操作符

val name: String? = null
val displayName = name ?: "Unknown"

5. 使用let函数

val user: User? = getUser()
user?.let {
    processUser(it)
}

6. 使用智能转换

val name: String? = getName()
if (name != null) {
    // name自动转换为非空类型
    println(name.length)  // 不需要?.
}

7. 使用when表达式

val user: User? = getUser()
when (user) {
    null -> println("No user")
    else -> {
        println("User: ${user.name}")
        processUser(user)
    }
}

8. 避免使用!!

// ❌ 避免
val name = user!!.name

// ✅ 推荐
val name = user?.name ?: "Unknown"

9. 延迟初始化

class User {
    // 使用lateinit替代可空类型
    lateinit var name: String
    
    // 或使用lazy
    val description: String by lazy {
        "Description"
    }
}

10. 最佳实践总结

  1. 优先非空类型:默认使用非空类型
  2. 安全操作符:使用?.?:let
  3. 智能转换:利用类型检查后的智能转换
  4. 避免!!:不要使用非空断言
  5. 延迟初始化:使用lateinitlazy

5.10 可空类型的最佳实践是什么?

答案:

可空类型的最佳实践:

1. 优先使用非空类型

// ✅ 推荐:优先非空类型
val name: String = "Kotlin"

// ❌ 避免:除非确实需要null
val name: String? = "Kotlin"

2. 明确标记可空

// ✅ 明确标记
val optionalName: String? = findOptionalName()

// ❌ 不明确
val name = findName()  // 类型不明确

3. 安全处理可空类型

val name: String? = getName()
// ✅ 使用安全调用
val length = name?.length ?: 0

// ✅ 使用let
name?.let { process(it) }

// ❌ 避免!!
// val length = name!!.length

4. 提供有意义的默认值

// ✅ 有意义的默认值
val name = user?.name ?: "Unknown User"
val email = user?.email ?: "no-email@example.com"

// ❌ 无意义的默认值
val name = user?.name ?: ""  // 空字符串可能不是最佳选择

5. 使用延迟初始化

class User {
    // ✅ 使用lateinit替代可空类型(如果确定会初始化)
    lateinit var name: String
    
    // ✅ 使用lazy(如果计算成本高)
    val description: String by lazy {
        calculateDescription()
    }
    
    // ❌ 避免不必要的可空
    // var name: String? = null
}

6. 函数设计

// ✅ 推荐:返回值明确
fun findUser(id: Int): User? {
    return users.find { it.id == id }
}

// ✅ 推荐:参数明确
fun processUser(user: User?) {
    user?.let { process(it) }
}

// ❌ 避免:返回可空但没有意义
fun getUserName(): String? {
    return "Kotlin"  // 总是返回非null,不应该声明为可空
}

7. 集合处理

// ✅ 处理可空集合
val list: List<String>? = getList()
val items = list ?: emptyList()
val size = items.size

// ✅ 处理可空元素
val list: List<String?> = listOf("a", null, "c")
val nonNull = list.filterNotNull()  // [a, c]

8. 与Java互操作

// ✅ 处理Java可空类型
fun processJavaString(str: String?) {
    str?.let { println(it) }
}

// ✅ 使用注解
fun processWithAnnotation(@Nullable name: String?) {
    name?.let { process(it) }
}

9. 代码审查要点

  1. 检查可空使用:是否合理
  2. 检查安全处理:是否正确处理null
  3. 检查!!使用:是否必要
  4. 检查默认值:是否有意义

10. 最佳实践总结

  1. 默认非空:优先使用非空类型
  2. 明确标记:确实需要null时使用?
  3. 安全处理:使用?.?:let
  4. 有意义的默认值:使用Elvis提供有意义默认值
  5. 延迟初始化:使用lateinitlazy
  6. 避免!!:不要使用非空断言
  7. 代码审查:定期审查可空类型使用

5.11 安全转换(Safe Cast)as?的用法

答案:

安全转换as?用于安全地进行类型转换,失败时返回null而不是抛出异常。

1. 基本语法

as? 目标类型

2. 基本用法

val obj: Any = "Kotlin"
val str: String? = obj as? String  // 安全转换,返回String?

val obj2: Any = 123
val str2: String? = obj2 as? String  // null(转换失败)

3. 与非安全转换对比

操作符失败时行为返回类型
as抛出异常非空类型
as?返回null可空类型

4. 示例

// 安全转换
val obj: Any = "Kotlin"
val str = obj as? String
str?.let { println(it.length) }

// 非安全转换(不推荐)
val obj2: Any = "Kotlin"
try {
    val str2 = obj2 as String  // 可能抛出异常
    println(str2.length)
} catch (e: ClassCastException) {
    // 处理异常
}

5. 与is配合使用

// 方式1:使用is(推荐)
val obj: Any = "Kotlin"
if (obj is String) {
    println(obj.length)  // 智能转换
}

// 方式2:使用as?(也可以)
val str = obj as? String
str?.let { println(it.length) }

6. when表达式中的使用

fun process(value: Any) {
    when (val str = value as? String) {
        null -> println("Not a string")
        else -> println("String: ${str.length}")
    }
}

7. 链式转换

val obj: Any = getUser()
val user = obj as? User
val name = user?.name  // 安全访问

8. 实际应用

类型转换

fun processValue(value: Any) {
    val str = value as? String
    str?.let { 
        println("String: $it")
    } ?: run {
        println("Not a string")
    }
}

集合转换

val list: List<Any> = listOf("a", 1, "b", 2)
val strings = list.mapNotNull { it as? String }  // ["a", "b"]

9. 最佳实践

  1. 优先使用as?:更安全
  2. 配合let使用:处理转换结果
  3. 配合is使用:类型检查后使用智能转换
  4. 避免!!:不要对as?结果使用!!

5.12 非空断言!!的使用场景和风险

答案:

非空断言!!的使用场景和风险:

1. 基本语法

值!!

2. 行为

val name: String? = "Kotlin"
val length = name!!.length  // 强制转换为String,返回Int

val name2: String? = null
val length2 = name2!!.length  // 运行时抛出NullPointerException

3. 风险

高风险操作

val user: User? = getUser()
val name = user!!.name  // ⚠️ 如果user为null,抛出NPE
val email = user!!.email  // ⚠️ 如果user为null,抛出NPE

4. 使用场景(极其有限)

测试代码

@Test
fun testUser() {
    val user = createUser()
    val name = user!!.name  // 测试中明确期望非null
    assertEquals("Expected", name)
}

遗留代码互操作

// 与Java互操作时临时使用
val javaValue: String? = javaMethod()
val kotlinValue: String = javaValue!!  // 临时转换

5. 避免使用的原因

  1. 失去空安全优势:失去Kotlin空安全的优势
  2. 运行时崩溃:可能抛出NPE
  3. 调试困难:难以定位问题
  4. 代码质量:表明代码设计有问题

6. 替代方案

不推荐

val user: User? = getUser()
val name = user!!.name  // ❌

推荐方案1:安全调用

val user: User? = getUser()
val name = user?.name ?: "Unknown"  // ✅

推荐方案2:let函数

val user: User? = getUser()
user?.let {
    val name = it.name  // ✅ 安全
    processUser(it)
}

推荐方案3:智能转换

val user: User? = getUser()
if (user != null) {
    val name = user.name  // ✅ 智能转换
    processUser(user)
}

推荐方案4:when表达式

val user: User? = getUser()
when (user) {
    null -> println("No user")
    else -> {
        val name = user.name  // ✅ 智能转换
        processUser(user)
    }
}

7. 最佳实践

  1. 避免使用!!:除非绝对必要
  2. 使用安全操作符?.?:let
  3. 智能转换:利用类型检查
  4. 代码审查:检查!!的使用是否必要
  5. 重构代码:如果经常使用!!,考虑重构

8. 总结

  • 风险:高,可能抛出NPE
  • 使用场景:极其有限,应避免
  • 推荐替代:安全调用、let、智能转换、when

5.13 可空类型的集合处理

答案:

处理可空类型集合的方法:

1. 可空元素集合

val list: List<String?> = listOf("a", null, "c", null, "e")

// 过滤null
val nonNull = list.filterNotNull()  // ["a", "c", "e"]

// 安全处理
list.forEach { item ->
    item?.let { println(it) }
}

2. 可空集合

val list: List<String>? = getList()

// 提供默认值
val items = list ?: emptyList()
val size = items.size

// 安全访问
val first = list?.first()
val size = list?.size ?: 0

3. 可空元素且可空集合

val list: List<String?>? = getOptionalList()

// 处理
val nonNull = list?.filterNotNull() ?: emptyList()

4. 常用操作

filterNotNull()

val list: List<String?> = listOf("a", null, "c")
val nonNull = list.filterNotNull()  // [a, c]

mapNotNull()

val list: List<Int?> = listOf(1, null, 3)
val strings = list.mapNotNull { it?.toString() }  // ["1", "3"]

firstOrNull()

val list: List<String?> = listOf(null, "b", "c")
val first = list.firstOrNull { it != null }  // "b"

5. 实际应用

处理API响应

val users: List<User?>? = getUsers()
val validUsers = users?.filterNotNull() ?: emptyList()
validUsers.forEach { processUser(it) }

数据处理

val data: List<String?> = getData()
val processed = data.mapNotNull { 
    it?.uppercase()?.trim()
}

6. 最佳实践

  1. 使用filterNotNull():过滤null元素
  2. 使用mapNotNull():转换并过滤null
  3. 提供默认值:可空集合使用空列表作为默认值
  4. 安全访问:使用安全调用访问集合

5.14 空安全在Android开发中的应用

答案:

空安全在Android开发中的实际应用:

1. View处理

// findViewById可能返回null
val button: Button? = findViewById(R.id.button)
button?.setOnClickListener {
    // 处理点击
}

// 使用lateinit(确定会初始化)
lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    button = findViewById(R.id.button)  // 确定非null
}

2. Bundle处理

// Bundle值可能为null
val name = intent.getStringExtra("name") ?: "Unknown"
val age = intent.getIntExtra("age", 0)  // 使用默认值

// 安全转换
val user = intent.getParcelableExtra<User>("user")
user?.let { processUser(it) }

3. Fragment参数

// Fragment参数
class DetailFragment : Fragment() {
    private val userId: Int by lazy {
        arguments?.getInt("userId") ?: throw IllegalArgumentException("userId required")
    }
}

4. 生命周期方法

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    
    // savedInstanceState可能为null
    savedInstanceState?.let {
        restoreState(it)
    }
}

5. 网络请求

// 网络响应可能为null
apiService.getUser(userId).enqueue(object : Callback<User> {
    override fun onResponse(call: Call<User>, response: Response<User>) {
        val user = response.body()  // User?
        user?.let {
            updateUI(it)
        } ?: run {
            showError("User not found")
        }
    }
    
    override fun onFailure(call: Call<User>, t: Throwable) {
        showError(t.message ?: "Unknown error")
    }
})

6. 数据库查询

// 查询结果可能为null
val user = database.userDao().getUserById(userId)
user?.let {
    displayUser(it)
} ?: run {
    showEmptyState()
}

7. SharedPreferences

val preferences = getSharedPreferences("settings", Context.MODE_PRIVATE)
val userName = preferences.getString("user_name", null) ?: "Guest"
val isLoggedIn = preferences.getBoolean("is_logged_in", false)

8. LiveData观察

viewModel.userLiveData.observe(this) { user ->
    user?.let {
        updateUI(it)
    } ?: run {
        showLoading()
    }
}

9. 最佳实践

  1. 使用lateinit:View等确定会初始化的对象
  2. 使用安全调用:处理可能为null的返回值
  3. 提供默认值:使用Elvis操作符
  4. 使用let:处理可空对象
  5. 避免!!:不要使用非空断言

5.15 空安全与Java互操作的问题

答案:

Kotlin与Java互操作时的空安全问题:

1. 平台类型(Platform Types)

Java返回的类型在Kotlin中视为平台类型(可空或非空):

// Java方法
public String getName() {
    return name;  // 可能为null
}

// Kotlin调用
val name = javaObject.getName()  // 平台类型String!
// name.length  // ⚠️ 可能NPE,编译不检查

2. 注解处理

使用@Nullable@NonNull注解:

// Java
public @Nullable String getOptionalName() {
    return name;
}

public @NonNull String getName() {
    return name;
}

// Kotlin调用
val optional: String? = javaObject.getOptionalName()  // 可空
val required: String = javaObject.getName()  // 非空

3. 处理Java可空类型

// Java方法可能返回null
fun processJavaString(str: String?) {
    str?.let { println(it) }
}

// 调用Java方法
val result = javaObject.getResult()  // 平台类型
val safe = result ?: defaultValue  // 安全处理

4. 类型映射

Java和Kotlin的类型映射:

Java类型Kotlin类型
StringString!(平台类型)
@Nullable StringString?
@NonNull StringString

5. 最佳实践

  1. 使用注解:Java代码中使用@Nullable@NonNull
  2. 安全处理:Java返回值按可空处理
  3. 尽早转换:Java返回值尽早处理
  4. 测试验证:测试null情况