kotlin面试题-第三部

52 阅读49分钟

第六章:Kotlin 扩展面试题答案

1. 扩展函数

6.1 Kotlin的扩展函数是什么?

答案:

扩展函数(Extension Functions)允许在不修改原有类的情况下,为类添加新函数。

1. 基本概念

扩展函数是Kotlin的强大特性,可以为任何类(包括第三方库的类)添加新函数,而无需修改原始类的源代码。

2. 基本语法

// 为String类添加扩展函数
fun String.removeSpaces(): String {
    return this.replace(" ", "")
}

// 调用
val text = "Hello World"
val result = text.removeSpaces()  // "HelloWorld"

3. 语法说明

fun ReceiverType.functionName(parameters): ReturnType {
    // 函数体
    // this指向接收者对象(可以省略)
}
  • ReceiverType:被扩展的类型(接收者类型)
  • functionName:扩展函数名
  • this:指向接收者对象(隐式参数)

4. 示例

字符串扩展

// 移除空格
fun String.removeSpaces(): String = this.replace(" ", "")

// 首字母大写
fun String.capitalizeFirst(): String {
    return if (isEmpty()) this
    else this[0].uppercase() + substring(1)
}

// 使用
"hello world".removeSpaces()      // "helloworld"
"kotlin".capitalizeFirst()        // "Kotlin"

集合扩展

// 获取第二个元素
fun <T> List<T>.second(): T? = if (size >= 2) this[1] else null

// 使用
val list = listOf(1, 2, 3)
val second = list.second()  // 2

数字扩展

// 平方
fun Int.square(): Int = this * this

// 是否偶数
fun Int.isEven(): Boolean = this % 2 == 0

// 使用
5.square()   // 25
4.isEven()   // true

5. 实现原理

扩展函数在编译时转换为静态方法:

// Kotlin代码
fun String.removeSpaces(): String = this.replace(" ", "")

// 编译后的Java等价代码(伪代码)
public static String removeSpaces(String $this) {
    return $this.replace(" ", "");
}

6. 调用方式

// 方式1:对象调用(推荐)
val text = "Hello World"
val result = text.removeSpaces()

// 方式2:直接调用(不推荐,但可以)
val result = String.removeSpaces("Hello World")

7. 扩展函数的特点

  1. 不修改原类:不需要修改原始类的源代码
  2. 语法简洁:调用方式像成员函数
  3. 类型安全:编译时类型检查
  4. 作用域限制:需要导入才能使用

8. 与成员函数的区别

特性扩展函数成员函数
定义位置类外部类内部
优先级低(成员函数优先)
可见性需要导入类作用域
多态性不支持(静态解析)支持(动态解析)

9. 实际应用

工具函数

// 日期格式化
fun Date.format(pattern: String): String {
    return SimpleDateFormat(pattern).format(this)
}

// 使用
val date = Date()
date.format("yyyy-MM-dd")  // "2024-01-01"

第三方库扩展

// 为第三方库类添加函数
fun View.hide() {
    this.visibility = View.GONE
}

fun View.show() {
    this.visibility = View.VISIBLE
}

// 使用
button.hide()
button.show()

10. 最佳实践

  1. 合理使用:不要过度使用扩展函数
  2. 命名清晰:使用有意义的函数名
  3. 避免冲突:注意与成员函数的优先级
  4. 文档说明:为扩展函数添加文档注释
  5. 组织代码:按功能组织扩展函数

6.2 扩展函数的实现原理是什么?

答案:

扩展函数的实现原理:

1. 编译时转换

扩展函数在编译时转换为静态方法,不是真正的成员函数:

// Kotlin代码
fun String.removeSpaces(): String {
    return this.replace(" ", "")
}

// 编译后的字节码(反编译为Java伪代码)
public static final String removeSpaces(String $this) {
    return $this.replace(" ", "");
}

2. 调用转换

扩展函数调用转换为静态方法调用:

// Kotlin代码
val text = "Hello"
val result = text.removeSpaces()

// 编译后(伪代码)
String text = "Hello";
String result = StringKt.removeSpaces(text);  // 静态方法调用

3. 参数传递

接收者对象作为第一个参数传递:

// Kotlin代码
fun Int.add(other: Int): Int {
    return this + other
}

// 编译后(伪代码)
public static final int add(int $this, int other) {
    return $this + other;
}

// 调用
5.add(3)  // 转换为 add(5, 3)

4. 类文件组织

扩展函数通常放在对应的工具类文件中:

// StringExtensions.kt
fun String.removeSpaces(): String { }

// 编译后生成 StringExtensionsKt 类

5. 泛型扩展

泛型扩展函数的实现:

// Kotlin代码
fun <T> List<T>.second(): T? {
    return if (size >= 2) this[1] else null
}

// 编译后(伪代码)
public static <T> T second(List<T> $this) {
    return $this.size() >= 2 ? $this.get(1) : null;
}

6. 可见性

扩展函数的可见性取决于定义位置:

// 私有扩展(在同一文件内可见)
private fun String.privateExtension() { }

// 内部扩展(在同一模块内可见)
internal fun String.internalExtension() { }

// 公共扩展(需要导入)
public fun String.publicExtension() { }

7. 性能影响

扩展函数的性能:

  1. 调用开销:静态方法调用,开销与普通函数相同
  2. 无虚函数表:不参与虚函数调用
  3. 编译时优化:可以被内联优化

8. 与成员函数的性能对比

特性扩展函数成员函数
调用方式静态方法调用虚函数调用
性能相同相同(非虚)
内联优化支持支持
虚函数开销可能有

9. 反编译示例

使用工具反编译Kotlin代码可以看到:

// Kotlin源码
fun String.removeSpaces(): String = this.replace(" ", "")

// 反编译后(类似)
public static final String removeSpaces(@NotNull String $this) {
    Intrinsics.checkNotNullParameter($this, "$this");
    String result = StringsKt.replace$default(
        $this, " ", "", false, 4, (Object)null
    );
    return result;
}

10. 总结

  • 编译时转换:扩展函数编译为静态方法
  • 接收者参数:接收者对象作为第一个参数
  • 性能相同:与成员函数性能相同
  • 静态解析:不支持多态(静态解析)

6.3 扩展函数和成员函数的区别是什么?

答案:

扩展函数和成员函数的主要区别:

1. 定义位置

扩展函数

// 定义在类外部
fun String.removeSpaces(): String {
    return this.replace(" ", "")
}

class String {  // 原有类,无需修改
    // ...
}

成员函数

// 定义在类内部
class String {
    fun removeSpaces(): String {
        return this.replace(" ", "")
    }
}

2. 优先级

成员函数优先级高于扩展函数:

class User {
    fun getName(): String = "Member"  // 成员函数
}

fun User.getName(): String = "Extension"  // 扩展函数

val user = User()
println(user.getName())  // "Member"(成员函数优先)

3. 可见性

扩展函数

// 需要显式导入
import com.example.StringExtensions.removeSpaces

// 或使用通配符导入
import com.example.StringExtensions.*

val text = "Hello"
text.removeSpaces()

成员函数

// 在类作用域内可见
class User {
    fun getName(): String = "User"
}

val user = User()
user.getName()  // 直接可用

4. 多态性

扩展函数(静态解析)

open class Animal
class Dog : Animal()

fun Animal.speak() = "Animal sound"
fun Dog.speak() = "Woof"

val animal: Animal = Dog()
println(animal.speak())  // "Animal sound"(静态解析,看声明类型)

成员函数(动态解析)

open class Animal {
    open fun speak() = "Animal sound"
}

class Dog : Animal() {
    override fun speak() = "Woof"
}

val animal: Animal = Dog()
println(animal.speak())  // "Woof"(动态解析,看实际类型)

5. 继承

扩展函数

// 扩展函数不能被重写
open class Animal
class Dog : Animal()

fun Animal.speak() = "Animal sound"
// fun Dog.speak() = "Woof"  // 不是重写,是新函数

val dog: Animal = Dog()
dog.speak()  // "Animal sound"

成员函数

// 成员函数可以重写
open class Animal {
    open fun speak() = "Animal sound"
}

class Dog : Animal() {
    override fun speak() = "Woof"
}

val dog: Animal = Dog()
dog.speak()  // "Woof"

6. 访问私有成员

扩展函数

class User {
    private val secret = "Secret"
}

fun User.getSecret(): String {
    // return this.secret  // ❌ 编译错误,不能访问私有成员
    return "Cannot access"
}

成员函数

class User {
    private val secret = "Secret"
    
    fun getSecret(): String {
        return this.secret  // ✅ 可以访问私有成员
    }
}

7. 总结对比

特性扩展函数成员函数
定义位置类外部类内部
优先级
可见性需要导入类作用域
多态性不支持(静态解析)支持(动态解析)
重写不支持支持
私有访问不能访问可以访问
使用场景为现有类添加功能类的基本功能

8. 使用建议

使用扩展函数的场景:

  1. 为第三方库类添加功能
  2. 为现有类添加工具函数
  3. 不想修改原始类时
  4. 按功能组织代码时

使用成员函数的场景:

  1. 类的核心功能
  2. 需要访问私有成员时
  3. 需要多态时
  4. 需要重写时

9. 最佳实践

  1. 优先成员函数:类的核心功能使用成员函数
  2. 合理使用扩展:为现有类添加功能时使用扩展函数
  3. 避免冲突:注意与成员函数的优先级
  4. 明确意图:扩展函数用于添加功能,不是修改类

6.4 扩展函数的优先级是什么?

答案:

扩展函数的优先级规则:

1. 基本规则

成员函数优先级 > 扩展函数优先级

class User {
    fun getName(): String = "Member Function"  // 成员函数
}

fun User.getName(): String = "Extension Function"  // 扩展函数

val user = User()
println(user.getName())  // "Member Function"(成员函数优先)

2. 同类型扩展优先级

同类型的扩展函数,后定义的优先:

// 文件1
fun String.removeSpaces(): String = "Extension 1"

// 文件2(后导入)
fun String.removeSpaces(): String = "Extension 2"

// 如果都导入,会产生冲突,需要明确指定

3. 导入优先级

明确导入优先于通配符导入:

// 明确导入
import com.example.StringExtensions.removeSpaces

// 通配符导入(如果冲突,明确导入优先)
import com.example.OtherExtensions.*

4. 同一文件内

同一文件内的扩展函数,后定义的优先(如果有冲突):

fun String.removeSpaces(): String = "First"

fun String.removeSpaces(): String = "Second"  // ❌ 编译错误,函数重复

// 如果参数不同,可以重载
fun String.removeSpaces(): String = "No param"
fun String.removeSpaces(separator: String): String = "With param"  // ✅ 可以,重载

5. 扩展函数 vs 扩展属性

如果扩展函数和扩展属性同名但类型不同,会产生冲突:

// 扩展函数
fun String.length(): Int = this.length  // ❌ 冲突,String已有length属性

// 应该使用不同名称
fun String.customLength(): Int = this.length  // ✅

6. 实际示例

成员函数优先

class Calculator {
    fun calculate(x: Int, y: Int): Int = x + y  // 成员函数
}

fun Calculator.calculate(x: Int, y: Int): Int = x * y  // 扩展函数

val calc = Calculator()
println(calc.calculate(2, 3))  // 5(成员函数优先,执行加法)

扩展函数访问成员

class User {
    fun getName(): String = "Member"
}

fun User.getDisplayName(): String {
    return "Display: ${this.getName()}"  // ✅ 可以调用成员函数
}

7. 冲突解决

导入冲突

// 如果两个包有同名扩展函数,需要明确指定
import com.example.ext1.removeSpaces as removeSpaces1
import com.example.ext2.removeSpaces as removeSpaces2

val text = "Hello"
text.removeSpaces1()  // 使用第一个
text.removeSpaces2()  // 使用第二个

8. 最佳实践

  1. 避免命名冲突:使用有意义的、唯一的函数名
  2. 明确导入:使用明确导入避免冲突
  3. 避免覆盖成员:不要定义与成员函数同名的扩展函数
  4. 文档说明:说明扩展函数的作用和优先级

6.5 扩展函数的实际应用场景有哪些?

答案:

扩展函数的实际应用场景:

1. 为第三方库类添加功能

// 为Android View添加扩展
fun View.hide() {
    this.visibility = View.GONE
}

fun View.show() {
    this.visibility = View.VISIBLE
}

fun View.isVisible(): Boolean {
    return this.visibility == View.VISIBLE
}

// 使用
button.hide()
button.show()
if (button.isVisible()) { }

2. 字符串工具函数

// 首字母大写
fun String.capitalizeFirst(): String {
    return if (isEmpty()) this
    else this[0].uppercase() + substring(1)
}

// 移除空格
fun String.removeSpaces(): String = this.replace(" ", "")

// 验证邮箱
fun String.isEmail(): Boolean {
    return this.contains("@") && this.contains(".")
}

// 使用
"hello".capitalizeFirst()      // "Hello"
"hello world".removeSpaces()   // "helloworld"
"test@example.com".isEmail()   // true

3. 集合工具函数

// 获取第二个元素
fun <T> List<T>.second(): T? = if (size >= 2) this[1] else null

// 获取倒数第二个元素
fun <T> List<T>.penultimate(): T? = if (size >= 2) this[size - 2] else null

// 安全的first和last
fun <T> List<T>.firstOrNull(): T? = firstOrNull()
fun <T> List<T>.lastOrNull(): T? = lastOrNull()

// 使用
val list = listOf(1, 2, 3)
list.second()       // 2
list.penultimate()  // 2

4. 数字工具函数

// 平方
fun Int.square(): Int = this * this

// 是否偶数/奇数
fun Int.isEven(): Boolean = this % 2 == 0
fun Int.isOdd(): Boolean = this % 2 != 0

// 转百分比
fun Double.toPercentage(): String = "${this * 100}%"

