第六章: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. 扩展函数的特点
- 不修改原类:不需要修改原始类的源代码
- 语法简洁:调用方式像成员函数
- 类型安全:编译时类型检查
- 作用域限制:需要导入才能使用
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. 最佳实践
- 合理使用:不要过度使用扩展函数
- 命名清晰:使用有意义的函数名
- 避免冲突:注意与成员函数的优先级
- 文档说明:为扩展函数添加文档注释
- 组织代码:按功能组织扩展函数
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. 性能影响
扩展函数的性能:
- 调用开销:静态方法调用,开销与普通函数相同
- 无虚函数表:不参与虚函数调用
- 编译时优化:可以被内联优化
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. 使用建议
使用扩展函数的场景:
- 为第三方库类添加功能
- 为现有类添加工具函数
- 不想修改原始类时
- 按功能组织代码时
使用成员函数的场景:
- 类的核心功能
- 需要访问私有成员时
- 需要多态时
- 需要重写时
9. 最佳实践
- 优先成员函数:类的核心功能使用成员函数
- 合理使用扩展:为现有类添加功能时使用扩展函数
- 避免冲突:注意与成员函数的优先级
- 明确意图:扩展函数用于添加功能,不是修改类
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. 最佳实践
- 避免命名冲突:使用有意义的、唯一的函数名
- 明确导入:使用明确导入避免冲突
- 避免覆盖成员:不要定义与成员函数同名的扩展函数
- 文档说明:说明扩展函数的作用和优先级
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. 最佳实践
- 按功能组织:将相关扩展函数放在同一文件
- 命名规范:使用有意义的函数名
- 避免冲突:避免与成员函数冲突
- 文档说明:为扩展函数添加文档
- 合理使用:不要过度使用扩展函数
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. 可变扩展属性
可变扩展属性需要可变的接收者类型(如StringBuilder、MutableList等):
// 可变属性(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. 最佳实践
- 只读优先:优先使用只读扩展属性
- 计算属性:用于计算值而不是存储值
- 语义清晰:属性名应该表示"是什么"而不是"做什么"
- 避免副作用: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. 性能
扩展属性的性能与扩展函数相同:
- 调用开销:静态方法调用,开销相同
- 无存储开销:不能存储状态,无额外内存
- 编译时优化:可以被内联优化
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. 最佳实践
- 组织代码:将相关扩展函数放在同一文件
- 明确导入:使用明确导入避免冲突
- 可见性:合理使用可见性修饰符
- 文档说明:说明扩展函数的位置和导入方式
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. 最佳实践
- 明确导入:使用明确导入提高可读性
- 避免冲突:使用别名解决冲突
- 组织代码:按功能组织扩展函数
4. 作用域函数
6.10 Kotlin的作用域函数(Scope Functions)是什么?
答案:
作用域函数(Scope Functions)是Kotlin标准库提供的五个函数(let、run、with、apply、also),用于在对象上下文中执行代码块。
1. 基本概念
作用域函数提供了一种简洁的方式来处理对象,在代码块内可以访问对象,而不需要重复写对象名。
2. 五个作用域函数
- let:接受对象,返回Lambda结果
- run:接受对象,返回Lambda结果(使用this)
- with:非扩展函数,返回Lambda结果(使用this)
- apply:接受对象,返回对象本身
- also:接受对象,返回对象本身(使用it)
3. 基本对比
| 函数 | 对象引用 | 返回值 | 使用场景 |
|---|---|---|---|
| let | it | Lambda结果 | 空安全调用、转换 |
| run | this | Lambda结果 | 对象配置、计算 |
| with | this | Lambda结果 | 非空对象配置 |
| apply | this | 对象本身 | 对象初始化 |
| also | it | 对象本身 | 附加操作 |
4. 示例对比
val person = Person().apply {
name = "Kotlin"
age = 30
}
val result = person.let {
it.name // 返回name
}
person.also {
println(it.name) // 打印但返回person
}
5. 选择指南
- 需要返回值:使用
let或run - 不需要返回值:使用
apply或also - 空安全:使用
let - 对象初始化:使用
apply - 附加操作:使用
also - 配置对象:使用
run或with
6. 最佳实践
- 明确目的:根据需求选择合适的函数
- 避免嵌套:不要过度嵌套作用域函数
- 保持简洁:作用域函数应该让代码更简洁
- 可读性优先:如果让代码更难读,考虑使用普通代码
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. 详细对比表
| 函数 | 对象引用 | 返回值 | 空安全 | 使用场景 |
|---|---|---|---|---|
| let | it | Lambda结果 | 支持 | 空安全调用、转换 |
| run | this | Lambda结果 | 支持 | 对象配置、计算 |
| with | this | Lambda结果 | 不支持 | 非空对象配置 |
| apply | this | 对象本身 | 支持 | 对象初始化 |
| also | it | 对象本身 | 支持 | 附加操作、日志 |
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. 最佳实践
- 空安全时使用:处理可空对象时优先使用
- 值转换时使用:需要转换值时使用
- 链式调用时使用:需要链式转换时使用
- 避免嵌套过深:不要过度嵌套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. 最佳实践
- 配置对象:需要配置多个属性时使用
- 需要this:使用this引用更自然时
- 计算返回:需要计算并返回结果时
- 扩展形式:可空对象时使用扩展形式
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. 最佳实践
- 非空对象:确定对象不为null时使用
- 多个属性:需要配置多个属性时使用
- 明确对象:对象名明确时使用with更清晰
- 避免空安全:需要空安全时使用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. 最佳实践
- 对象初始化:创建并配置对象时使用
- Builder模式:实现Builder模式时使用
- 链式配置:需要链式配置时使用
- 需要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. 最佳实践
- 附加操作:需要执行附加操作时使用
- 日志调试:记录或调试时使用
- 副作用操作:需要副作用但保留原对象时使用
- 使用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. 类型对比
| 函数 | 返回值类型 | 示例 |
|---|---|---|
| let | Lambda结果类型 | String? → Int? |
| run | Lambda结果类型 | Person → String |
| with | Lambda结果类型 | Person → String |
| apply | 对象本身类型 | Person → Person |
| also | 对象本身类型 | Person → Person |
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. 最佳实践
- 需要转换:使用let/run/with返回转换结果
- 需要对象:使用apply/also返回对象本身
- 链式调用:根据返回值类型选择
- 类型明确:确保返回值类型符合预期
6.18 作用域函数的上下文对象(this、it)区别
答案:
作用域函数中this和it的区别:
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. 区别对比
| 函数 | 对象引用 | 是否可省略 | 示例 |
|---|---|---|---|
| let | it | 否 | it.name |
| run | this | 是 | name 或 this.name |
| with | this | 是 | name 或 this.name |
| apply | this | 是 | name 或 this.name |
| also | it | 否 | it.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. 最佳实践
- this:配置对象时使用,代码更简洁
- it:需要明确引用时使用,避免混淆
- 一致性:保持代码风格一致
- 可读性:优先考虑可读性
6.19 作用域函数的选择指南
答案:
选择合适作用域函数的指南:
1. 决策树
需要返回值?
├─ 是 → 对象为null?
│ ├─ 可能 → 使用let
│ └─ 否 → 需要this?
│ ├─ 是 → 使用run或with
│ └─ 否 → 使用let
└─ 否 → 需要this?
├─ 是 → 使用apply
└─ 否 → 使用also
2. 快速参考
| 需求 | 推荐函数 | 示例 |
|---|---|---|
| 空安全调用 | let | obj?.let { } |
| 值转换 | let | obj?.let { transform(it) } |
| 对象配置(返回结果) | run | obj.run { result } |
| 对象配置(非空) | with | with(obj) { } |
| 对象初始化 | apply | Obj().apply { } |
| 附加操作 | also | obj.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. 最佳实践总结
- 空安全:使用
let - 初始化:使用
apply - 配置计算:使用
run或with - 附加操作:使用
also - 避免嵌套:不要过度嵌套
- 保持简洁:让代码更简洁,不要更复杂
第七章: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. 优势
- 组合优于继承:遵循组合优于继承原则
- 灵活性:运行时选择委托对象
- 多接口:可以实现多个接口
- 代码复用:复用现有实现
7. 最佳实践
- 优先委托:优先使用委托而非继承
- 接口委托:委托给接口而非具体类
- 选择性重写:只重写需要的方法
- 明确意图:使用委托明确表示组合关系
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. 最佳实践
- 优先委托:优先使用委托
- 真正继承:只有真正的is-a关系才使用继承
- 接口委托:委托给接口提高灵活性
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. 最佳实践
- 使用标准委托:优先使用lazy、observable等
- 自定义委托:需要特殊逻辑时自定义
- 理解原理:理解委托的实现机制
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对比
| 特性 | lazy | lateinit |
|---|---|---|
| 类型 | val | var |
| 初始化时机 | 首次访问 | 手动初始化 |
| 线程安全 | 默认是 | 不保证 |
| 基本类型 | 支持 | 不支持 |
6. 最佳实践
- 昂贵计算使用lazy:计算成本高时使用
- 线程安全考虑:多线程环境使用默认模式
- val属性使用lazy:只读属性使用lazy
7.6 如何自定义属性委托?
答案:
自定义属性委托的方法:
1. 基本要求
委托类需要实现getValue和setValue操作符:
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. 可变属性委托
可变属性需要getValue和setValue:
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. 最佳实践
- 实现操作符:正确实现getValue/setValue
- 类型安全:使用泛型提高类型安全
- 错误处理:添加适当的验证和错误处理
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. 最佳实践
- 优先使用标准委托:使用标准委托而非自定义
- 根据需求选择:根据需求选择合适的委托
- 理解特性:理解每个委托的特性
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. 最佳实践
- 基本类型使用:基本类型延迟初始化时使用
- 确保初始化:确保在使用前初始化
- 对象类型用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. 最佳实践
- 监听变化:需要监听属性变化时使用
- 副作用处理:在回调中处理副作用
- 避免复杂逻辑:回调中避免复杂逻辑
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对比
| 特性 | observable | vetoable |
|---|---|---|
| 作用 | 监听变化 | 验证并可能拒绝 |
| 返回值 | Unit | Boolean |
| 使用场景 | 通知变化 | 验证值 |
5. 最佳实践
- 验证使用:需要验证值时使用
- 返回Boolean:回调必须返回Boolean
- 明确规则:验证规则要明确
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. 最佳实践
- 合理使用:不要过度使用委托
- 性能敏感:性能敏感场景谨慎使用
- 标准委托:优先使用标准委托(可能优化)
- 测试验证:必要时进行性能测试
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. 最佳实践
- 延迟初始化:昂贵资源使用lazy
- 属性监听:需要监听变化时使用observable
- 属性验证:需要验证时使用vetoable
- 自定义委托:特殊需求时自定义
第八章: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. 最佳实践
- 使用泛型:提高代码复用性
- 类型安全:利用类型检查
- 类型推断:利用类型推断简化代码
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. 对比总结
| 特性 | Kotlin | Java |
|---|---|---|
| 语法 | out/in | extends/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. 最佳实践
- 使用有意义的名称:多个参数时使用有意义的名称
- 遵循约定:遵循常见的命名约定
- 添加约束:需要时添加类型约束
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使用out和in关键字:
// 协变(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. 最佳实践
- 理解型变:理解三种型变的概念
- 使用out/in:根据需求使用out/in
- 默认不变:默认是不变,需要时使用型变
8.5 协变(Covariance)是什么?
答案:
协变(Covariance)表示泛型类型与类型参数同方向变化。
1. 基本概念
如果A是B的子类型,那么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. 最佳实践
- 只读使用协变:只读集合使用协变
- 使用out关键字:明确标记协变
- 理解限制:理解协变的限制
8.6 逆变(Contravariance)是什么?
答案:
逆变(Contravariance)表示泛型类型与类型参数反方向变化。
1. 基本概念
如果A是B的子类型,那么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. 最佳实践
- 只写使用逆变:只写集合使用逆变
- 使用in关键字:明确标记逆变
- 理解限制:理解逆变的限制
8.7 不变(Invariance)是什么?
答案:
不变(Invariance)表示泛型类型之间没有子类型关系。
1. 基本概念
即使A是B的子类型,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. 最佳实践
- 默认不变:大多数情况使用不变
- 需要时使用型变:需要子类型关系时使用out/in
- 理解关系:理解三种型变的关系
8.8 out和in关键字的作用是什么?
答案:
out和in关键字的作用:
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. 最佳实践
- 理解位置规则:理解输入输出位置
- 使用out/in:根据需求使用
- 明确意图:使用关键字明确型变意图
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. 最佳实践
- 理解限制:理解类型擦除的限制
- 使用reified:需要类型信息时使用reified
- 使用星投影:运行时检查使用星投影
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. 最佳实践
- 使用reified:需要类型信息时使用reified
- 内联函数:reified必须配合inline使用
- 理解限制:理解类型擦除的根本限制
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. 最佳实践
- 配合inline使用:reified必须配合inline
- 运行时类型信息:需要运行时类型信息时使用
- 理解原理:理解内联展开的原理
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. 最佳实践
- 使用上界:需要约束类型时使用上界
- 多个约束:使用where子句
- 理解下界:通过逆变理解下界概念
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. 最佳实践
- 多个约束使用where:需要多个约束时使用
- 明确约束:明确类型约束
- 合理使用:不要过度约束
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. 最佳实践
- 合理约束:根据需求添加约束
- 避免过度约束:不要添加不必要的约束
- 使用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. 最佳实践
- 运行时检查:运行时类型检查时使用
- 理解限制:理解星投影的限制
- 需要类型时使用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. 最佳实践
- 利用泛型:使用泛型提高类型安全
- 类型推断:利用类型推断简化代码
- 明确类型:复杂情况明确指定类型
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. 最佳实践
- 利用编译时检查:利用编译时类型检查
- 明确类型:明确指定类型提高安全性
- 理解擦除:理解类型擦除的影响
第九章: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. 最佳实践
- 实现接口:需要实现接口时使用
- 一次性使用:只使用一次的对象
- 访问外部变量:需要访问外部变量时使用
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. 最佳实践
- Kotlin更灵活:对象表达式功能更强大
- 理解差异:理解两者的差异
- 合理使用:根据需求选择合适的方案
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. 最佳实践
- 一次性使用:只使用一次的对象
- 实现接口:需要实现接口时使用
- 访问外部变量:需要访问外部变量时使用
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. 最佳实践
- 单例使用:需要单例时使用对象声明
- 工具类:工具类使用对象声明
- 全局配置:全局配置使用对象声明
- 避免滥用:不要过度使用单例
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. 最佳实践
- 使用对象声明:需要单例时使用对象声明
- 理解初始化:理解延迟初始化机制
- 线程安全:对象声明是线程安全的
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. 最佳实践
- 单例使用对象声明:需要单例时使用对象声明
- 普通类使用类:需要多个实例时使用类
- 明确意图:根据需求选择合适的类型
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. 最佳实践
- 替代静态成员:使用伴生对象替代Java静态成员
- 工厂方法:实现工厂模式
- 常量定义:定义类相关常量
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. 最佳实践
- 理解差异:理解本质差异
- 利用优势:利用伴生对象的优势
- 替代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. 最佳实践
- 工厂方法:实现工厂模式
- 常量定义:定义类相关常量
- 工具方法:定义类相关的工具方法
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. 最佳实践
- 理解时机:理解延迟初始化机制
- 线程安全:初始化是线程安全的
- 避免复杂逻辑:初始化中避免复杂逻辑
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. 最佳实践
- 私有构造函数:使用私有构造函数
- 工厂方法:提供工厂方法创建实例
- 实现接口:可以实现工厂接口
第十章: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. 最佳实践
- 受限层次结构:表示受限的类层次结构
- 配合when使用:与when表达式配合使用
- 状态管理:用于状态管理和结果类型
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. 最佳实践
- 需要数据使用密封类:需要携带不同数据时使用密封类
- 固定常量使用枚举:固定常量集合使用枚举
- 根据需求选择:根据实际需求选择合适的类型
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. 最佳实践
- 结果类型:表示操作结果
- 状态管理:管理UI状态
- 表达式树:表示表达式结构
- 命令模式:实现命令模式
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. 最佳实践
- 配合when使用:密封类与when配合使用
- 利用智能转换:利用智能转换简化代码
- 完整性检查:让编译器检查完整性
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. 最佳实践
- 理解限制:理解继承限制
- 合理组织:在同一文件或模块内组织
- 使用数据类:需要数据时使用数据类
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. 最佳实践
- 状态管理:用于管理应用状态
- UI状态:管理UI状态
- 配合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. 最佳实践
- 错误处理:使用Result类型处理错误
- 扩展函数:添加扩展函数提高易用性
- 类型安全:利用类型安全处理结果
第十一章: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. 优势
- 消除函数调用开销:减少函数调用开销
- 支持reified:支持reified类型参数
- 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. 最佳实践
- 高阶函数使用:高阶函数使用内联
- 性能敏感:性能敏感场景使用
- 避免过度使用:不要过度使用内联
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. 最佳实践
- 高阶函数使用:高阶函数使用内联
- 避免大函数:避免内联大函数
- 避免递归:不能内联递归函数
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. 最佳实践
- 高阶函数使用:高阶函数使用内联
- 性能敏感使用:性能敏感场景使用
- 避免大函数:避免内联大函数
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. 最佳实践
- 需要存储时使用noinline:需要存储Lambda时使用
- 传递给非内联函数:传递给非内联函数时使用
- 理解限制:理解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. 最佳实践
- 嵌套Lambda使用crossinline:在嵌套Lambda中使用时使用
- 不能直接return:理解不能直接return的限制
- 使用标签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. 最佳实践
- 必须inline:reified必须配合inline
- 运行时类型:需要运行时类型信息时使用
- 理解原理:理解内联展开的原理
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. 最佳实践
- 合理使用:合理使用内联函数
- 避免大函数:避免内联大函数
- 性能测试:必要时进行性能测试
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. 最佳实践
- 理解限制:理解内联函数的限制
- 避免递归:不能内联递归函数
- 合理使用:根据限制合理使用
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. 最佳实践
- 高阶函数使用内联:高阶函数使用内联优化Lambda
- 理解优化:理解Lambda优化机制
- 性能考虑:性能敏感场景使用内联