// 使用
5.square()        // 25
4.isEven()        // true
0.95.toPercentage()  // "95.0%"

5. 日期时间扩展

// 日期格式化
fun Date.format(pattern: String): String {
    return SimpleDateFormat(pattern).format(this)
}

// 是否是今天
fun Date.isToday(): Boolean {
    val calendar = Calendar.getInstance()
    val today = calendar.get(Calendar.DAY_OF_YEAR)
    calendar.time = this
    return calendar.get(Calendar.DAY_OF_YEAR) == today
}

// 使用
val date = Date()
date.format("yyyy-MM-dd")  // "2024-01-01"
date.isToday()             // true/false

6. Android开发扩展

// Context扩展
fun Context.toast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

fun Context.dpToPx(dp: Int): Int {
    return (dp * resources.displayMetrics.density).toInt()
}

// Fragment扩展
fun Fragment.toast(message: String) {
    context?.toast(message)
}

// 使用
context.toast("Hello")
val pixels = context.dpToPx(16)

7. 网络请求扩展

// Retrofit扩展
fun <T> Call<T>.executeOrNull(): T? {
    return try {
        execute().body()
    } catch (e: Exception) {
        null
    }
}

// 使用
val user = apiService.getUser(id).executeOrNull()

8. JSON处理扩展

// Gson扩展
fun <T> String.fromJson(clazz: Class<T>): T? {
    return try {
        Gson().fromJson(this, clazz)
    } catch (e: Exception) {
        null
    }
}

fun Any.toJson(): String {
    return Gson().toJson(this)
}

// 使用
val json = """{"name":"Kotlin"}"""
val user = json.fromJson(User::class.java)
val jsonString = user.toJson()

9. 文件操作扩展

// File扩展
fun File.readTextOrNull(): String? {
    return try {
        readText()
    } catch (e: Exception) {
        null
    }
}

fun File.writeTextSafe(text: String): Boolean {
    return try {
        writeText(text)
        true
    } catch (e: Exception) {
        false
    }
}

10. 最佳实践

  1. 按功能组织:将相关扩展函数放在同一文件
  2. 命名规范:使用有意义的函数名
  3. 避免冲突:避免与成员函数冲突
  4. 文档说明:为扩展函数添加文档
  5. 合理使用:不要过度使用扩展函数

2. 扩展属性

6.6 Kotlin的扩展属性是什么?

答案:

扩展属性(Extension Properties)允许在不修改原有类的情况下,为类添加新属性。

1. 基本概念

扩展属性与扩展函数类似,但用于属性而不是方法。扩展属性不能存储状态,只能提供getter和setter。

2. 基本语法

// 只读扩展属性
val String.lastChar: Char
    get() = this[length - 1]

// 可变扩展属性(需要可变的接收者类型)
var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value) = setCharAt(length - 1, value)

// 使用
val str = "Kotlin"
val last = str.lastChar  // 'n'

val sb = StringBuilder("Hello")
sb.lastChar = '!'  // "Hell!"

3. 只读扩展属性

// 只读属性(val)
val String.isNotEmpty: Boolean
    get() = this.isNotEmpty()

val Int.isEven: Boolean
    get() = this % 2 == 0

// 使用
"hello".isNotEmpty  // true
4.isEven           // true

4. 可变扩展属性

可变扩展属性需要可变的接收者类型(如StringBuilderMutableList等):

// 可变属性(var)
var StringBuilder.lastChar: Char
    get() = if (isNotEmpty()) get(length - 1) else throw IndexOutOfBoundsException()
    set(value) {
        if (isNotEmpty()) {
            setCharAt(length - 1, value)
        } else {
            throw IndexOutOfBoundsException()
        }
    }

// 使用
val sb = StringBuilder("Hello")
sb.lastChar = '!'
println(sb)  // "Hell!"

5. 实现原理

扩展属性编译为静态方法:

// Kotlin代码
val String.lastChar: Char
    get() = this[length - 1]

// 编译后(伪代码)
public static final char getLastChar(String $this) {
    return $this.charAt($this.length() - 1);
}

6. 限制

扩展属性不能存储状态:

// ❌ 不能这样做
var String.customField: String  // 错误:扩展属性不能存储状态
    get() = ???
    set(value) = ???

// ✅ 只能通过计算或访问现有状态
val String.lastChar: Char
    get() = this[length - 1]

7. 实际应用

字符串扩展属性

val String.firstChar: Char
    get() = this[0]

val String.lastChar: Char
    get() = this[length - 1]

val String.isEmail: Boolean
    get() = contains("@") && contains(".")

// 使用
"Kotlin".firstChar  // 'K'
"Kotlin".lastChar   // 'n'
"test@example.com".isEmail  // true

数字扩展属性

val Int.isEven: Boolean
    get() = this % 2 == 0

val Int.isOdd: Boolean
    get() = this % 2 != 0

val Int.squared: Int
    get() = this * this

// 使用
4.isEven   // true
5.isOdd    // true
5.squared  // 25

集合扩展属性

val <T> List<T>.second: T?
    get() = if (size >= 2) this[1] else null

val <T> List<T>.penultimate: T?
    get() = if (size >= 2) this[size - 2] else null

// 使用
val list = listOf(1, 2, 3)
list.second       // 2
list.penultimate  // 2

8. 与扩展函数对比

// 扩展属性
val String.lastChar: Char
    get() = this[length - 1]

// 扩展函数(等价)
fun String.getLastChar(): Char = this[length - 1]

// 使用
"Kotlin".lastChar      // 属性方式(更简洁)
"Kotlin".getLastChar() // 函数方式

9. 最佳实践

  1. 只读优先:优先使用只读扩展属性
  2. 计算属性:用于计算值而不是存储值
  3. 语义清晰:属性名应该表示"是什么"而不是"做什么"
  4. 避免副作用:getter不应该有副作用

6.7 扩展属性的实现原理是什么?

答案:

扩展属性的实现原理:

1. 编译时转换

扩展属性在编译时转换为静态方法:

// Kotlin代码
val String.lastChar: Char
    get() = this[length - 1]

// 编译后的Java等价代码(伪代码)
public static final char getLastChar(String $this) {
    return $this.charAt($this.length() - 1);
}

2. Getter转换

// Kotlin代码
val String.lastChar: Char
    get() = this[length - 1]

// 编译后
// getter转换为静态方法 getLastChar(String)

3. Setter转换

// Kotlin代码
var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value) = setCharAt(length - 1, value)

// 编译后
// getter转换为 getLastChar(StringBuilder)
// setter转换为 setLastChar(StringBuilder, char)

4. 调用转换

// Kotlin代码
val str = "Kotlin"
val last = str.lastChar

// 编译后(伪代码)
String str = "Kotlin";
char last = StringExtensionsKt.getLastChar(str);

5. 性能

扩展属性的性能与扩展函数相同:

  1. 调用开销:静态方法调用,开销相同
  2. 无存储开销:不能存储状态,无额外内存
  3. 编译时优化:可以被内联优化

6. 总结

  • 编译时转换:扩展属性编译为静态方法
  • 无状态存储:不能存储状态,只能计算
  • 性能相同:与扩展函数性能相同

3. 扩展的作用域

6.8 扩展函数的作用域是什么?

答案:

扩展函数的作用域规则:

1. 基本作用域

扩展函数的作用域取决于定义位置:

// 顶层扩展(公共,需要导入)
fun String.removeSpaces(): String { }

// 类内扩展(类作用域)
class Utils {
    fun String.helper(): String { }
}

// 私有扩展(同一文件内)
private fun String.privateHelper(): String { }

2. 顶层扩展

顶层扩展函数需要导入:

// 文件:StringExtensions.kt
package com.example.extensions

fun String.removeSpaces(): String { }

// 使用(需要导入)
package com.example.app

import com.example.extensions.removeSpaces

fun main() {
    "hello world".removeSpaces()
}

3. 包内扩展

同一包内的扩展可以直接使用:

// 文件1
package com.example.utils

fun String.helper(): String { }

// 文件2(同一包)
package com.example.utils

fun main() {
    "hello".helper()  // ✅ 可以直接使用
}

4. 类内扩展

类内的扩展函数在类作用域内可见:

class Utils {
    fun String.helper(): String {
        return this.uppercase()
    }
    
    fun process() {
        "hello".helper()  // ✅ 类内可以使用
    }
}

fun main() {
    // "hello".helper()  // ❌ 类外不能使用
    Utils().process()  // ✅ 通过类方法使用
}

5. 私有扩展

私有扩展函数只在同一文件内可见:

// 文件内
private fun String.privateHelper(): String { }

fun main() {
    "hello".privateHelper()  // ✅ 同一文件内可以使用
}

// 其他文件
// "hello".privateHelper()  // ❌ 不能使用

6. 可见性修饰符

// public(默认)
fun String.publicExt(): String { }

// private
private fun String.privateExt(): String { }

// internal
internal fun String.internalExt(): String { }

// protected(不能用于顶层扩展)

7. 导入规则

// 明确导入
import com.example.extensions.removeSpaces

// 通配符导入
import com.example.extensions.*

// 别名导入(避免冲突)
import com.example.ext1.removeSpaces as removeSpaces1
import com.example.ext2.removeSpaces as removeSpaces2

8. 最佳实践

  1. 组织代码:将相关扩展函数放在同一文件
  2. 明确导入:使用明确导入避免冲突
  3. 可见性:合理使用可见性修饰符
  4. 文档说明:说明扩展函数的位置和导入方式

6.9 如何在不同包中使用扩展函数?

答案:

在不同包中使用扩展函数的方法:

1. 导入扩展函数

扩展函数需要显式导入才能使用:

// 文件1:com/example/utils/StringExtensions.kt
package com.example.utils

fun String.removeSpaces(): String {
    return this.replace(" ", "")
}

// 文件2:com/example/app/Main.kt
package com.example.app

import com.example.utils.removeSpaces  // ✅ 导入扩展函数

fun main() {
    "hello world".removeSpaces()  // 可以使用
}

2. 通配符导入

使用通配符导入所有扩展函数:

package com.example.app

import com.example.utils.*  // 导入所有扩展函数

fun main() {
    "hello world".removeSpaces()
    "kotlin".capitalizeFirst()
}

3. 别名导入

避免命名冲突时使用别名:

// 两个包有同名扩展函数
import com.example.ext1.removeSpaces as removeSpaces1
import com.example.ext2.removeSpaces as removeSpaces2

fun main() {
    "hello".removeSpaces1()  // 使用第一个
    "hello".removeSpaces2()  // 使用第二个
}

4. 包级导入

// 导入整个包的扩展函数
import com.example.utils.*

5. 实际应用

工具类组织

// 文件:utils/StringExtensions.kt
package com.example.utils

fun String.removeSpaces(): String { }
fun String.capitalizeFirst(): String { }

// 文件:utils/IntExtensions.kt
package com.example.utils

fun Int.square(): Int { }
fun Int.isEven(): Boolean { }

// 使用
package com.example.app

import com.example.utils.*  // 导入所有扩展

fun main() {
    "hello".removeSpaces()
    5.square()
}

6. 最佳实践

  1. 明确导入:使用明确导入提高可读性
  2. 避免冲突:使用别名解决冲突
  3. 组织代码:按功能组织扩展函数

4. 作用域函数

6.10 Kotlin的作用域函数(Scope Functions)是什么?

答案:

作用域函数(Scope Functions)是Kotlin标准库提供的五个函数(let、run、with、apply、also),用于在对象上下文中执行代码块。

1. 基本概念

作用域函数提供了一种简洁的方式来处理对象,在代码块内可以访问对象,而不需要重复写对象名。

2. 五个作用域函数

  1. let:接受对象,返回Lambda结果
  2. run:接受对象,返回Lambda结果(使用this)
  3. with:非扩展函数,返回Lambda结果(使用this)
  4. apply:接受对象,返回对象本身
  5. also:接受对象,返回对象本身(使用it)

3. 基本对比

函数对象引用返回值使用场景
letitLambda结果空安全调用、转换
runthisLambda结果对象配置、计算
withthisLambda结果非空对象配置
applythis对象本身对象初始化
alsoit对象本身附加操作

4. 示例对比

val person = Person().apply {
    name = "Kotlin"
    age = 30
}

val result = person.let {
    it.name  // 返回name
}

person.also {
    println(it.name)  // 打印但返回person
}

5. 选择指南

  • 需要返回值:使用letrun
  • 不需要返回值:使用applyalso
  • 空安全:使用let
  • 对象初始化:使用apply
  • 附加操作:使用also
  • 配置对象:使用runwith

6. 最佳实践

  1. 明确目的:根据需求选择合适的函数
  2. 避免嵌套:不要过度嵌套作用域函数
  3. 保持简洁:作用域函数应该让代码更简洁
  4. 可读性优先:如果让代码更难读,考虑使用普通代码

6.11 let、run、with、apply、also的区别是什么?

答案:

五个作用域函数的详细区别:

1. let

特点:

  • 对象引用:it
  • 返回值:Lambda结果
  • 使用场景:空安全调用、转换、链式调用

示例:

val person: Person? = getPerson()
val name = person?.let {
    println(it.name)  // it指向person
    it.name.uppercase()  // 返回转换后的值
} ?: "Unknown"

2. run

特点:

  • 对象引用:this(可省略)
  • 返回值:Lambda结果
  • 使用场景:对象配置、计算、链式调用

示例:

val result = person.run {
    name = "Kotlin"  // this.name,可省略this
    age = 30
    "${this.name} is ${this.age} years old"  // 返回字符串
}

3. with

特点:

  • 对象引用:this(可省略)
  • 返回值:Lambda结果
  • 使用场景:非空对象配置(非扩展函数)

示例:

val person = Person()
val result = with(person) {
    name = "Kotlin"
    age = 30
    "${name} is ${age} years old"  // 返回字符串
}

4. apply

特点:

  • 对象引用:this(可省略)
  • 返回值:对象本身
  • 使用场景:对象初始化、配置

示例:

val person = Person().apply {
    name = "Kotlin"  // this.name
    age = 30         // this.age
    email = "kotlin@example.com"
}  // 返回person对象

5. also

特点:

  • 对象引用:it
  • 返回值:对象本身
  • 使用场景:附加操作、日志、调试

示例:

val person = Person().also {
    println("Creating person: ${it.name}")  // 打印日志
    validatePerson(it)  // 验证
}  // 返回person对象

6. 详细对比表

函数对象引用返回值空安全使用场景
letitLambda结果支持空安全调用、转换
runthisLambda结果支持对象配置、计算
withthisLambda结果不支持非空对象配置
applythis对象本身支持对象初始化
alsoit对象本身支持附加操作、日志

7. 实际应用对比

对象初始化

// apply
val person = Person().apply {
    name = "Kotlin"
    age = 30
}

// 普通写法
val person = Person()
person.name = "Kotlin"
person.age = 30

空安全处理

// let
val name = person?.let {
    it.name.uppercase()
} ?: "Unknown"

// 普通写法
val name = if (person != null) {
    person.name.uppercase()
} else {
    "Unknown"
}

链式调用

// let链式
val result = person?.let {
    it.name
}?.let {
    it.uppercase()
}?.let {
    "Hello, $it"
}

// apply链式
val person = Person().apply {
    name = "Kotlin"
}.apply {
    age = 30
}.also {
    println(it)
}

8. 最佳实践选择

使用let:

  • 空安全调用
  • 值转换
  • 链式调用

使用run:

  • 对象配置
  • 计算返回值
  • 需要this引用

使用with:

  • 非空对象配置
  • 多个属性设置

使用apply:

  • 对象初始化
  • Builder模式
  • 配置后返回对象

使用also:

  • 附加操作
  • 日志记录
  • 验证检查

6.12 什么时候使用let?

答案:

let函数的使用场景:

1. 空安全调用

最常用的场景是空安全调用:

val person: Person? = getPerson()
person?.let {
    println(it.name)
    processPerson(it)
}

// 等价于
if (person != null) {
    println(person.name)
    processPerson(person)
}

2. 值转换

将对象转换为另一个值:

val name = person?.let {
    it.name.uppercase()
} ?: "Unknown"

val length = string?.let {
    it.length
} ?: 0

3. 链式调用

进行链式转换:

val result = person?.let {
    it.name
}?.let {
    it.uppercase()
}?.let {
    "Hello, $it"
} ?: "Unknown"

4. 作用域隔离

创建临时作用域,避免变量污染:

val result = calculate().let { value ->
    // value只在let块内可见
    process(value)
    transform(value)
}  // value在这里不可见

5. 实际应用

空安全处理

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

转换处理

val input: String? = readLine()
val number = input?.let {
    it.toIntOrNull()
} ?: 0

链式处理

val result = data?.let {
    parseData(it)
}?.let {
    validateData(it)
}?.let {
    transformData(it)
}

6. 最佳实践

  1. 空安全时使用:处理可空对象时优先使用
  2. 值转换时使用:需要转换值时使用
  3. 链式调用时使用:需要链式转换时使用
  4. 避免嵌套过深:不要过度嵌套let

6.13 什么时候使用run?

答案:

run函数的使用场景:

1. 对象配置和计算

配置对象并返回计算结果:

val result = person.run {
    name = "Kotlin"
    age = 30
    "${name} is ${age} years old"  // 返回计算结果
}

2. 需要使用this

需要访问对象成员时,使用this更自然:

val result = person.run {
    // this指向person,可以省略
    this.name = "Kotlin"
    name = "Kotlin"  // 等价,this可省略
    calculateDescription()  // 调用成员方法
}

3. 扩展函数形式

作为扩展函数使用:

val person: Person? = getPerson()
val result = person?.run {
    name = "Kotlin"
    age = 30
    "${name} is ${age}"
} ?: "Unknown"

4. 非扩展函数形式

直接调用Lambda:

val result = run {
    val a = 1
    val b = 2
    a + b  // 返回计算结果
}

5. 实际应用

对象配置

val dialog = Dialog().run {
    setTitle("Title")
    setMessage("Message")
    setPositiveButton("OK", null)
    show()
    this  // 返回dialog
}

计算返回

val description = person.run {
    val fullName = "$firstName $lastName"
    "Name: $fullName, Age: $age"
}

6. 与let对比

// run(使用this)
val result = person.run {
    name = "Kotlin"  // this.name
    age = 30
    "${name} is ${age}"
}

// let(使用it)
val result = person?.let {
    it.name = "Kotlin"
    it.age = 30
    "${it.name} is ${it.age}"
}

7. 最佳实践

  1. 配置对象:需要配置多个属性时使用
  2. 需要this:使用this引用更自然时
  3. 计算返回:需要计算并返回结果时
  4. 扩展形式:可空对象时使用扩展形式

6.14 什么时候使用with?

答案:

with函数的使用场景:

1. 非空对象配置

配置非空对象时使用:

val person = Person()  // 非空对象
val result = with(person) {
    name = "Kotlin"  // this.name,this可省略
    age = 30
    "${name} is ${age} years old"  // 返回结果
}

2. 非扩展函数

with不是扩展函数,第一个参数是对象:

with(person) {
    // 配置person
    name = "Kotlin"
    age = 30
}

3. 不使用返回值

如果不需要返回值,可以使用Unit

with(person) {
    name = "Kotlin"
    age = 30
    // 不返回,只是配置
}

4. 与run对比

// with(非扩展)
val result = with(person) {
    name = "Kotlin"
    age = 30
    "${name} is ${age}"
}

// run(扩展)
val result = person.run {
    name = "Kotlin"
    age = 30
    "${name} is ${age}"
}

// 两者功能相同,但with需要明确对象,run可以链式调用

5. 实际应用

配置对象

val builder = StringBuilder()
with(builder) {
    append("Line 1\n")
    append("Line 2\n")
    append("Line 3\n")
}
val text = builder.toString()

初始化对象

val view = View(context)
with(view) {
    layoutParams = params
    visibility = View.VISIBLE
    setOnClickListener { }
}

6. 最佳实践

  1. 非空对象:确定对象不为null时使用
  2. 多个属性:需要配置多个属性时使用
  3. 明确对象:对象名明确时使用with更清晰
  4. 避免空安全:需要空安全时使用run而不是with

6.15 什么时候使用apply?

答案:

apply函数的使用场景:

1. 对象初始化

初始化对象并配置属性:

val person = Person().apply {
    name = "Kotlin"  // this.name
    age = 30         // this.age
    email = "kotlin@example.com"
}  // 返回person对象

2. Builder模式

实现Builder模式:

val dialog = AlertDialog.Builder(context)
    .apply {
        setTitle("Title")
        setMessage("Message")
        setPositiveButton("OK", null)
    }
    .create()

3. 链式调用

链式配置对象:

val person = Person()
    .apply { name = "Kotlin" }
    .apply { age = 30 }
    .apply { email = "kotlin@example.com" }

4. 返回对象本身

apply总是返回对象本身,适合链式调用:

val file = File("data.txt").apply {
    writeText("content")
    setReadable(true)
}  // 返回file对象

5. 实际应用

Android View初始化

val button = Button(context).apply {
    text = "Click Me"
    textSize = 16f
    setOnClickListener { 
        // 处理点击
    }
}

数据类初始化

val user = User().apply {
    name = "Kotlin"
    age = 30
    email = "kotlin@example.com"
}

集合初始化

val list = mutableListOf<String>().apply {
    add("Item 1")
    add("Item 2")
    add("Item 3")
}

6. 与also对比

// apply(返回对象,使用this)
val person = Person().apply {
    name = "Kotlin"  // this.name
    age = 30
}  // 返回person

// also(返回对象,使用it)
val person = Person().also {
    it.name = "Kotlin"
    it.age = 30
}  // 返回person

7. 最佳实践

  1. 对象初始化:创建并配置对象时使用
  2. Builder模式:实现Builder模式时使用
  3. 链式配置:需要链式配置时使用
  4. 需要this:使用this引用更自然时

6.16 什么时候使用also?

答案:

also函数的使用场景:

1. 附加操作

执行附加操作但返回原对象:

val person = Person().also {
    println("Creating person: ${it.name}")  // 日志
    validatePerson(it)  // 验证
    logCreation(it)  // 记录
}  // 返回person对象

2. 日志记录

记录对象状态:

val result = calculate().also {
    println("Calculated value: $it")
    logger.log(it)
}

3. 调试

调试时查看对象状态:

val data = processData().also {
    println("Debug: $it")
    debugPrint(it)
}

4. 副作用操作

执行副作用操作但需要原对象:

val file = createFile().also {
    it.setReadable(true)
    it.setWritable(true)
    logFileCreation(it)
}

5. 实际应用

验证和日志

val user = createUser().also {
    validateUser(it)
    logUserCreation(it)
    sendWelcomeEmail(it.email)
}

链式调用中的中间操作

val result = data
    .process()
    .also { println("Processed: $it") }
    .transform()
    .also { println("Transformed: $it") }
    .finalize()

6. 与apply对比

// apply(使用this,返回对象)
val person = Person().apply {
    name = "Kotlin"  // this.name
    age = 30
}

// also(使用it,返回对象)
val person = Person().also {
    it.name = "Kotlin"
    it.age = 30
}

// also更适合附加操作
val person = Person().also {
    validate(it)
    log(it)
}

7. 最佳实践

  1. 附加操作:需要执行附加操作时使用
  2. 日志调试:记录或调试时使用
  3. 副作用操作:需要副作用但保留原对象时使用
  4. 使用it:使用it引用对象时

6.17 作用域函数的返回值区别

答案:

作用域函数返回值类型的区别:

1. 返回Lambda结果

let、run、with返回Lambda表达式的结果:

// let
val result: String = person?.let {
    it.name.uppercase()  // 返回String
} ?: "Unknown"

// run
val result: String = person.run {
    "${name} is ${age}"  // 返回String
}

// with
val result: String = with(person) {
    "${name} is ${age}"  // 返回String
}

2. 返回对象本身

apply、also返回对象本身:

// apply
val person: Person = Person().apply {
    name = "Kotlin"  // 配置对象
}  // 返回Person对象

// also
val person: Person = Person().also {
    it.name = "Kotlin"  // 附加操作
}  // 返回Person对象

3. 类型对比

函数返回值类型示例
letLambda结果类型String?Int?
runLambda结果类型PersonString
withLambda结果类型PersonString
apply对象本身类型PersonPerson
also对象本身类型PersonPerson

4. 实际应用

返回结果(let/run/with)

// 转换类型
val name: String? = person?.let { it.name }

// 计算值
val description: String = person.run {
    "${name} is ${age}"
}

返回对象(apply/also)

// 链式调用
val person = Person()
    .apply { name = "Kotlin" }
    .apply { age = 30 }
    .also { println(it) }
// person类型仍然是Person

5. 最佳实践

  1. 需要转换:使用let/run/with返回转换结果
  2. 需要对象:使用apply/also返回对象本身
  3. 链式调用:根据返回值类型选择
  4. 类型明确:确保返回值类型符合预期

6.18 作用域函数的上下文对象(this、it)区别

答案:

作用域函数中thisit的区别:

1. this(隐式引用)

run、with、apply使用this引用对象,可以省略:

// run
person.run {
    name = "Kotlin"  // this.name,this可省略
    age = 30         // this.age
}

// with
with(person) {
    name = "Kotlin"  // this.name
    age = 30
}

// apply
Person().apply {
    name = "Kotlin"  // this.name
    age = 30
}

2. it(显式引用)

let、also使用it引用对象,不能省略:

// let
person?.let {
    it.name = "Kotlin"  // it必须写
    it.age = 30
}

// also
Person().also {
    it.name = "Kotlin"  // it必须写
    it.age = 30
}

3. 区别对比

函数对象引用是否可省略示例
letitit.name
runthisnamethis.name
withthisnamethis.name
applythisnamethis.name
alsoitit.name

4. 实际影响

使用this的优势:

  • 代码更简洁(可省略)
  • 更像成员函数调用
  • 适合配置对象

使用it的优势:

  • 明确表示对象引用
  • 避免与外部作用域混淆
  • 适合链式调用

5. 实际应用

this使用(更简洁)

person.run {
    name = "Kotlin"    // 简洁
    age = 30
    email = "kotlin@example.com"
}

it使用(更明确)

person?.let {
    it.name = "Kotlin"      // 明确
    it.age = 30
    it.email = "kotlin@example.com"
}

6. 最佳实践

  1. this:配置对象时使用,代码更简洁
  2. it:需要明确引用时使用,避免混淆
  3. 一致性:保持代码风格一致
  4. 可读性:优先考虑可读性

6.19 作用域函数的选择指南

答案:

选择合适作用域函数的指南:

1. 决策树

需要返回值?
├─ 是 → 对象为null?
│      ├─ 可能 → 使用let
│      └─ 否 → 需要this?
│             ├─ 是 → 使用run或with
│             └─ 否 → 使用let
└─ 否 → 需要this?
       ├─ 是 → 使用apply
       └─ 否 → 使用also

2. 快速参考

需求推荐函数示例
空安全调用letobj?.let { }
值转换letobj?.let { transform(it) }
对象配置(返回结果)runobj.run { result }
对象配置(非空)withwith(obj) { }
对象初始化applyObj().apply { }
附加操作alsoobj.also { log(it) }

3. 常见场景

场景1:空安全调用

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

场景2:对象初始化

// ✅ 使用apply
val person = Person().apply {
    name = "Kotlin"
    age = 30
}

场景3:配置并返回结果

// ✅ 使用run
val description = person.run {
    name = "Kotlin"
    age = 30
    "${name} is ${age}"
}

场景4:附加操作

// ✅ 使用also
val person = createPerson().also {
    validate(it)
    log(it)
}

场景5:非空对象配置

// ✅ 使用with
with(person) {
    name = "Kotlin"
    age = 30
}

4. 避免的错误

过度嵌套

// ❌ 过度嵌套
person?.let {
    it.name.uppercase()
}?.let {
    it.lowercase()
}?.let {
    "Hello, $it"
}

// ✅ 简化
person?.let {
    "Hello, ${it.name.uppercase().lowercase()}"
}

错误选择

// ❌ 不需要返回值却使用let
person?.let {
    it.name = "Kotlin"  // 不应该用let
}

// ✅ 使用apply
person?.apply {
    name = "Kotlin"
}

5. 最佳实践总结

  1. 空安全:使用let
  2. 初始化:使用apply
  3. 配置计算:使用runwith
  4. 附加操作:使用also
  5. 避免嵌套:不要过度嵌套
  6. 保持简洁:让代码更简洁,不要更复杂

第七章:Kotlin 委托面试题答案

1. 类委托

7.1 Kotlin的类委托是什么?

答案:

类委托(Class Delegation)是Kotlin中实现继承的替代方案,通过委托将类的部分功能委托给另一个对象。

1. 基本概念

类委托允许将类的实现委托给另一个对象,实现代码复用而不使用继承。

2. 基本语法

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() {
        println(x)
    }
}

// 类委托
class Derived(b: Base) : Base by b

// 使用
val base = BaseImpl(10)
val derived = Derived(base)
derived.print()  // 10(委托给base)

3. 委托实现

interface Printer {
    fun print()
    fun printLine()
}

class ConsolePrinter : Printer {
    override fun print() {
        print("Console")
    }
    
    override fun printLine() {
        println("Console Line")
    }
}

// 委托给ConsolePrinter
class Logger(printer: Printer) : Printer by printer {
    // 可以重写部分方法
    override fun print() {
        print("[LOG] ")
        printer.print()  // 调用委托对象
    }
}

4. 与继承对比

特性类委托继承
关系组合继承
灵活性高(运行时选择)低(编译时确定)
多继承支持(多个接口)不支持
代码复用通过委托通过继承

5. 实际应用

实现多个接口

interface A {
    fun methodA()
}

interface B {
    fun methodB()
}

class AImpl : A {
    override fun methodA() {
        println("A")
    }
}

class BImpl : B {
    override fun methodB() {
        println("B")
    }
}

// 委托实现多个接口
class AB(a: A, b: B) : A by a, B by b

// 使用
val ab = AB(AImpl(), BImpl())
ab.methodA()  // A
ab.methodB()  // B

6. 优势

  1. 组合优于继承:遵循组合优于继承原则
  2. 灵活性:运行时选择委托对象
  3. 多接口:可以实现多个接口
  4. 代码复用:复用现有实现

7. 最佳实践

  1. 优先委托:优先使用委托而非继承
  2. 接口委托:委托给接口而非具体类
  3. 选择性重写:只重写需要的方法
  4. 明确意图:使用委托明确表示组合关系

7.2 类委托的实现原理是什么?

答案:

类委托的实现原理:

1. 编译时转换

类委托在编译时转换为组合模式:

// Kotlin代码
class Derived(b: Base) : Base by b

// 编译后的Java等价代码(伪代码)
class Derived implements Base {
    private Base b;
    
    public Derived(Base b) {
        this.b = b;
    }
    
    public void print() {
        b.print();  // 委托调用
    }
}

2. 方法转发

委托的方法调用被转发给委托对象:

class Derived(b: Base) : Base by b {
    // 编译器自动生成所有Base方法,转发给b
}

3. 重写方法

如果重写方法,使用重写的实现:

class Derived(b: Base) : Base by b {
    override fun print() {
        println("Derived")
        // 不调用委托对象
    }
}

4. 性能

类委托的性能:

  • 方法调用开销:一次额外的方法调用
  • 内存开销:存储委托对象引用
  • 编译时优化:编译器可能优化

5. 总结

  • 编译时转换:转换为组合模式
  • 方法转发:自动转发方法调用
  • 性能影响:有轻微开销,但可接受

7.3 类委托和继承的区别是什么?

答案:

类委托和继承的主要区别:

1. 关系类型

类委托(组合)

class Derived(b: Base) : Base by b  // 组合关系

继承

class Derived : Base  // 继承关系

2. 灵活性

类委托

// 运行时选择委托对象
val base1 = BaseImpl1()
val base2 = BaseImpl2()
val derived1 = Derived(base1)  // 委托给base1
val derived2 = Derived(base2)  // 委托给base2

继承

// 编译时确定,不能改变
class Derived : BaseImpl  // 固定继承BaseImpl

3. 多继承

类委托

// 可以实现多个接口
class AB(a: A, b: B) : A by a, B by b

继承

// 只能继承一个类
class Derived : Base  // 单继承

4. 代码复用

类委托

// 通过委托复用
class Derived(b: Base) : Base by b

继承

// 通过继承复用
class Derived : Base

5. 对比总结

特性类委托继承
关系组合继承
灵活性高(运行时)低(编译时)
多继承支持不支持
耦合度
使用场景组合优于继承真正的is-a关系

6. 最佳实践

  1. 优先委托:优先使用委托
  2. 真正继承:只有真正的is-a关系才使用继承
  3. 接口委托:委托给接口提高灵活性

2. 属性委托

7.4 Kotlin的属性委托是什么?

答案:

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

1. 基本语法

class Example {
    var p: String by Delegate()
}

2. 基本概念

属性委托允许将属性的访问逻辑委托给另一个对象,实现属性的自定义行为。

3. 自定义委托

class Delegate {
    private var storedValue: String = ""
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("Getting ${property.name}")
        return storedValue
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("Setting ${property.name} to $value")
        storedValue = value
    }
}

class Example {
    var p: String by Delegate()
}

// 使用
val example = Example()
example.p = "Kotlin"  // Setting p to Kotlin
println(example.p)    // Getting p, Kotlin

4. 标准委托

Kotlin提供标准委托:

lazy

val name: String by lazy {
    "Kotlin"
}

observable

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

5. 实际应用

延迟初始化

class Database {
    val connection: Connection by lazy {
        createConnection()  // 首次访问时创建
    }
}

属性监听

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

6. 最佳实践

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

7.5 lazy委托的用法和原理

答案:

lazy委托的用法和原理:

1. 基本用法

val name: String by lazy {
    println("Initializing name")
    "Kotlin"
}

// 使用
println(name)  // Initializing name, Kotlin(首次访问时初始化)
println(name)  // Kotlin(直接返回,不再初始化)

2. 线程安全模式

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

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

// 线程安全(PUBLICATION模式)
val name: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
    "Kotlin"
}

3. 实现原理

lazy在编译时转换为延迟初始化模式:

// Kotlin代码
val name: String by lazy { "Kotlin" }

// 编译后(伪代码)
private Lazy<String> name$delegate = LazyKt.lazy(() -> "Kotlin");

String getName() {
    return name$delegate.getValue();
}

4. 使用场景

昂贵计算

val expensiveValue: String by lazy {
    calculateExpensiveValue()  // 只在需要时计算
}

单例模式

class Singleton {
    companion object {
        val instance: Singleton by lazy {
            Singleton()
        }
    }
}

5. 与lateinit对比

特性lazylateinit
类型valvar
初始化时机首次访问手动初始化
线程安全默认是不保证
基本类型支持不支持

6. 最佳实践

  1. 昂贵计算使用lazy:计算成本高时使用
  2. 线程安全考虑:多线程环境使用默认模式
  3. val属性使用lazy:只读属性使用lazy

7.6 如何自定义属性委托?

答案:

自定义属性委托的方法:

1. 基本要求

委托类需要实现getValuesetValue操作符:

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

2. 只读属性委托

只读属性只需要getValue

class ReadOnlyDelegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "ReadOnly"
    }
}

class Example {
    val name: String by ReadOnlyDelegate()
}

3. 可变属性委托

可变属性需要getValuesetValue

class MutableDelegate {
    private var value: String = ""
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return value
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        this.value = value
    }
}

class Example {
    var name: String by MutableDelegate()
}

4. 实际应用

验证委托

class ValidatedDelegate<T>(private val validator: (T) -> Boolean) {
    private var value: T? = null
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Not initialized")
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        require(validator(value)) { "Invalid value: $value" }
        this.value = value
    }
}

class User {
    var age: Int by ValidatedDelegate { it >= 0 }
}

5. 最佳实践

  1. 实现操作符:正确实现getValue/setValue
  2. 类型安全:使用泛型提高类型安全
  3. 错误处理:添加适当的验证和错误处理

3. 标准委托

7.7 Kotlin的标准委托有哪些?

答案:

Kotlin提供的标准委托:

1. lazy

延迟初始化:

val name: String by lazy {
    "Kotlin"
}

2. observable

监听属性变化:

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

3. vetoable

可以否决属性变化:

var age: Int by Delegates.vetoable(0) { prop, old, new ->
    new >= 0  // 只有new >= 0时才接受
}

4. notNull

非空委托:

var name: String by Delegates.notNull<String>()

5. 实际应用

class User {
    val connection: Connection by lazy {
        createConnection()
    }
    
    var name: String by Delegates.observable("") { _, old, new ->
        println("Name changed: $old -> $new")
    }
    
    var age: Int by Delegates.vetoable(0) { _, _, new ->
        new >= 0
    }
}

6. 最佳实践

  1. 优先使用标准委托:使用标准委托而非自定义
  2. 根据需求选择:根据需求选择合适的委托
  3. 理解特性:理解每个委托的特性

7.8 Delegates.notNull()的用法

答案:

Delegates.notNull()的用法:

1. 基本用法

var name: String by Delegates.notNull<String>()

// 使用前必须初始化
name = "Kotlin"
println(name)  // Kotlin

// 未初始化访问会抛出异常
// var name2: String by Delegates.notNull<String>()
// println(name2)  // 抛出IllegalStateException

2. 与lateinit对比

特性Delegates.notNull()lateinit
类型基本类型支持不支持基本类型
检查运行时检查编译时检查
使用场景基本类型延迟初始化对象类型延迟初始化

3. 实际应用

class User {
    var id: Int by Delegates.notNull<Int>()
    var score: Double by Delegates.notNull<Double>()
    
    fun init(id: Int, score: Double) {
        this.id = id
        this.score = score
    }
}

4. 最佳实践

  1. 基本类型使用:基本类型延迟初始化时使用
  2. 确保初始化:确保在使用前初始化
  3. 对象类型用lateinit:对象类型优先使用lateinit

7.9 Delegates.observable()的详细用法

答案:

Delegates.observable()的详细用法:

1. 基本语法

var name: String by Delegates.observable("初始值") { prop, old, new ->
    // 属性变化时的回调
    println("${prop.name}: $old -> $new")
}

2. 参数说明

  • prop:属性元数据(KProperty)
  • old:旧值
  • new:新值

3. 实际应用

属性监听

class User {
    var name: String by Delegates.observable("") { _, old, new ->
        println("Name changed: $old -> $new")
        onNameChanged(old, new)
    }
    
    private fun onNameChanged(old: String, new: String) {
        // 处理名称变化
    }
}

数据绑定

class ViewModel {
    var count: Int by Delegates.observable(0) { _, _, new ->
        updateUI(new)
    }
}

4. 最佳实践

  1. 监听变化:需要监听属性变化时使用
  2. 副作用处理:在回调中处理副作用
  3. 避免复杂逻辑:回调中避免复杂逻辑

7.10 Delegates.vetoable()的详细用法

答案:

Delegates.vetoable()的详细用法:

1. 基本语法

var age: Int by Delegates.vetoable(0) { prop, old, new ->
    // 返回true接受新值,false拒绝
    new >= 0
}

2. 工作原理

如果回调返回true,接受新值;返回false,拒绝新值(保持旧值)。

3. 实际应用

验证年龄

class Person {
    var age: Int by Delegates.vetoable(0) { _, _, new ->
        new >= 0 && new <= 150  // 只接受0-150
    }
}

val person = Person()
person.age = 25   // ✅ 接受
person.age = -5   // ❌ 拒绝,保持25
person.age = 200  // ❌ 拒绝,保持25

验证字符串

class User {
    var email: String by Delegates.vetoable("") { _, _, new ->
        new.contains("@")  // 只接受包含@的邮箱
    }
}

4. 与observable对比

特性observablevetoable
作用监听变化验证并可能拒绝
返回值UnitBoolean
使用场景通知变化验证值

5. 最佳实践

  1. 验证使用:需要验证值时使用
  2. 返回Boolean:回调必须返回Boolean
  3. 明确规则:验证规则要明确

7.11 属性委托的实现原理

答案:

属性委托的实现原理:

1. 编译时转换

属性委托在编译时转换为方法调用:

// Kotlin代码
class Example {
    var p: String by Delegate()
}

// 编译后(伪代码)
class Example {
    private Delegate p$delegate = new Delegate();
    
    String getP() {
        return p$delegate.getValue(this, property);
    }
    
    void setP(String value) {
        p$delegate.setValue(this, property, value);
    }
}

2. 操作符调用

// 属性访问
example.p = "Kotlin"  // 调用setValue
val value = example.p  // 调用getValue

3. 性能影响

  • 方法调用开销:一次额外的方法调用
  • 内存开销:存储委托对象
  • 编译时优化:编译器可能优化

4. 总结

  • 编译时转换:转换为方法调用
  • 操作符实现:通过getValue/setValue实现
  • 性能可接受:开销较小

7.12 委托属性的性能影响

答案:

委托属性的性能影响:

1. 方法调用开销

每次属性访问都有额外的方法调用:

// 属性访问
example.p = "Kotlin"  // 调用setValue方法
val value = example.p  // 调用getValue方法

2. 内存开销

委托对象需要存储:

class Example {
    var p: String by Delegate()  // 存储Delegate对象
}

3. 优化建议

内联委托

// 某些委托可能被内联优化
val name: String by lazy { "Kotlin" }  // 可能被优化

避免过度使用

// ❌ 过度使用
class Example {
    var a: String by Delegate()
    var b: String by Delegate()
    var c: String by Delegate()
    // ... 太多委托
}

// ✅ 合理使用
class Example {
    var important: String by Delegate()  // 只在需要时使用
    var normal: String = ""  // 普通属性
}

4. 性能测试

// 性能测试
val start = System.currentTimeMillis()
repeat(1_000_000) {
    example.p = "test"
    val value = example.p
}
val end = System.currentTimeMillis()
println("Time: ${end - start}ms")

5. 最佳实践

  1. 合理使用:不要过度使用委托
  2. 性能敏感:性能敏感场景谨慎使用
  3. 标准委托:优先使用标准委托(可能优化)
  4. 测试验证:必要时进行性能测试

7.13 委托属性的使用场景

答案:

委托属性的使用场景:

1. 延迟初始化(lazy)

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

2. 属性监听(observable)

class ViewModel {
    var count: Int by Delegates.observable(0) { _, _, new ->
        updateUI(new)  // 监听变化,更新UI
    }
}

3. 属性验证(vetoable)

class User {
    var age: Int by Delegates.vetoable(0) { _, _, new ->
        new >= 0 && new <= 150  // 验证年龄范围
    }
}

4. 自定义委托

缓存委托

class CachedDelegate<T>(private val loader: () -> T) {
    private var cached: T? = null
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (cached == null) {
            cached = loader()
        }
        return cached!!
    }
}

class DataLoader {
    val data: String by CachedDelegate {
        loadFromNetwork()  // 只加载一次
    }
}

5. 实际应用

Android开发

class MainActivity : AppCompatActivity() {
    private var count: Int by Delegates.observable(0) { _, _, new ->
        updateCountText(new)
    }
}

配置管理

class Config {
    var apiUrl: String by Delegates.observable("") { _, old, new ->
        if (old != new) {
            reloadConfig()
        }
    }
}

6. 最佳实践

  1. 延迟初始化:昂贵资源使用lazy
  2. 属性监听:需要监听变化时使用observable
  3. 属性验证:需要验证时使用vetoable
  4. 自定义委托:特殊需求时自定义

第八章:Kotlin 泛型面试题答案

1. 泛型基础

8.1 Kotlin的泛型是什么?

答案:

泛型(Generics)允许在定义类、接口、函数时使用类型参数,提高代码的复用性和类型安全。

1. 基本概念

泛型允许编写可以处理多种类型的代码,而不需要为每种类型编写重复代码。

2. 基本语法

// 泛型类
class Box<T>(val value: T)

// 泛型函数
fun <T> identity(value: T): T = value

// 泛型接口
interface Container<T> {
    fun get(): T
    fun set(value: T)
}

3. 使用示例

// 使用泛型类
val intBox = Box<Int>(10)
val stringBox = Box<String>("Kotlin")

// 使用泛型函数
val number = identity<Int>(5)
val text = identity<String>("Hello")

// 类型推断
val number2 = identity(5)  // 推断为Int
val text2 = identity("Hello")  // 推断为String

4. 类型安全

泛型提供编译时类型检查:

val intBox = Box<Int>(10)
// val value: String = intBox.value  // ❌ 编译错误,类型不匹配
val value: Int = intBox.value  // ✅

5. 与Java对比

Kotlin

class Box<T>(val value: T)

Java

public class Box<T> {
    private T value;
    public Box(T value) {
        this.value = value;
    }
}

6. 实际应用

集合泛型

val list: List<String> = listOf("a", "b", "c")
val map: Map<String, Int> = mapOf("a" to 1, "b" to 2)

泛型函数

fun <T> first(list: List<T>): T? {
    return list.firstOrNull()
}

7. 最佳实践

  1. 使用泛型:提高代码复用性
  2. 类型安全:利用类型检查
  3. 类型推断:利用类型推断简化代码

8.2 Kotlin和Java的泛型有什么区别?

答案:

Kotlin和Java泛型的主要区别:

1. 声明位置差异

Kotlin

class Box<T>  // 类型参数在类名后

Java

class Box<T>  // 相同

2. 通配符

Kotlin

// 使用out/in关键字
List<out String>  // 协变
List<in String>    // 逆变

Java

List<? extends String>  // 上界通配符
List<? super String>    // 下界通配符

3. 类型擦除

两者都有类型擦除,但Kotlin支持reified

Kotlin

inline fun <reified T> getType(): Class<T> {
    return T::class.java
}

Java

// 不支持reified,需要传递Class对象
public <T> Class<T> getType(Class<T> clazz) {
    return clazz;
}

4. 空安全

Kotlin

List<String?>  // 可空元素
List<String>    // 非空元素

Java

List<String>  // 所有元素都可能为null

5. 对比总结

特性KotlinJava
语法out/inextends/super
类型擦除支持reified不支持
空安全支持不支持
默认行为不变不变

6. 最佳实践

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


8.3 泛型的类型参数是什么?

答案:

泛型的类型参数:

1. 基本概念

类型参数是泛型中使用的占位符,表示实际类型。

2. 命名约定

// 单个类型参数通常使用T
class Box<T>

// 多个类型参数使用有意义的名称
class Map<K, V>
class Pair<A, B>

// 常见命名
// T - Type
// E - Element
// K - Key
// V - Value
// R - Return

3. 类型参数约束

// 上界约束
class Box<T : Number>  // T必须是Number的子类型

// 多个约束
class Box<T> where T : Comparable<T>, T : Serializable

4. 实际应用

// 单个参数
class Container<T>

// 多个参数
class Pair<A, B>(val first: A, val second: B)

// 有约束的参数
class SortedList<T : Comparable<T>>

5. 最佳实践

  1. 使用有意义的名称:多个参数时使用有意义的名称
  2. 遵循约定:遵循常见的命名约定
  3. 添加约束:需要时添加类型约束

2. 型变

8.4 Kotlin的型变(Variance)是什么?

答案:

型变(Variance)描述泛型类型之间的子类型关系。

1. 基本概念

型变决定List<String>List<Any>之间的关系。

2. 三种型变

  • 协变(Covariance)List<String>List<Any>的子类型
  • 逆变(Contravariance)List<Any>List<String>的子类型
  • 不变(Invariance):两者没有关系

3. Kotlin的型变

Kotlin使用outin关键字:

// 协变(out)
interface Producer<out T> {
    fun produce(): T
}

// 逆变(in)
interface Consumer<in T> {
    fun consume(value: T)
}

// 不变(默认)
interface Container<T> {
    fun get(): T
    fun set(value: T)
}

4. 实际应用

// 协变:只能生产,不能消费
val producer: Producer<String> = object : Producer<String> {
    override fun produce() = "Kotlin"
}
val anyProducer: Producer<Any> = producer  // ✅ 协变

// 逆变:只能消费,不能生产
val consumer: Consumer<Any> = object : Consumer<Any> {
    override fun consume(value: Any) { }
}
val stringConsumer: Consumer<String> = consumer  // ✅ 逆变

5. 最佳实践

  1. 理解型变:理解三种型变的概念
  2. 使用out/in:根据需求使用out/in
  3. 默认不变:默认是不变,需要时使用型变

8.5 协变(Covariance)是什么?

答案:

协变(Covariance)表示泛型类型与类型参数同方向变化。

1. 基本概念

如果AB的子类型,那么Producer<A>Producer<B>的子类型。

2. out关键字

使用out关键字声明协变:

interface Producer<out T> {
    fun produce(): T  // 只能生产T,不能消费
}

3. 使用场景

只读集合

interface List<out E> {
    fun get(index: Int): E  // 只能读取
    // fun add(element: E)  // ❌ 不能添加(消费)
}

4. 实际应用

// 协变示例
val stringList: List<String> = listOf("a", "b")
val anyList: List<Any> = stringList  // ✅ 协变,可以赋值

// 使用
fun process(list: List<Any>) {
    list.forEach { println(it) }
}

process(stringList)  // ✅ 可以传递

5. 限制

协变类型参数只能出现在输出位置(返回值、只读属性):

interface Producer<out T> {
    fun produce(): T  // ✅ 可以(输出位置)
    // fun consume(value: T)  // ❌ 不能(输入位置)
}

6. 最佳实践

  1. 只读使用协变:只读集合使用协变
  2. 使用out关键字:明确标记协变
  3. 理解限制:理解协变的限制

8.6 逆变(Contravariance)是什么?

答案:

逆变(Contravariance)表示泛型类型与类型参数反方向变化。

1. 基本概念

如果AB的子类型,那么Consumer<B>Consumer<A>的子类型。

2. in关键字

使用in关键字声明逆变:

interface Consumer<in T> {
    fun consume(value: T)  // 只能消费T,不能生产
}

3. 使用场景

只写集合

interface MutableList<in E> {
    fun add(element: E)  // 只能添加(消费)
    // fun get(index: Int): E  // ❌ 不能读取(生产)
}

4. 实际应用

// 逆变示例
val anyConsumer: Consumer<Any> = object : Consumer<Any> {
    override fun consume(value: Any) { }
}
val stringConsumer: Consumer<String> = anyConsumer  // ✅ 逆变,可以赋值

// 使用
fun process(consumer: Consumer<String>) {
    consumer.consume("Kotlin")
}

process(anyConsumer)  // ✅ 可以传递

5. 限制

逆变类型参数只能出现在输入位置(参数、可变属性):

interface Consumer<in T> {
    fun consume(value: T)  // ✅ 可以(输入位置)
    // fun produce(): T    // ❌ 不能(输出位置)
}

6. 最佳实践

  1. 只写使用逆变:只写集合使用逆变
  2. 使用in关键字:明确标记逆变
  3. 理解限制:理解逆变的限制

8.7 不变(Invariance)是什么?

答案:

不变(Invariance)表示泛型类型之间没有子类型关系。

1. 基本概念

即使AB的子类型,Container<A>Container<B>也没有关系。

2. 默认行为

Kotlin的泛型默认是不变的:

class Box<T>(var value: T)

val stringBox = Box<String>("Kotlin")
// val anyBox: Box<Any> = stringBox  // ❌ 编译错误,不变

3. 使用场景

可读可写

class MutableContainer<T> {
    private var value: T? = null
    
    fun get(): T? = value
    fun set(newValue: T) {
        value = newValue
    }
}

4. 实际应用

// 不变示例
val stringBox = Box<String>("Kotlin")
val intBox = Box<Int>(10)

// 不能互相赋值
// val anyBox: Box<Any> = stringBox  // ❌ 错误
// val anyBox: Box<Any> = intBox     // ❌ 错误

5. 最佳实践

  1. 默认不变:大多数情况使用不变
  2. 需要时使用型变:需要子类型关系时使用out/in
  3. 理解关系:理解三种型变的关系

8.8 out和in关键字的作用是什么?

答案:

outin关键字的作用:

1. out关键字(协变)

interface Producer<out T> {
    fun produce(): T  // 只能生产(输出位置)
}

作用:

  • 声明协变
  • 类型参数只能出现在输出位置
  • Producer<String>Producer<Any>的子类型

2. in关键字(逆变)

interface Consumer<in T> {
    fun consume(value: T)  // 只能消费(输入位置)
}

作用:

  • 声明逆变
  • 类型参数只能出现在输入位置
  • Consumer<Any>Consumer<String>的子类型

3. 使用位置规则

out(协变)

interface Producer<out T> {
    fun produce(): T        // ✅ 返回值(输出)
    val value: T            // ✅ 只读属性(输出)
    // fun consume(value: T) // ❌ 参数(输入)
    // var value: T          // ❌ 可变属性(输入输出)
}

in(逆变)

interface Consumer<in T> {
    fun consume(value: T)   // ✅ 参数(输入)
    // fun produce(): T    // ❌ 返回值(输出)
    // val value: T         // ❌ 只读属性(输出)
}

4. 实际应用

协变(out)

interface Source<out T> {
    fun next(): T
}

val stringSource: Source<String> = ...
val anySource: Source<Any> = stringSource  // ✅ 协变

逆变(in)

interface Sink<in T> {
    fun add(value: T)
}

val anySink: Sink<Any> = ...
val stringSink: Sink<String> = anySink  // ✅ 逆变

5. 记忆方法

  • out = 输出:只能输出(生产)
  • in = 输入:只能输入(消费)
  • PECS原则:Producer Extends, Consumer Super(Java)
  • Kotlin:Producer out, Consumer in

6. 最佳实践

  1. 理解位置规则:理解输入输出位置
  2. 使用out/in:根据需求使用
  3. 明确意图:使用关键字明确型变意图

3. 类型擦除

8.9 Kotlin的类型擦除是什么?

答案:

类型擦除(Type Erasure)是JVM上泛型类型信息在运行时被擦除的特性。

1. 基本概念

在运行时,泛型类型参数信息被擦除,只保留原始类型。

2. 类型擦除示例

val stringList = listOf<String>("a", "b")
val intList = listOf<Int>(1, 2)

// 运行时类型相同
println(stringList::class)  // class java.util.ArrayList
println(intList::class)     // class java.util.ArrayList

3. 影响

不能检查泛型类型

fun <T> checkType(value: Any) {
    // if (value is List<String>)  // ❌ 编译错误,不能检查
    if (value is List<*>) {  // ✅ 可以使用星投影
        // 但不能区分List<String>和List<Int>
    }
}

4. 解决方案

reified关键字

inline fun <reified T> checkType(value: Any): Boolean {
    return value is T  // ✅ 可以使用reified
}

5. 与Java对比

两者都有类型擦除,但Kotlin支持reified解决部分问题。

6. 最佳实践

  1. 理解限制:理解类型擦除的限制
  2. 使用reified:需要类型信息时使用reified
  3. 使用星投影:运行时检查使用星投影

8.10 如何解决类型擦除问题?

答案:

解决类型擦除问题的方法:

1. reified关键字

使用reified关键字和内联函数:

inline fun <reified T> getType(): Class<T> {
    return T::class.java
}

inline fun <reified T> isInstance(value: Any): Boolean {
    return value is T
}

2. 星投影

使用星投影进行运行时检查:

fun process(list: List<*>) {
    if (list is List<*>) {
        // 处理,但不能知道具体类型
    }
}

3. 传递Class对象

传递Class对象保存类型信息:

fun <T> process(clazz: Class<T>, value: Any): T? {
    return if (clazz.isInstance(value)) {
        clazz.cast(value)
    } else {
        null
    }
}

4. 实际应用

reified使用

inline fun <reified T> List<*>.filterIsInstance(): List<T> {
    return filterIsInstance<T>()
}

val list: List<Any> = listOf(1, "a", 2, "b")
val numbers = list.filterIsInstance<Int>()  // [1, 2]

5. 最佳实践

  1. 使用reified:需要类型信息时使用reified
  2. 内联函数:reified必须配合inline使用
  3. 理解限制:理解类型擦除的根本限制

8.11 reified关键字的作用是什么?

答案:

reified关键字的作用:

1. 基本概念

reified允许在运行时访问泛型类型参数的实际类型。

2. 基本用法

inline fun <reified T> getType(): Class<T> {
    return T::class.java
}

// 使用
val stringType = getType<String>()  // String.class
val intType = getType<Int>()        // Integer.class

3. 类型检查

inline fun <reified T> isInstance(value: Any): Boolean {
    return value is T
}

// 使用
isInstance<String>("Kotlin")  // true
isInstance<String>(123)       // false

4. 限制

reified只能用于:

  • 内联函数(inline
  • 类型参数
// ✅ 正确
inline fun <reified T> process() { }

// ❌ 错误
fun <reified T> process() { }  // 必须inline

5. 实现原理

reified通过内联展开,在编译时替换为具体类型:

// Kotlin代码
inline fun <reified T> getType(): Class<T> {
    return T::class.java
}

// 调用
getType<String>()

// 编译后(伪代码)
String::class.java  // 直接替换

6. 实际应用

类型过滤

inline fun <reified T> List<*>.filterIsInstance(): List<T> {
    return filter { it is T } as List<T>
}

val list: List<Any> = listOf(1, "a", 2, "b")
val numbers = list.filterIsInstance<Int>()  // [1, 2]

7. 最佳实践

  1. 配合inline使用:reified必须配合inline
  2. 运行时类型信息:需要运行时类型信息时使用
  3. 理解原理:理解内联展开的原理

8.12 泛型的上界和下界

答案:

泛型的上界和下界:

1. 上界(Upper Bound)

限制类型参数必须是某个类型的子类型:

// 上界:T必须是Number的子类型
class Box<T : Number>(val value: T)

// 使用
val intBox = Box<Int>(10)      // ✅ Int是Number的子类型
val stringBox = Box<String>("")  // ❌ String不是Number的子类型

2. 多个上界

使用where子句:

class Box<T> where T : Comparable<T>, T : Serializable {
    fun compare(other: T): Int {
        return value.compareTo(other)
    }
}

3. 下界(Lower Bound)

Kotlin不直接支持下界,但可以通过逆变实现类似效果:

// 使用in实现下界效果
interface Consumer<in T>  // T可以是Any的子类型

4. 实际应用

上界约束

fun <T : Comparable<T>> max(a: T, b: T): T {
    return if (a > b) a else b
}

max(1, 2)        // ✅ Int实现Comparable
max("a", "b")    // ✅ String实现Comparable

5. 最佳实践

  1. 使用上界:需要约束类型时使用上界
  2. 多个约束:使用where子句
  3. 理解下界:通过逆变理解下界概念

8.13 where子句的用法

答案:

where子句的用法:

1. 基本语法

class Box<T> where T : Comparable<T>, T : Serializable {
    // T必须同时实现Comparable和Serializable
}

2. 多个约束

fun <T> process(value: T) where T : Comparable<T>, T : Serializable {
    // 处理
}

3. 实际应用

class SortedContainer<T> where T : Comparable<T>, T : Serializable {
    private val items = mutableListOf<T>()
    
    fun add(item: T) {
        items.add(item)
        items.sort()
    }
}

4. 最佳实践

  1. 多个约束使用where:需要多个约束时使用
  2. 明确约束:明确类型约束
  3. 合理使用:不要过度约束

8.14 泛型约束的使用

答案:

泛型约束的使用:

1. 上界约束

class Box<T : Number>

2. 多个约束

class Box<T> where T : Comparable<T>, T : Serializable

3. 实际应用

// 约束为Number
fun <T : Number> sum(list: List<T>): Double {
    return list.sumOf { it.toDouble() }
}

// 约束为Comparable
fun <T : Comparable<T>> max(a: T, b: T): T {
    return if (a > b) a else b
}

4. 最佳实践

  1. 合理约束:根据需求添加约束
  2. 避免过度约束:不要添加不必要的约束
  3. 使用where:多个约束使用where子句

8.15 星投影(Star Projection)是什么?

答案:

星投影(Star Projection)用于表示未知的泛型类型。

1. 基本语法

List<*>  // 未知类型的List
Map<*, *>  // 未知键值类型的Map

2. 使用场景

运行时类型检查

fun process(list: List<*>) {
    if (list is List<*>) {
        // 处理,但不知道具体类型
    }
}

3. 限制

星投影的类型信息有限:

val list: List<*> = listOf("a", "b")
// val item: String = list[0]  // ❌ 不能确定类型
val item: Any? = list[0]  // ✅ 只能作为Any?

4. 实际应用

fun printSize(list: List<*>) {
    println(list.size)  // 可以访问size
    // 但不能访问具体元素类型
}

5. 最佳实践

  1. 运行时检查:运行时类型检查时使用
  2. 理解限制:理解星投影的限制
  3. 需要类型时使用reified:需要具体类型时使用reified

8.16 泛型在集合中的应用

答案:

泛型在集合中的应用:

1. List泛型

val list: List<String> = listOf("a", "b", "c")
val intList: List<Int> = listOf(1, 2, 3)

2. Map泛型

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

3. 泛型函数

fun <T> first(list: List<T>): T? {
    return list.firstOrNull()
}

fun <T, R> map(list: List<T>, transform: (T) -> R): List<R> {
    return list.map(transform)
}

4. 实际应用

// 类型安全的集合操作
val numbers = listOf(1, 2, 3)
val strings = listOf("a", "b", "c")

val doubled = numbers.map { it * 2 }  // List<Int>
val uppercased = strings.map { it.uppercase() }  // List<String>

5. 最佳实践

  1. 利用泛型:使用泛型提高类型安全
  2. 类型推断:利用类型推断简化代码
  3. 明确类型:复杂情况明确指定类型

8.17 泛型与类型安全的保证

答案:

泛型如何保证类型安全:

1. 编译时检查

泛型在编译时进行类型检查:

val list: List<String> = listOf("a", "b")
// list.add(1)  // ❌ 编译错误,类型不匹配
list.add("c")  // ✅

2. 类型推断

编译器可以推断类型:

val list = listOf("a", "b")  // 推断为List<String>
val map = mapOf("a" to 1)     // 推断为Map<String, Int>

3. 类型擦除的影响

运行时类型信息被擦除,但编译时检查仍然有效:

// 编译时检查
val list: List<String> = listOf("a", "b")
// list.add(1)  // ❌ 编译错误

// 运行时类型擦除
println(list::class)  // class java.util.ArrayList(无类型信息)

4. 最佳实践

  1. 利用编译时检查:利用编译时类型检查
  2. 明确类型:明确指定类型提高安全性
  3. 理解擦除:理解类型擦除的影响

第九章:Kotlin 对象表达式和声明面试题答案

1. 对象表达式

9.1 Kotlin的对象表达式是什么?

答案:

对象表达式(Object Expression)用于创建匿名类的实例,类似Java的匿名内部类。

1. 基本概念

对象表达式可以创建匿名对象,实现接口或继承类。

2. 基本语法

// 实现接口
val listener = object : OnClickListener {
    override fun onClick() {
        println("Clicked")
    }
}

// 继承类
val obj = object : SomeClass() {
    override fun someMethod() {
        println("Overridden")
    }
}

3. 实现接口

interface ClickListener {
    fun onClick()
}

val listener = object : ClickListener {
    override fun onClick() {
        println("Button clicked")
    }
}

listener.onClick()

4. 继承类

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

val dog = object : Animal() {
    override fun makeSound() {
        println("Woof")
    }
}

dog.makeSound()

5. 访问外部变量

对象表达式可以访问外部作用域的变量:

var count = 0
val listener = object : ClickListener {
    override fun onClick() {
        count++  // 可以访问外部变量
        println("Count: $count")
    }
}

6. 与Java匿名内部类对比

Kotlin

val listener = object : OnClickListener {
    override fun onClick() {
        println("Clicked")
    }
}

Java

OnClickListener listener = new OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("Clicked");
    }
};

7. 实际应用

事件监听

button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
        println("Button clicked")
    }
})

回调接口

api.call(object : Callback {
    override fun onSuccess(data: String) {
        println("Success: $data")
    }
    
    override fun onError(error: Exception) {
        println("Error: ${error.message}")
    }
})

8. 最佳实践

  1. 实现接口:需要实现接口时使用
  2. 一次性使用:只使用一次的对象
  3. 访问外部变量:需要访问外部变量时使用

9.2 对象表达式和匿名内部类的区别是什么?

答案:

对象表达式和匿名内部类的主要区别:

1. 语法差异

Kotlin对象表达式

val listener = object : OnClickListener {
    override fun onClick() {
        println("Clicked")
    }
}

Java匿名内部类

OnClickListener listener = new OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("Clicked");
    }
};

2. 功能差异

Kotlin对象表达式

  • 可以实现多个接口
  • 可以访问外部变量(包括修改)
  • 可以继承类

Java匿名内部类

  • 只能实现一个接口或继承一个类
  • 访问外部变量需要final(Java 8+可以访问effectively final)

3. 实际应用

Kotlin(更灵活)

val obj = object : Interface1, Interface2 {
    override fun method1() { }
    override fun method2() { }
}

Java(限制更多)

// 只能实现一个接口或继承一个类
SomeInterface obj = new SomeInterface() {
    @Override
    public void method() { }
};

4. 最佳实践

  1. Kotlin更灵活:对象表达式功能更强大
  2. 理解差异:理解两者的差异
  3. 合理使用:根据需求选择合适的方案

9.3 对象表达式的使用场景有哪些?

答案:

对象表达式的使用场景:

1. 事件监听

button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
        handleClick()
    }
})

2. 回调接口

api.request(object : Callback {
    override fun onSuccess(data: String) {
        processData(data)
    }
    
    override fun onError(error: Exception) {
        handleError(error)
    }
})

3. 临时对象

val temp = object {
    val x = 1
    val y = 2
    fun sum() = x + y
}

println(temp.sum())

4. 实现多个接口

val handler = object : ClickListener, LongClickListener {
    override fun onClick() { }
    override fun onLongClick() { }
}

5. 最佳实践

  1. 一次性使用:只使用一次的对象
  2. 实现接口:需要实现接口时使用
  3. 访问外部变量:需要访问外部变量时使用

2. 对象声明

9.4 Kotlin的对象声明是什么?

答案:

对象声明(Object Declaration)用于创建单例对象。

1. 基本概念

对象声明创建一个单例对象,整个程序只有一个实例。

2. 基本语法

object Singleton {
    fun doSomething() {
        println("Doing something")
    }
}

// 使用
Singleton.doSomething()

3. 单例模式

对象声明自动实现单例模式:

object DatabaseManager {
    fun connect() {
        println("Connecting to database")
    }
    
    fun disconnect() {
        println("Disconnecting from database")
    }
}

// 使用
DatabaseManager.connect()
DatabaseManager.disconnect()

// 验证单例
val db1 = DatabaseManager
val db2 = DatabaseManager
println(db1 === db2)  // true(同一个实例)

4. 实现接口

对象声明可以实现接口:

interface Logger {
    fun log(message: String)
}

object ConsoleLogger : Logger {
    override fun log(message: String) {
        println(message)
    }
}

ConsoleLogger.log("Hello")

5. 继承类

对象声明可以继承类:

open class Handler {
    open fun handle() {
        println("Handling")
    }
}

object DefaultHandler : Handler() {
    override fun handle() {
        println("Default handling")
    }
}

6. 与Java单例对比

Kotlin

object Singleton {
    fun doSomething() { }
}

Java

public class Singleton {
    private static Singleton instance;
    
    private Singleton() { }
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
    public void doSomething() { }
}

7. 实际应用

工具类

object StringUtils {
    fun capitalize(str: String): String {
        return str.capitalize()
    }
    
    fun reverse(str: String): String {
        return str.reversed()
    }
}

StringUtils.capitalize("hello")  // "Hello"

配置管理

object Config {
    val apiUrl = "https://api.example.com"
    val timeout = 5000
    
    fun getBaseUrl(): String {
        return apiUrl
    }
}

8. 最佳实践

  1. 单例使用:需要单例时使用对象声明
  2. 工具类:工具类使用对象声明
  3. 全局配置:全局配置使用对象声明
  4. 避免滥用:不要过度使用单例

9.5 对象声明和单例模式的关系是什么?

答案:

对象声明自动实现单例模式:

1. 自动单例

对象声明自动创建单例,无需手动实现:

object Singleton {
    fun doSomething() { }
}

// 验证单例
val s1 = Singleton
val s2 = Singleton
println(s1 === s2)  // true

2. 线程安全

对象声明的初始化是线程安全的:

object Singleton {
    init {
        println("Initializing")  // 线程安全初始化
    }
}

3. 延迟初始化

对象声明在首次访问时初始化:

object Singleton {
    init {
        println("Initialized")  // 首次访问时执行
    }
}

// 首次访问
Singleton  // 输出:Initialized

4. 与Java单例对比

Kotlin(简洁)

object Singleton {
    fun doSomething() { }
}

Java(复杂)

// 需要手动实现单例
public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() { }
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

5. 最佳实践

  1. 使用对象声明:需要单例时使用对象声明
  2. 理解初始化:理解延迟初始化机制
  3. 线程安全:对象声明是线程安全的

9.6 对象声明和类的区别是什么?

答案:

对象声明和类的主要区别:

1. 实例数量

对象声明

object Singleton {
    fun doSomething() { }
}

// 只有一个实例
val s1 = Singleton
val s2 = Singleton
println(s1 === s2)  // true

class MyClass {
    fun doSomething() { }
}

// 可以创建多个实例
val c1 = MyClass()
val c2 = MyClass()
println(c1 === c2)  // false

2. 实例化

对象声明

object Singleton { }
// 直接使用,不需要实例化
Singleton.doSomething()

class MyClass { }
// 需要实例化
val instance = MyClass()
instance.doSomething()

3. 构造函数

对象声明

// 不能有构造函数
object Singleton {
    // 不能有主构造函数或次构造函数
}

// 可以有构造函数
class MyClass(val name: String) { }

4. 对比总结

特性对象声明
实例数量单例多个
实例化不需要需要
构造函数不能有可以有
使用场景单例、工具类普通类

5. 最佳实践

  1. 单例使用对象声明:需要单例时使用对象声明
  2. 普通类使用类:需要多个实例时使用类
  3. 明确意图:根据需求选择合适的类型

3. 伴生对象

9.7 Kotlin的伴生对象(Companion Object)是什么?

答案:

伴生对象(Companion Object)是类内部的对象声明,类似Java的静态成员。

1. 基本概念

伴生对象是类内部的对象,可以通过类名直接访问,类似Java的静态成员。

2. 基本语法

class MyClass {
    companion object {
        fun create(): MyClass {
            return MyClass()
        }
        
        const val CONSTANT = "Constant"
    }
}

// 使用
MyClass.create()  // 通过类名访问
MyClass.CONSTANT  // 访问常量

3. 命名伴生对象

class MyClass {
    companion object Factory {
        fun create(): MyClass {
            return MyClass()
        }
    }
}

// 使用
MyClass.Factory.create()
MyClass.create()  // 也可以

4. 实现接口

伴生对象可以实现接口:

interface Factory<T> {
    fun create(): T
}

class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass {
            return MyClass()
        }
    }
}

5. 与Java静态成员对比

Kotlin

class MyClass {
    companion object {
        fun create(): MyClass {
            return MyClass()
        }
    }
}

Java

public class MyClass {
    public static MyClass create() {
        return new MyClass();
    }
}

6. 实际应用

工厂方法

class User private constructor(val name: String) {
    companion object {
        fun create(name: String): User {
            return User(name)
        }
    }
}

val user = User.create("Kotlin")

常量定义

class Constants {
    companion object {
        const val API_URL = "https://api.example.com"
        const val TIMEOUT = 5000
    }
}

val url = Constants.API_URL

7. 最佳实践

  1. 替代静态成员:使用伴生对象替代Java静态成员
  2. 工厂方法:实现工厂模式
  3. 常量定义:定义类相关常量

9.8 伴生对象和Java的static的区别是什么?

答案:

伴生对象和Java static的区别:

1. 本质差异

Kotlin伴生对象

class MyClass {
    companion object {
        fun method() { }
    }
}
// 伴生对象是真实的对象,可以实现接口

Java static

public class MyClass {
    public static void method() { }
}
// static是关键字,不是对象

2. 功能差异

伴生对象

  • 是真实的对象
  • 可以实现接口
  • 可以继承类
  • 可以有扩展函数

Java static

  • 不是对象
  • 不能实现接口
  • 不能继承类
  • 不能有扩展

3. 实际应用

伴生对象实现接口

interface Factory<T> {
    fun create(): T
}

class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass {
            return MyClass()
        }
    }
}

4. 最佳实践

  1. 理解差异:理解本质差异
  2. 利用优势:利用伴生对象的优势
  3. 替代static:使用伴生对象替代Java static

9.9 伴生对象的使用场景有哪些?

答案:

伴生对象的使用场景:

1. 工厂方法

class User private constructor(val name: String) {
    companion object {
        fun create(name: String): User {
            return User(name)
        }
    }
}

2. 常量定义

class Config {
    companion object {
        const val API_URL = "https://api.example.com"
        const val TIMEOUT = 5000
    }
}

3. 工具方法

class StringUtils {
    companion object {
        fun capitalize(str: String): String {
            return str.capitalize()
        }
    }
}

4. 实现接口

interface Factory<T> {
    fun create(): T
}

class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass {
            return MyClass()
        }
    }
}

5. 最佳实践

  1. 工厂方法:实现工厂模式
  2. 常量定义:定义类相关常量
  3. 工具方法:定义类相关的工具方法

9.10 伴生对象的初始化时机

答案:

伴生对象的初始化时机:

1. 延迟初始化

伴生对象在首次访问时初始化:

class MyClass {
    companion object {
        init {
            println("Companion object initialized")
        }
    }
}

// 首次访问时初始化
MyClass  // 输出:Companion object initialized

2. 线程安全

伴生对象的初始化是线程安全的:

class MyClass {
    companion object {
        init {
            // 线程安全初始化
            println("Initialized")
        }
    }
}

3. 初始化顺序

class MyClass {
    init {
        println("Class initialized")
    }
    
    companion object {
        init {
            println("Companion initialized")
        }
    }
}

// 执行顺序:
// 1. 伴生对象初始化(首次访问类时)
// 2. 类实例初始化(创建实例时)

4. 最佳实践

  1. 理解时机:理解延迟初始化机制
  2. 线程安全:初始化是线程安全的
  3. 避免复杂逻辑:初始化中避免复杂逻辑

9.11 伴生对象实现工厂模式

答案:

使用伴生对象实现工厂模式:

1. 基本工厂模式

class User private constructor(val name: String) {
    companion object {
        fun create(name: String): User {
            return User(name)
        }
    }
}

val user = User.create("Kotlin")

2. 多种创建方式

class User private constructor(val name: String, val email: String) {
    companion object {
        fun create(name: String): User {
            return User(name, "$name@example.com")
        }
        
        fun createWithEmail(name: String, email: String): User {
            return User(name, email)
        }
    }
}

3. 实现工厂接口

interface Factory<T> {
    fun create(): T
}

class Product {
    companion object : Factory<Product> {
        override fun create(): Product {
            return Product()
        }
    }
}

4. 实际应用

class Connection private constructor(val url: String) {
    companion object {
        fun create(url: String): Connection {
            validateUrl(url)
            return Connection(url)
        }
        
        private fun validateUrl(url: String) {
            require(url.startsWith("http")) { "Invalid URL" }
        }
    }
}

5. 最佳实践

  1. 私有构造函数:使用私有构造函数
  2. 工厂方法:提供工厂方法创建实例
  3. 实现接口:可以实现工厂接口

第十章:Kotlin 密封类面试题答案

1. 密封类基础

10.1 Kotlin的密封类(Sealed Class)是什么?

答案:

密封类(Sealed Class)是Kotlin中用于表示受限类层次结构的类,所有子类必须在同一文件或同一模块内定义。

1. 基本概念

密封类用于表示一组有限的、已知的子类型,编译器可以检查是否处理了所有情况。

2. 基本语法

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

3. 特点

  • 所有子类必须在同一文件或模块内定义
  • 不能直接实例化
  • 与when表达式配合使用,编译器可以检查完整性

4. 使用示例

sealed class NetworkResult {
    data class Success(val data: String) : NetworkResult()
    data class Error(val message: String) : NetworkResult()
    object Loading : NetworkResult()
}

fun process(result: NetworkResult) {
    when (result) {
        is NetworkResult.Success -> println("Data: ${result.data}")
        is NetworkResult.Error -> println("Error: ${result.message}")
        is NetworkResult.Loading -> println("Loading...")
        // 编译器确保处理了所有情况
    }
}

5. 与枚举类对比

特性密封类枚举类
实例可以有多个实例每个值只有一个实例
数据可以携带数据不能携带数据(可以带属性)
类型可以是不同类型必须是同一类型
使用场景状态、结果类型固定常量集合

6. 实际应用

结果类型

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

fun fetchData(): Result<String> {
    return try {
        Result.Success("Data")
    } catch (e: Exception) {
        Result.Error(e)
    }
}

状态管理

sealed class ViewState {
    object Loading : ViewState()
    data class Success(val data: List<Item>) : ViewState()
    data class Error(val message: String) : ViewState()
}

7. 最佳实践

  1. 受限层次结构:表示受限的类层次结构
  2. 配合when使用:与when表达式配合使用
  3. 状态管理:用于状态管理和结果类型

10.2 密封类和枚举类的区别是什么?

答案:

密封类和枚举类的主要区别:

1. 实例数量

密封类

sealed class Result {
    data class Success(val data: String) : Result()
}

val result1 = Result.Success("Data1")
val result2 = Result.Success("Data2")
// 可以有多个实例

枚举类

enum class Status {
    LOADING, SUCCESS, ERROR
}

val status1 = Status.LOADING
val status2 = Status.LOADING
println(status1 === status2)  // true(同一个实例)

2. 数据携带

密封类

sealed class Result {
    data class Success(val data: String) : Result()  // 可以携带数据
}

枚举类

enum class Status(val code: Int) {
    LOADING(0),  // 可以带属性,但所有实例共享
    SUCCESS(1),
    ERROR(2)
}

3. 类型差异

密封类

sealed class Result {
    data class Success(val data: String) : Result()  // 不同类型
    data class Error(val code: Int) : Result()      // 不同类型
}

枚举类

enum class Status {
    LOADING, SUCCESS, ERROR  // 同一类型
}

4. 对比总结

特性密封类枚举类
实例可以有多个每个值一个实例
数据可以携带不同数据可以带属性但共享
类型可以是不同类型必须是同一类型
使用场景状态、结果类型固定常量集合

5. 最佳实践

  1. 需要数据使用密封类:需要携带不同数据时使用密封类
  2. 固定常量使用枚举:固定常量集合使用枚举
  3. 根据需求选择:根据实际需求选择合适的类型

10.3 密封类的使用场景有哪些?

答案:

密封类的使用场景:

1. 结果类型(Result Type)

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

fun fetchData(): Result<String> {
    return try {
        Result.Success("Data")
    } catch (e: Exception) {
        Result.Error(e)
    }
}

2. 状态管理

sealed class ViewState {
    object Loading : ViewState()
    data class Success(val items: List<Item>) : ViewState()
    data class Error(val message: String) : ViewState()
    object Empty : ViewState()
}

fun updateState(state: ViewState) {
    when (state) {
        is ViewState.Loading -> showLoading()
        is ViewState.Success -> showItems(state.items)
        is ViewState.Error -> showError(state.message)
        is ViewState.Empty -> showEmpty()
    }
}

3. 表达式树

sealed class Expr {
    data class Number(val value: Int) : Expr()
    data class Sum(val left: Expr, val right: Expr) : Expr()
    data class Product(val left: Expr, val right: Expr) : Expr()
}

fun evaluate(expr: Expr): Int {
    return when (expr) {
        is Expr.Number -> expr.value
        is Expr.Sum -> evaluate(expr.left) + evaluate(expr.right)
        is Expr.Product -> evaluate(expr.left) * evaluate(expr.right)
    }
}

4. 命令模式

sealed class Command {
    data class Add(val item: String) : Command()
    data class Remove(val id: Int) : Command()
    object Clear : Command()
}

fun execute(command: Command) {
    when (command) {
        is Command.Add -> addItem(command.item)
        is Command.Remove -> removeItem(command.id)
        is Command.Clear -> clearAll()
    }
}

5. 最佳实践

  1. 结果类型:表示操作结果
  2. 状态管理:管理UI状态
  3. 表达式树:表示表达式结构
  4. 命令模式:实现命令模式

10.4 密封类和when表达式的配合使用

答案:

密封类和when表达式的配合使用:

1. 完整性检查

编译器可以检查when表达式是否处理了所有情况:

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
}

fun process(result: Result) {
    when (result) {
        is Result.Success -> println(result.data)
        is Result.Error -> println(result.message)
        // 编译器确保处理了所有情况
    }
}

2. 智能转换

when表达式中可以智能转换:

fun process(result: Result) {
    when (result) {
        is Result.Success -> {
            // result自动转换为Result.Success
            println(result.data)  // 可以直接访问data
        }
        is Result.Error -> {
            // result自动转换为Result.Error
            println(result.message)  // 可以直接访问message
        }
    }
}

3. 不需要else

密封类的when表达式不需要else分支:

fun process(result: Result) {
    when (result) {
        is Result.Success -> { }
        is Result.Error -> { }
        // 不需要else,编译器知道所有情况
    }
}

4. 实际应用

sealed class ViewState {
    object Loading : ViewState()
    data class Success(val data: List<Item>) : ViewState()
    data class Error(val message: String) : ViewState()
}

fun updateUI(state: ViewState) {
    when (state) {
        is ViewState.Loading -> showLoading()
        is ViewState.Success -> {
            // 智能转换,可以直接访问data
            showItems(state.data)
        }
        is ViewState.Error -> {
            // 智能转换,可以直接访问message
            showError(state.message)
        }
    }
}

5. 最佳实践

  1. 配合when使用:密封类与when配合使用
  2. 利用智能转换:利用智能转换简化代码
  3. 完整性检查:让编译器检查完整性

10.5 密封类的继承限制是什么?

答案:

密封类的继承限制:

1. 子类位置限制

所有子类必须在:

  • 同一文件内
  • 或同一模块内(Kotlin 1.5+)
// 文件1
sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
}

// 文件2(同一模块)
// data class Loading : Result()  // ✅ Kotlin 1.5+可以

2. 不能直接实例化

sealed class Result { }

// val result = Result()  // ❌ 编译错误,不能直接实例化

3. 子类类型

子类可以是:

  • 数据类(data class)
  • 普通类
  • 对象(object)
sealed class Result {
    data class Success(val data: String) : Result()  // 数据类
    class Error(val message: String) : Result()     // 普通类
    object Loading : Result()                        // 对象
}

4. 实际应用

sealed class NetworkResult {
    data class Success(val data: String) : NetworkResult()
    data class Error(val code: Int, val message: String) : NetworkResult()
    object Loading : NetworkResult()
    object Timeout : NetworkResult()
}

5. 最佳实践

  1. 理解限制:理解继承限制
  2. 合理组织:在同一文件或模块内组织
  3. 使用数据类:需要数据时使用数据类

10.6 密封类在状态管理中的应用

答案:

密封类在状态管理中的应用:

1. UI状态管理

sealed class UIState {
    object Loading : UIState()
    data class Success(val data: List<Item>) : UIState()
    data class Error(val message: String) : UIState()
    object Empty : UIState()
}

class ViewModel {
    private var state: UIState = UIState.Loading
    
    fun updateState(newState: UIState) {
        state = newState
        when (state) {
            is UIState.Loading -> showLoading()
            is UIState.Success -> showData(state.data)
            is UIState.Error -> showError(state.message)
            is UIState.Empty -> showEmpty()
        }
    }
}

2. 网络请求状态

sealed class RequestState<T> {
    object Idle : RequestState<Nothing>()
    object Loading : RequestState<Nothing>()
    data class Success<T>(val data: T) : RequestState<T>()
    data class Error(val exception: Exception) : RequestState<Nothing>()
}

fun handleState(state: RequestState<String>) {
    when (state) {
        is RequestState.Idle -> { }
        is RequestState.Loading -> showLoading()
        is RequestState.Success -> showData(state.data)
        is RequestState.Error -> showError(state.exception)
    }
}

3. 表单验证状态

sealed class ValidationResult {
    object Valid : ValidationResult()
    data class Invalid(val errors: List<String>) : ValidationResult()
}

fun validateForm(): ValidationResult {
    val errors = mutableListOf<String>()
    // 验证逻辑
    return if (errors.isEmpty()) {
        ValidationResult.Valid
    } else {
        ValidationResult.Invalid(errors)
    }
}

4. 最佳实践

  1. 状态管理:用于管理应用状态
  2. UI状态:管理UI状态
  3. 配合when:与when表达式配合使用

10.7 密封类与Result类型的实现

答案:

使用密封类实现Result类型:

1. 基本Result类型

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

fun fetchData(): Result<String> {
    return try {
        Result.Success("Data")
    } catch (e: Exception) {
        Result.Error(e)
    }
}

2. 扩展函数

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
    
    fun isSuccess(): Boolean = this is Success
    fun isError(): Boolean = this is Error
    
    fun getOrNull(): T? = when (this) {
        is Success -> data
        is Error -> null
    }
    
    fun getOrElse(default: T): T = when (this) {
        is Success -> data
        is Error -> default
    }
}

3. 实际应用

fun processResult(result: Result<String>) {
    when (result) {
        is Result.Success -> {
            println("Success: ${result.data}")
        }
        is Result.Error -> {
            println("Error: ${result.exception.message}")
        }
    }
}

val data = fetchData()
val value = data.getOrElse("Default")

4. 最佳实践

  1. 错误处理:使用Result类型处理错误
  2. 扩展函数:添加扩展函数提高易用性
  3. 类型安全:利用类型安全处理结果

第十一章:Kotlin 内联函数面试题答案

1. 内联函数基础

11.1 Kotlin的内联函数(Inline Function)是什么?

答案:

内联函数(Inline Function)使用inline关键字标记,函数体在调用处展开,而不是函数调用。

1. 基本概念

内联函数在编译时将函数体直接插入到调用处,消除函数调用的开销。

2. 基本语法

inline fun <T> measureTime(block: () -> T): T {
    val start = System.currentTimeMillis()
    val result = block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
    return result
}

// 使用
val result = measureTime {
    // 执行代码
    calculate()
}

3. 编译时展开

// Kotlin代码
inline fun <T> process(block: () -> T): T {
    return block()
}

val result = process { 42 }

// 编译后(伪代码)
val result = 42  // 直接展开,无函数调用

4. 优势

  1. 消除函数调用开销:减少函数调用开销
  2. 支持reified:支持reified类型参数
  3. Lambda优化:消除Lambda对象创建

5. 实际应用

性能测量

inline fun <T> measureTime(block: () -> T): T {
    val start = System.currentTimeMillis()
    val result = block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
    return result
}

val result = measureTime {
    expensiveOperation()
}

6. 最佳实践

  1. 高阶函数使用:高阶函数使用内联
  2. 性能敏感:性能敏感场景使用
  3. 避免过度使用:不要过度使用内联

11.2 内联函数的实现原理是什么?

答案:

内联函数的实现原理:

1. 编译时展开

内联函数在编译时将函数体直接插入到调用处:

// Kotlin代码
inline fun <T> process(block: () -> T): T {
    println("Processing")
    return block()
}

val result = process { 42 }

// 编译后(伪代码)
println("Processing")
val result = 42  // 直接展开

2. Lambda展开

Lambda表达式也被展开:

// Kotlin代码
inline fun <T> process(block: () -> T): T {
    return block()
}

val result = process { 
    println("Lambda")
    42
}

// 编译后(伪代码)
println("Lambda")
val result = 42

3. 性能影响

  • 消除函数调用:无函数调用开销
  • 消除Lambda对象:无Lambda对象创建
  • 代码膨胀:可能增加代码大小

4. 总结

  • 编译时展开:函数体在编译时展开
  • 消除开销:消除函数调用和Lambda对象开销
  • 代码膨胀:可能增加代码大小

11.3 内联函数的优缺点是什么?

答案:

内联函数的优缺点:

1. 优点

消除函数调用开销

inline fun process(block: () -> Unit) {
    block()
}

// 无函数调用开销

消除Lambda对象

inline fun <T> map(list: List<T>, transform: (T) -> T): List<T> {
    return list.map(transform)
}

// Lambda不创建对象

支持reified

inline fun <reified T> getType(): Class<T> {
    return T::class.java
}

2. 缺点

代码膨胀

// 如果内联函数被多次调用,代码会重复
inline fun process(block: () -> Unit) {
    block()
}

process { }  // 展开一次
process { }  // 展开一次
// 代码重复

不能递归

// ❌ 不能内联递归函数
inline fun factorial(n: Int): Int {
    return if (n <= 1) 1 else n * factorial(n - 1)  // 错误
}

3. 最佳实践

  1. 高阶函数使用:高阶函数使用内联
  2. 避免大函数:避免内联大函数
  3. 避免递归:不能内联递归函数

11.4 什么时候使用内联函数?

答案:

使用内联函数的场景:

1. 高阶函数

// ✅ 高阶函数使用内联
inline fun <T> process(list: List<T>, operation: (T) -> T): List<T> {
    return list.map(operation)
}

2. 性能敏感场景

// ✅ 性能敏感场景
inline fun <T> measureTime(block: () -> T): T {
    val start = System.currentTimeMillis()
    val result = block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
    return result
}

3. 需要reified

// ✅ 需要reified时
inline fun <reified T> filterIsInstance(list: List<*>): List<T> {
    return list.filterIsInstance<T>()
}

4. 避免使用的场景

大函数

// ❌ 避免内联大函数
inline fun largeFunction() {
    // 大量代码...
}

递归函数

// ❌ 不能内联递归函数
inline fun factorial(n: Int): Int {
    return if (n <= 1) 1 else n * factorial(n - 1)
}

5. 最佳实践

  1. 高阶函数使用:高阶函数使用内联
  2. 性能敏感使用:性能敏感场景使用
  3. 避免大函数:避免内联大函数

2. 内联函数类型

11.5 noinline关键字的作用是什么?

答案:

noinline关键字用于标记内联函数中不需要内联的Lambda参数。

1. 基本用法

inline fun process(
    inlineBlock: () -> Unit,
    noinline noInlineBlock: () -> Unit
) {
    inlineBlock()  // 内联
    // noInlineBlock需要作为函数对象传递
    store(noInlineBlock)  // 可以存储
}

fun store(block: () -> Unit) {
    // 存储block
}

2. 使用场景

需要存储Lambda

inline fun process(
    block: () -> Unit,
    noinline storedBlock: () -> Unit
) {
    block()  // 内联
    callbacks.add(storedBlock)  // 需要存储,不能内联
}

传递给非内联函数

inline fun process(
    block: () -> Unit,
    noinline callback: () -> Unit
) {
    block()  // 内联
    nonInlineFunction(callback)  // 传递给非内联函数
}

3. 最佳实践

  1. 需要存储时使用noinline:需要存储Lambda时使用
  2. 传递给非内联函数:传递给非内联函数时使用
  3. 理解限制:理解noinline的限制

11.6 crossinline关键字的作用是什么?

答案:

crossinline关键字用于标记内联函数中不能直接return的Lambda参数。

1. 基本用法

inline fun process(crossinline block: () -> Unit) {
    Runnable {
        block()  // 在另一个Lambda中使用
    }.run()
}

2. 问题场景

直接return的问题

inline fun process(block: () -> Unit) {
    Runnable {
        block()  // 如果block中有return,会从process返回
    }.run()
}

process {
    return  // ❌ 编译错误,不能从process返回
}

使用crossinline

inline fun process(crossinline block: () -> Unit) {
    Runnable {
        block()  // ✅ 可以使用,但不能return
    }.run()
}

process {
    // return  // ❌ 仍然不能return
    return@process  // ✅ 可以使用标签return
}

3. 实际应用

inline fun runOnBackground(crossinline block: () -> Unit) {
    Thread {
        block()  // 在另一个线程中执行
    }.start()
}

4. 最佳实践

  1. 嵌套Lambda使用crossinline:在嵌套Lambda中使用时使用
  2. 不能直接return:理解不能直接return的限制
  3. 使用标签return:需要使用标签return

11.7 reified和内联函数的关系是什么?

答案:

reified必须配合inline使用:

1. 基本关系

reified类型参数只能在inline函数中使用:

// ✅ 正确
inline fun <reified T> getType(): Class<T> {
    return T::class.java
}

// ❌ 错误
fun <reified T> getType(): Class<T> {  // 必须inline
    return T::class.java
}

2. 实现原理

reified通过内联展开实现:

// Kotlin代码
inline fun <reified T> getType(): Class<T> {
    return T::class.java
}

getType<String>()

// 编译后(伪代码)
String::class.java  // 直接替换为具体类型

3. 实际应用

inline fun <reified T> List<*>.filterIsInstance(): List<T> {
    return filterIsInstance<T>()
}

val list: List<Any> = listOf(1, "a", 2, "b")
val numbers = list.filterIsInstance<Int>()  // [1, 2]

4. 最佳实践

  1. 必须inline:reified必须配合inline
  2. 运行时类型:需要运行时类型信息时使用
  3. 理解原理:理解内联展开的原理

11.8 内联函数的性能影响

答案:

内联函数的性能影响:

1. 消除函数调用开销

inline fun process(block: () -> Unit) {
    block()
}

// 无函数调用开销

2. 消除Lambda对象

inline fun <T> map(list: List<T>, transform: (T) -> T): List<T> {
    return list.map(transform)
}

// Lambda不创建对象

3. 代码膨胀

多次调用可能增加代码大小:

inline fun process(block: () -> Unit) {
    block()
}

process { }  // 展开
process { }  // 展开
// 代码重复

4. 最佳实践

  1. 合理使用:合理使用内联函数
  2. 避免大函数:避免内联大函数
  3. 性能测试:必要时进行性能测试

11.9 内联函数的使用限制

答案:

内联函数的使用限制:

1. 不能递归

// ❌ 不能内联递归函数
inline fun factorial(n: Int): Int {
    return if (n <= 1) 1 else n * factorial(n - 1)
}

2. 不能是虚函数

open class Base {
    // ❌ 不能是open的内联函数
    // inline open fun method() { }
}

3. 不能有函数类型参数作为默认值

// ❌ 不能有函数类型参数作为默认值
// inline fun process(block: () -> Unit = { }) { }

4. 最佳实践

  1. 理解限制:理解内联函数的限制
  2. 避免递归:不能内联递归函数
  3. 合理使用:根据限制合理使用

11.10 内联函数与Lambda的关系

答案:

内联函数与Lambda的关系:

1. Lambda优化

内联函数可以消除Lambda对象创建:

// 非内联
fun process(block: () -> Unit) {
    block()  // 创建Lambda对象
}

// 内联
inline fun process(block: () -> Unit) {
    block()  // 不创建Lambda对象,直接展开
}

2. 实际应用

inline fun <T> process(list: List<T>, operation: (T) -> T): List<T> {
    return list.map(operation)
}

// Lambda不创建对象
val result = process(listOf(1, 2, 3)) { it * 2 }

3. 最佳实践

  1. 高阶函数使用内联:高阶函数使用内联优化Lambda
  2. 理解优化:理解Lambda优化机制
  3. 性能考虑:性能敏感场景使用内联