第三章:Kotlin 类和对象面试题答案
1. 类基础
3.1 Kotlin的类声明方式是什么?
答案:
Kotlin的类声明方式:
1. 基本语法
class ClassName {
// 类体
}
2. 空类
// 空类
class EmptyClass
// 或
class EmptyClass {
}
3. 带属性的类
class Person {
var name: String = ""
var age: Int = 0
}
4. 带方法的类
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
fun multiply(a: Int, b: Int): Int {
return a * b
}
}
5. 主构造函数
// 主构造函数(简洁形式)
class Person(val name: String, val age: Int)
// 等价于
class Person(name: String, age: Int) {
val name: String = name
val age: Int = age
}
6. 主构造函数带初始化
class Person(val name: String, val age: Int) {
init {
require(age >= 0) { "Age cannot be negative" }
println("Person created: $name")
}
}
7. 次构造函数
class Person(val name: String, val age: Int) {
// 次构造函数
constructor(name: String) : this(name, 0)
constructor() : this("Unknown", 0)
}
8. 与Java对比
| Kotlin | Java等价 |
|---|---|
class Person | public class Person { } |
class Person(val name: String) | public class Person { private String name; public Person(String name) { this.name = name; } } |
| 空类 | public class EmptyClass { } |
9. 实际应用
简单类
class User {
var name: String = ""
var email: String = ""
}
带构造函数
class User(val name: String, val email: String) {
init {
require(email.contains("@")) { "Invalid email" }
}
}
10. 最佳实践
- 使用主构造函数:优先使用主构造函数
- 简洁声明:利用Kotlin的简洁语法
- 初始化逻辑:使用init块处理初始化
- 属性声明:在主构造函数中声明属性
3.2 Kotlin的构造函数有哪些类型?
答案:
Kotlin有两种构造函数:主构造函数和次构造函数。
1. 主构造函数(Primary Constructor)
主构造函数是类头的一部分:
// 主构造函数
class Person(val name: String, val age: Int)
// 等价于
class Person(name: String, age: Int) {
val name: String = name
val age: Int = age
}
2. 主构造函数特性
- 在类名后声明
- 可以声明属性(
val/var) - 可以有默认参数
- 可以有可见性修饰符
// 带默认参数
class Person(val name: String, val age: Int = 0)
// 私有主构造函数
class Person private constructor(val name: String)
3. 次构造函数(Secondary Constructor)
次构造函数在类体内声明:
class Person(val name: String, val age: Int) {
// 次构造函数
constructor(name: String) : this(name, 0)
constructor() : this("Unknown", 0)
}
4. 次构造函数规则
- 必须调用主构造函数(如果存在)
- 或调用另一个次构造函数
- 使用
constructor关键字
class Person(val name: String, val age: Int) {
constructor(name: String) : this(name, 0) // 调用主构造函数
constructor() : this("Unknown") // 调用次构造函数
}
5. 主构造函数 vs 次构造函数
| 特性 | 主构造函数 | 次构造函数 |
|---|---|---|
| 位置 | 类头 | 类体 |
| 声明 | 类名后 | constructor关键字 |
| 属性声明 | 可以(val/var) | 不能 |
| 默认参数 | 支持 | 不支持 |
| 调用 | 自动调用 | 需要显式调用 |
| 数量 | 最多1个 | 可以有多个 |
6. 实际应用
主构造函数(推荐)
class User(val name: String, val email: String, val age: Int = 0) {
init {
require(age >= 0) { "Age cannot be negative" }
}
}
次构造函数(特殊情况)
class User(val name: String, val email: String) {
constructor(name: String) : this(name, "$name@example.com")
constructor() : this("Unknown")
}
7. 最佳实践
- 优先主构造函数:大多数情况使用主构造函数
- 使用默认参数:替代多个次构造函数
- 次构造函数用于特殊情况:需要特殊初始化逻辑时使用
3.3 主构造函数和次构造函数的区别是什么?
答案:
主构造函数和次构造函数的主要区别:
1. 声明位置
主构造函数
// 在类头声明
class Person(val name: String, val age: Int)
次构造函数
// 在类体声明
class Person(val name: String, val age: Int) {
constructor(name: String) : this(name, 0)
}
2. 属性声明
主构造函数
// 可以直接声明属性
class Person(val name: String, val age: Int)
次构造函数
// 不能声明属性,只能调用主构造函数
class Person(val name: String, val age: Int) {
constructor(name: String) : this(name, 0) // 不能声明属性
}
3. 调用方式
主构造函数
// 自动调用
val person = Person("Kotlin", 30)
次构造函数
// 需要显式调用
val person = Person("Kotlin") // 调用次构造函数
4. 默认参数
主构造函数
// 支持默认参数
class Person(val name: String, val age: Int = 0)
次构造函数
// 不支持默认参数
class Person(val name: String, val age: Int) {
constructor(name: String) : this(name, 0) // 需要手动处理
}
5. 数量限制
- 主构造函数:最多1个
- 次构造函数:可以有多个
6. 初始化顺序
class Person(val name: String, val age: Int) {
init {
println("Init block 1")
}
constructor(name: String) : this(name, 0) {
println("Secondary constructor")
}
init {
println("Init block 2")
}
}
// 调用次构造函数时:
// 1. 先调用主构造函数
// 2. 执行所有init块
// 3. 执行次构造函数体
7. 实际应用
主构造函数(推荐)
class User(val name: String, val email: String, val age: Int = 0)
次构造函数(特殊情况)
class User(val name: String, val email: String) {
// 从JSON创建
constructor(json: JsonObject) : this(
json.getString("name"),
json.getString("email")
)
}
8. 最佳实践
- 优先主构造函数:使用主构造函数和默认参数
- 次构造函数用于特殊情况:需要特殊初始化时使用
- 避免过度使用次构造函数:可以用默认参数替代
3.4 Kotlin的init代码块是什么?
答案:
init代码块用于在对象创建时执行初始化逻辑。
1. 基本语法
class Person(val name: String, val age: Int) {
init {
// 初始化代码
require(age >= 0) { "Age cannot be negative" }
println("Person created: $name")
}
}
2. 多个init块
可以有多个init块,按顺序执行:
class Person(val name: String, val age: Int) {
init {
println("Init block 1")
}
val description = "Person: $name"
init {
println("Init block 2")
println(description)
}
}
3. 执行顺序
class Person(val name: String, val age: Int) {
// 1. 主构造函数参数初始化
// 2. 属性初始化
val firstName = name.split(" ")[0]
// 3. init块执行
init {
println("Name: $name")
}
// 4. 次构造函数体执行(如果使用次构造函数)
}
4. 实际应用
参数验证
class User(val email: String, val age: Int) {
init {
require(email.contains("@")) { "Invalid email" }
require(age >= 0) { "Age cannot be negative" }
}
}
初始化计算
class Rectangle(val width: Int, val height: Int) {
val area: Int
init {
area = width * height
println("Rectangle created: ${width}x${height}, area=$area")
}
}
5. 与Java对比
Kotlin
class Person(val name: String) {
init {
println("Created: $name")
}
}
Java
public class Person {
private String name;
public Person(String name) {
this.name = name;
System.out.println("Created: " + name);
}
}
6. 最佳实践
- 参数验证:在init块中验证参数
- 初始化逻辑:执行初始化计算
- 日志记录:记录对象创建
- 避免复杂逻辑:保持init块简洁
3.5 Kotlin的类属性有哪些特性?
答案:
Kotlin类属性的特性:
1. 属性声明
class Person {
// 可变属性
var name: String = ""
// 不可变属性
val age: Int = 0
// 延迟初始化
lateinit var email: String
// 懒加载
val description: String by lazy {
"Person description"
}
}
2. 自动getter/setter
Kotlin自动生成getter和setter:
class Person {
var name: String = ""
// 自动生成:
// fun getName(): String = name
// fun setName(value: String) { name = value }
}
3. 自定义getter/setter
class Person {
var name: String = ""
get() = field.uppercase() // 自定义getter
set(value) {
field = value.trim() // 自定义setter
}
}
4. 只读属性
class Person(val name: String) {
// val属性只有getter,没有setter
val age: Int = 0
}
5. 计算属性
class Rectangle(val width: Int, val height: Int) {
val area: Int
get() = width * height // 计算属性
}
6. 延迟初始化
class Person {
lateinit var name: String // 延迟初始化(仅var)
fun init() {
name = "Kotlin"
}
}
7. 懒加载
class Person {
val expensiveValue: String by lazy {
// 第一次访问时计算
calculateExpensiveValue()
}
}
8. 与Java对比
| Kotlin | Java等价 |
|---|---|
var name: String | private String name; + getter/setter |
val age: Int | private final int age; + getter |
| 自定义getter/setter | 手动实现getter/setter |
9. 最佳实践
- 优先使用val:除非需要修改
- 利用自动getter/setter:减少样板代码
- 自定义访问器:需要特殊逻辑时自定义
- 延迟初始化:使用lateinit或lazy
3.6 Kotlin的初始化顺序是什么?
答案:
Kotlin对象初始化的执行顺序:
1. 基本顺序
1. 主构造函数参数初始化
2. 类体中属性初始化(按声明顺序)
3. init块执行(按声明顺序)
4. 次构造函数体执行(如果使用次构造函数)
2. 详细示例
class Person(val name: String, val age: Int) {
// 1. 主构造函数参数:name, age已初始化
// 2. 属性初始化
val firstName = name.split(" ")[0] // 可以使用name
val lastName = name.split(" ").getOrElse(1) { "" }
// 3. init块执行
init {
println("Init 1: $name, $age")
}
val fullName = "$firstName $lastName"
init {
println("Init 2: $fullName")
}
// 4. 次构造函数(如果调用)
constructor(name: String) : this(name, 0) {
println("Secondary constructor")
}
}
3. 执行流程
使用主构造函数
val person = Person("John Doe", 30)
// 执行顺序:
// 1. name = "John Doe", age = 30
// 2. firstName = "John"
// 3. lastName = "Doe"
// 4. Init 1: John Doe, 30
// 5. fullName = "John Doe"
// 6. Init 2: John Doe
使用次构造函数
val person = Person("John Doe")
// 执行顺序:
// 1. 调用主构造函数:name = "John Doe", age = 0
// 2. firstName = "John"
// 3. lastName = "Doe"
// 4. Init 1: John Doe, 0
// 5. fullName = "John Doe"
// 6. Init 2: John Doe
// 7. Secondary constructor
4. 属性依赖
class Person(val name: String) {
// 可以依赖前面的属性
val firstName = name.split(" ")[0]
val lastName = name.split(" ").getOrElse(1) { "" }
// 可以依赖前面的属性
val fullName = "$firstName $lastName"
// ❌ 不能依赖后面的属性
// val test = fullName // 如果fullName在后面定义,会编译错误
}
5. init块中的属性访问
class Person(val name: String) {
val age = 25
init {
// ✅ 可以访问已初始化的属性
println("Name: $name, Age: $age")
// ❌ 不能访问未初始化的属性
// println(email) // 如果email在后面定义,会编译错误
}
val email = "$name@example.com"
}
6. 继承中的初始化顺序
open class Base(val name: String) {
init {
println("Base init: $name")
}
}
class Derived(name: String) : Base(name) {
init {
println("Derived init: $name")
}
}
// 执行顺序:
// 1. Derived主构造函数参数
// 2. Base主构造函数参数
// 3. Base属性初始化
// 4. Base init块
// 5. Derived属性初始化
// 6. Derived init块
7. 最佳实践
- 理解顺序:了解初始化顺序避免错误
- 属性依赖:注意属性之间的依赖关系
- init块位置:合理放置init块
- 避免循环依赖:避免属性之间的循环依赖
2. 可见性修饰符
3.7 Kotlin的可见性修饰符有哪些?
答案:
Kotlin的可见性修饰符:
1. 四种可见性
- public:公共(默认)
- private:私有
- protected:受保护
- internal:内部(模块内可见)
2. public(默认)
// public是默认的,可以不写
class Person {
public val name: String = "" // public可以省略
val age: Int = 0 // 默认public
}
// 等价于
class Person {
val name: String = ""
val age: Int = 0
}
3. private
class Person {
private val name: String = "" // 只在类内可见
private fun getName(): String = name
}
// 外部不能访问
// val person = Person()
// person.name // ❌ 编译错误
4. protected
open class Person {
protected val name: String = "" // 子类可见
}
class User : Person() {
fun display() {
println(name) // ✅ 子类可以访问
}
}
5. internal
// 同一模块内可见
internal class Person {
internal val name: String = ""
}
// 同一模块的其他文件可以访问
// 不同模块不能访问
6. 可见性范围
| 修饰符 | 类内 | 子类 | 同一模块 | 其他模块 |
|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ |
| private | ✅ | ❌ | ❌ | ❌ |
| protected | ✅ | ✅ | ❌ | ❌ |
| internal | ✅ | ✅ | ✅ | ❌ |
7. 顶层声明
// 顶层函数和类
public fun publicFunction() { } // 默认public
private fun privateFunction() { } // 文件内可见
internal fun internalFunction() { } // 模块内可见
8. 与Java对比
| Kotlin | Java |
|---|---|
public | public |
private | private |
protected | protected |
internal | package-private(类似) |
| 默认 | public(Kotlin)vs package-private(Java) |
9. 最佳实践
- 最小可见性:使用最小必要的可见性
- internal用于模块:模块内部使用internal
- private保护实现:实现细节使用private
- protected用于继承:需要子类访问时使用protected
3.8 Kotlin和Java的可见性修饰符对比
答案:
Kotlin和Java可见性修饰符的对比:
1. 基本对比
| Kotlin | Java | 说明 |
|---|---|---|
public | public | 公共访问 |
private | private | 私有访问 |
protected | protected | 受保护(子类可见) |
internal | package-private | 模块/包内可见 |
| 默认 | package-private(Java) | Kotlin默认public,Java默认package-private |
2. 默认可见性
Kotlin
// 默认public
class Person {
val name: String = "" // public
}
Java
// 默认package-private
class Person {
String name = ""; // package-private
}
3. internal vs package-private
Kotlin internal
// 模块内可见(编译单元)
internal class Person { }
Java package-private
// 包内可见
class Person { } // package-private
4. protected差异
Kotlin
open class Person {
protected val name: String = "" // 子类可见,同包不可见
}
Java
public class Person {
protected String name = ""; // 子类和同包可见
}
5. 实际应用
Kotlin
// 模块内部API
internal class InternalAPI { }
// 公共API
public class PublicAPI { } // public可以省略
Java
// 包内部API
class PackagePrivateAPI { }
// 公共API
public class PublicAPI { }
6. 最佳实践
- 理解差异:了解Kotlin和Java的差异
- 合理使用:根据需求选择合适的可见性
- 模块设计:使用internal设计模块内部API
3.9 internal修饰符的作用是什么?
答案:
internal修饰符的作用:
1. 基本概念
internal表示"模块内可见",是Kotlin特有的可见性修饰符。
2. 模块定义
模块(Module)是:
- 一起编译的一组Kotlin文件
- 一个IntelliJ IDEA模块
- 一个Maven/Gradle项目
- 一个Ant任务编译的一组文件
3. 使用场景
// 模块内部API
internal class InternalService {
internal fun process() { }
}
// 公共API
public class PublicService {
public fun process() { }
}
4. 与Java package-private对比
| 特性 | Kotlin internal | Java package-private |
|---|---|---|
| 可见范围 | 模块内 | 包内 |
| 跨包 | 同一模块内可以 | 不同包不可以 |
| 跨模块 | 不可以 | 不可以 |
5. 实际应用
模块设计
// 模块A
internal class DatabaseHelper { } // 模块A内部使用
public class UserService { } // 模块A对外API
// 模块B
// 可以访问UserService
// 不能访问DatabaseHelper
6. 最佳实践
- 模块内部API:使用internal标记模块内部API
- 公共API:对外API使用public
- 封装实现:隐藏实现细节
3. 属性
3.10 Kotlin的属性(Property)是什么?
答案:
Kotlin的属性(Property)是类中声明的变量,自动提供getter和setter。
1. 基本概念
属性是Kotlin中替代Java字段+getter/setter的方式:
class Person {
var name: String = "" // 属性
val age: Int = 0 // 只读属性
}
2. 自动getter/setter
Kotlin自动生成getter和setter:
class Person {
var name: String = ""
// 自动生成:
// fun getName(): String = name
// fun setName(value: String) { name = value }
}
// 使用
val person = Person()
person.name = "Kotlin" // 调用setter
println(person.name) // 调用getter
3. 自定义getter/setter
class Person {
var name: String = ""
get() = field.uppercase()
set(value) {
field = value.trim()
}
}
4. 只读属性
class Person {
val name: String = "Kotlin" // 只有getter,没有setter
// person.name = "Java" // ❌ 编译错误
}
5. 计算属性
class Rectangle(val width: Int, val height: Int) {
val area: Int
get() = width * height // 计算属性,不存储
}
6. 与Java对比
Kotlin
class Person {
var name: String = ""
}
Java
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
7. 最佳实践
- 利用自动getter/setter:减少样板代码
- 自定义访问器:需要特殊逻辑时自定义
- 优先使用val:除非需要修改
3.11 Kotlin的getter和setter是什么?
答案:
Kotlin的getter和setter:
1. 自动生成
Kotlin自动为属性生成getter和setter:
class Person {
var name: String = ""
// 自动生成:
// fun getName(): String = name
// fun setName(value: String) { name = value }
}
2. 自定义getter
class Person {
var name: String = ""
get() = field.uppercase() // 自定义getter
}
3. 自定义setter
class Person {
var name: String = ""
set(value) {
field = value.trim() // 自定义setter
}
}
4. field关键字
field是属性的幕后字段(backing field):
class Person {
var name: String = ""
get() = field.uppercase() // field指向实际存储
set(value) {
field = value.trim()
}
}
5. 只读属性的getter
class Person {
val name: String = "Kotlin"
// 自动生成:
// fun getName(): String = name
// 没有setter
}
6. 计算属性
class Rectangle(val width: Int, val height: Int) {
val area: Int
get() = width * height // 计算属性,没有field
}
7. 实际应用
验证setter
class Person {
var age: Int = 0
set(value) {
require(value >= 0) { "Age cannot be negative" }
field = value
}
}
格式化getter
class Person {
var name: String = ""
get() = field.uppercase()
}
8. 最佳实践
- 利用自动生成:大多数情况不需要自定义
- 验证逻辑:在setter中添加验证
- 格式化逻辑:在getter中添加格式化
- 使用field:访问实际存储时使用field
3.12 Kotlin的延迟初始化(lateinit)是什么?
答案:
lateinit用于延迟初始化非空、非基本类型的var属性。
1. 基本概念
lateinit允许在声明时不初始化属性,但必须在首次使用前初始化:
class Person {
lateinit var name: String // 延迟初始化
fun init() {
name = "Kotlin" // 必须在使用前初始化
}
fun display() {
if (::name.isInitialized) { // 检查是否初始化
println(name)
}
}
}
2. 使用条件
lateinit只能用于:
var属性(不能用于val)- 非空类型(不能用于可空类型)
- 非基本类型(不能用于Int、Double等)
class Person {
lateinit var name: String // ✅ 可以
// lateinit val name: String // ❌ 不能用于val
// lateinit var age: Int // ❌ 不能用于基本类型
// lateinit var email: String? // ❌ 不能用于可空类型
}
3. 初始化检查
class Person {
lateinit var name: String
fun check() {
if (::name.isInitialized) {
println(name)
} else {
println("Not initialized")
}
}
}
4. 未初始化异常
如果访问未初始化的lateinit属性,会抛出UninitializedPropertyAccessException:
class Person {
lateinit var name: String
}
val person = Person()
println(person.name) // 抛出UninitializedPropertyAccessException
5. 实际应用
Android开发
class MainActivity : AppCompatActivity() {
lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
button = findViewById(R.id.button) // 在onCreate中初始化
}
}
依赖注入
class UserService {
lateinit var repository: UserRepository
fun init(repository: UserRepository) {
this.repository = repository
}
}
6. 与可空类型对比
// lateinit方式
class Person {
lateinit var name: String
fun getName(): String = name // 返回非空String
}
// 可空类型方式
class Person {
var name: String? = null
fun getName(): String? = name // 返回可空String
}
7. 最佳实践
- 确定会初始化:确保在使用前初始化
- 检查初始化:使用
::property.isInitialized检查 - 避免滥用:不要过度使用lateinit
- 替代方案:考虑使用可空类型或lazy
3.13 lateinit和lazy的区别是什么?
答案:
lateinit和lazy的区别:
1. 基本区别
| 特性 | lateinit | lazy |
|---|---|---|
| 类型 | var | val |
| 初始化时机 | 手动初始化 | 首次访问时 |
| 线程安全 | 不保证 | 默认线程安全 |
| 可空性 | 非空类型 | 非空类型 |
| 基本类型 | 不支持 | 支持 |
2. lateinit
class Person {
lateinit var name: String // var,手动初始化
fun init() {
name = "Kotlin" // 手动初始化
}
}
3. lazy
class Person {
val name: String by lazy { // val,延迟初始化
"Kotlin" // 首次访问时计算
}
}
4. 初始化时机
lateinit
class Person {
lateinit var name: String
fun init() {
name = "Kotlin" // 手动调用时初始化
}
}
lazy
class Person {
val name: String by lazy {
println("Initializing name")
"Kotlin" // 首次访问时初始化
}
}
val person = Person()
println(person.name) // 此时才初始化
5. 线程安全
lateinit
// 不保证线程安全
lateinit var name: String
lazy
// 默认线程安全(LazyThreadSafetyMode.SYNCHRONIZED)
val name: String by lazy {
"Kotlin"
}
// 非线程安全
val name: String by lazy(LazyThreadSafetyMode.NONE) {
"Kotlin"
}
6. 使用场景
lateinit适用于:
- 确定会初始化的var属性
- 需要在外部初始化
- Android的View、依赖注入等
lazy适用于:
- 计算成本高的val属性
- 需要延迟计算的属性
- 单例模式
7. 实际应用
lateinit
class MainActivity : AppCompatActivity() {
lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
button = findViewById(R.id.button)
}
}
lazy
class Database {
val connection: Connection by lazy {
// 昂贵的初始化操作
createConnection()
}
}
8. 最佳实践
- lateinit用于var:需要修改的属性
- lazy用于val:只读的延迟计算属性
- 明确初始化:lateinit确保在使用前初始化
- 性能考虑:lazy用于昂贵计算
3.14 Kotlin的委托属性(Delegated Properties)是什么?
答案:
委托属性(Delegated Properties)使用by关键字将属性的getter/setter委托给另一个对象。
1. 基本语法
class Person {
var name: String by Delegate()
}
2. 标准委托
Kotlin提供标准委托:
lazy
val name: String by lazy {
"Kotlin"
}
observable
var name: String by Delegates.observable("") { prop, old, new ->
println("$old -> $new")
}
3. 自定义委托
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Value"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
// 设置值
}
}
4. 实际应用
lazy委托
val expensiveValue: String by lazy {
calculateExpensiveValue()
}
observable委托
var name: String by Delegates.observable("") { _, old, new ->
println("Changed: $old -> $new")
}
5. 最佳实践
- 使用标准委托:优先使用lazy、observable等
- 自定义委托:需要特殊逻辑时自定义
- 理解原理:理解委托的实现机制
4. 继承
3.15 Kotlin的继承机制是什么?
答案:
Kotlin的继承机制:
1. 基本语法
// 基类必须标记为open
open class Animal {
open fun makeSound() {
println("Animal sound")
}
}
// 继承
class Dog : Animal() {
override fun makeSound() {
println("Woof")
}
}
2. 默认final
Kotlin的类默认是final,不能继承:
class Animal { } // 默认final,不能继承
// class Dog : Animal() // ❌ 编译错误
// 需要标记open
open class Animal { } // 可以继承
class Dog : Animal() // ✅
3. open关键字
open class Animal {
open fun makeSound() { } // 可以重写
fun eat() { } // 不能重写(默认final)
}
class Dog : Animal() {
override fun makeSound() { } // ✅ 可以重写
// override fun eat() { } // ❌ 编译错误
}
4. override关键字
open class Animal {
open fun makeSound() { }
}
class Dog : Animal() {
override fun makeSound() { // 必须使用override
println("Woof")
}
}
5. 与Java对比
| Kotlin | Java |
|---|---|
| 默认final | 默认可继承 |
open class | public class |
override | @Override |
| 必须显式标记 | 默认可继承 |
6. 实际应用
open class Vehicle {
open fun start() {
println("Vehicle started")
}
}
class Car : Vehicle() {
override fun start() {
println("Car started")
}
}
7. 最佳实践
- 明确继承意图:使用open明确表示可继承
- 使用override:重写方法必须使用override
- final by default:默认final提高安全性
3.16 Kotlin的类默认是final的,如何实现继承?
答案:
实现继承的方法:
1. 标记基类为open
// 基类必须标记为open
open class Animal {
open fun makeSound() {
println("Animal sound")
}
}
// 子类继承
class Dog : Animal() {
override fun makeSound() {
println("Woof")
}
}
2. 标记方法为open
open class Animal {
open fun makeSound() { } // 可以重写
fun eat() { } // 不能重写
}
class Dog : Animal() {
override fun makeSound() { } // ✅
// override fun eat() { } // ❌ 编译错误
}
3. 抽象类
// 抽象类默认可继承
abstract class Animal {
abstract fun makeSound() // 必须实现
fun eat() { } // 可以有实现
}
class Dog : Animal() {
override fun makeSound() { // 必须实现
println("Woof")
}
}
4. 接口
interface Animal {
fun makeSound() // 默认可以重写
}
class Dog : Animal {
override fun makeSound() {
println("Woof")
}
}
5. 最佳实践
- 使用open:需要继承时标记open
- 使用abstract:需要强制实现时使用抽象类
- 使用接口:多继承时使用接口
3.17 Kotlin的open关键字的作用是什么?
答案:
open关键字的作用:
1. 允许继承
open class Animal { } // 可以继承
class Dog : Animal() // ✅
class Person { } // 不能继承
// class User : Person() // ❌ 编译错误
2. 允许重写
open class Animal {
open fun makeSound() { } // 可以重写
fun eat() { } // 不能重写
}
class Dog : Animal() {
override fun makeSound() { } // ✅
// override fun eat() { } // ❌
}
3. 与Java对比
Kotlin
open class Animal { } // 需要显式标记
Java
public class Animal { } // 默认可继承
4. 最佳实践
- 明确意图:使用open明确表示可继承
- 最小开放:只标记需要继承的类和方法
- 安全性:默认final提高安全性
3.18 Kotlin的方法重写(override)是什么?
答案:
方法重写(override)的规则:
1. 基本语法
open class Animal {
open fun makeSound() {
println("Animal sound")
}
}
class Dog : Animal() {
override fun makeSound() { // 必须使用override
println("Woof")
}
}
2. override是必须的
open class Animal {
open fun makeSound() { }
}
class Dog : Animal() {
override fun makeSound() { } // override必须写
// fun makeSound() { } // ❌ 编译错误,缺少override
}
3. 防止意外重写
open class Animal {
fun eat() { } // 默认final,不能重写
}
class Dog : Animal() {
// override fun eat() { } // ❌ 编译错误
}
4. 与Java对比
Kotlin
override fun makeSound() { } // override是关键字
Java
@Override
public void makeSound() { } // @Override是注解
5. 最佳实践
- 必须使用override:重写方法必须使用override
- 防止意外重写:默认final防止意外重写
- 明确意图:override明确表示重写
5. 接口
3.19 Kotlin的接口是什么?
答案:
Kotlin的接口:
1. 基本语法
interface Animal {
fun makeSound()
fun eat()
}
class Dog : Animal {
override fun makeSound() {
println("Woof")
}
override fun eat() {
println("Eating")
}
}
2. 接口特性
- 可以包含抽象方法
- 可以包含默认实现
- 可以包含属性
- 支持多继承
3. 默认实现
interface Animal {
fun makeSound() // 抽象方法
fun eat() { // 默认实现
println("Eating")
}
}
class Dog : Animal {
override fun makeSound() {
println("Woof")
}
// eat()可以不实现,使用默认实现
}
4. 属性
interface Animal {
val name: String // 抽象属性
val age: Int // 抽象属性
val description: String // 可以有getter
get() = "$name is $age years old"
}
class Dog(override val name: String, override val age: Int) : Animal
5. 多继承
interface Flyable {
fun fly()
}
interface Swimmable {
fun swim()
}
class Duck : Flyable, Swimmable {
override fun fly() { }
override fun swim() { }
}
6. 与Java对比
| 特性 | Kotlin | Java |
|---|---|---|
| 默认实现 | 支持 | Java 8+支持 |
| 属性 | 支持 | 不支持 |
| 多继承 | 支持 | 支持 |
7. 最佳实践
- 定义契约:使用接口定义契约
- 默认实现:提供合理的默认实现
- 多继承:利用接口实现多继承
3.20 Kotlin接口和Java接口的区别是什么?
答案:
主要区别:
1. 默认实现
Kotlin
interface Animal {
fun makeSound() // 抽象方法
fun eat() { // 默认实现
println("Eating")
}
}
Java
interface Animal {
void makeSound(); // 抽象方法
default void eat() { // Java 8+默认方法
System.out.println("Eating");
}
}
2. 属性支持
Kotlin
interface Animal {
val name: String // 属性
}
Java
interface Animal {
// 不能有属性,只能有常量
String NAME = "Animal";
}
3. 实际应用
两者功能类似,Kotlin语法更简洁。
3.21 Kotlin接口可以包含什么?
答案:
Kotlin接口可以包含:
1. 抽象方法
interface Animal {
fun makeSound() // 抽象方法
}
2. 默认实现方法
interface Animal {
fun makeSound() // 抽象方法
fun eat() { // 默认实现
println("Eating")
}
}
3. 抽象属性
interface Animal {
val name: String // 抽象属性
}
4. 属性getter
interface Animal {
val name: String
val description: String
get() = "Animal: $name"
}
5. 实际应用
interface User {
val name: String
val email: String
fun display(): String {
return "$name ($email)"
}
}
6. 抽象类
3.22 Kotlin的抽象类是什么?
答案:
抽象类:
1. 基本语法
abstract class Animal {
abstract fun makeSound() // 抽象方法,必须实现
fun eat() { // 普通方法,可以有实现
println("Eating")
}
}
class Dog : Animal() {
override fun makeSound() {
println("Woof")
}
}
2. 特性
- 可以包含抽象方法
- 可以包含普通方法
- 可以包含属性
- 不能实例化
- 默认可继承(不需要open)
3. 与接口对比
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 实例化 | 不能 | 不能 |
| 继承 | 单继承 | 多继承 |
| 构造函数 | 可以有 | 不能有 |
| 状态 | 可以有 | 不能有 |
4. 实际应用
abstract class Shape {
abstract fun area(): Double
fun description(): String {
return "Shape with area ${area()}"
}
}
class Circle(val radius: Double) : Shape() {
override fun area(): Double {
return Math.PI * radius * radius
}
}
3.23 抽象类和接口的区别是什么?
答案:
主要区别:
1. 继承数量
- 抽象类:单继承
- 接口:多继承
2. 构造函数
- 抽象类:可以有构造函数
- 接口:不能有构造函数
3. 状态
- 抽象类:可以有状态(属性)
- 接口:不能有状态(只有属性声明)
4. 使用场景
- 抽象类:有共同实现、需要状态时
- 接口:定义契约、多继承时
5. 最佳实践
- 优先接口:优先使用接口
- 抽象类用于实现:有共同实现时使用抽象类
- 组合使用:可以组合使用
7. 数据类
3.24 Kotlin的数据类(Data Class)是什么?
答案:
数据类(Data Class)是专门用于存储数据的类。
1. 基本语法
data class Person(val name: String, val age: Int)
2. 自动生成的方法
数据类自动生成:
equals()hashCode()toString()copy()componentN()(解构声明)
3. 示例
data class Person(val name: String, val age: Int)
val person1 = Person("Kotlin", 30)
val person2 = Person("Kotlin", 30)
println(person1 == person2) // true(equals)
println(person1) // Person(name=Kotlin, age=30)(toString)
val person3 = person1.copy(age = 31) // copy
4. 要求
- 主构造函数至少有一个参数
- 主构造函数参数必须标记为
val或var - 不能是抽象、开放、密封或内部类
5. 实际应用
data class User(
val id: Int,
val name: String,
val email: String
)
val user = User(1, "Kotlin", "kotlin@example.com")
val updated = user.copy(email = "new@example.com")
6. 最佳实践
- 用于数据存储:存储数据时使用数据类
- 利用自动方法:利用自动生成的方法
- 不可变优先:优先使用val属性
3.25 数据类的特点是什么?
答案:
数据类的特点:
1. 自动生成方法
自动生成equals、hashCode、toString、copy、componentN。
2. 解构声明
data class Person(val name: String, val age: Int)
val person = Person("Kotlin", 30)
val (name, age) = person // 解构声明
3. copy方法
val person = Person("Kotlin", 30)
val updated = person.copy(age = 31) // 创建副本并修改
4. 最佳实践
利用数据类的特性简化代码。
3.26 数据类和普通类的区别是什么?
答案:
主要区别:
1. 自动生成方法
- 数据类:自动生成equals、hashCode、toString等
- 普通类:需要手动实现
2. 用途
- 数据类:存储数据
- 普通类:业务逻辑
3. 限制
- 数据类:有特定要求(主构造函数参数等)
- 普通类:无特殊限制
4. 最佳实践
根据用途选择合适的类型。
3.27 数据类的copy()方法是什么?
答案:
copy()方法用于创建数据类实例的副本,可以修改部分属性。
1. 基本用法
data class Person(val name: String, val age: Int)
val person = Person("Kotlin", 30)
val updated = person.copy(age = 31) // 创建副本,修改age
2. 修改多个属性
val updated = person.copy(
name = "Java",
age = 31
)
3. 实际应用
val user = User(1, "Kotlin", "old@example.com")
val updated = user.copy(email = "new@example.com")
4. 最佳实践
使用copy创建不可变对象的修改版本。
3.28 数据类的equals()、hashCode()、toString()方法
答案:
数据类自动生成这些方法:
1. equals()
data class Person(val name: String, val age: Int)
val p1 = Person("Kotlin", 30)
val p2 = Person("Kotlin", 30)
println(p1 == p2) // true(比较内容)
2. hashCode()
val p1 = Person("Kotlin", 30)
val p2 = Person("Kotlin", 30)
println(p1.hashCode() == p2.hashCode()) // true
3. toString()
val person = Person("Kotlin", 30)
println(person) // Person(name=Kotlin, age=30)
4. 最佳实践
利用自动生成的方法,无需手动实现。
3.29 数据类支持解构声明吗?
答案:
是的,数据类支持解构声明。
1. 基本用法
data class Person(val name: String, val age: Int)
val person = Person("Kotlin", 30)
val (name, age) = person // 解构声明
println("$name is $age years old")
2. 部分解构
val (name, _) = person // 忽略age
3. 实际应用
val users = listOf(
Person("Alice", 25),
Person("Bob", 30)
)
for ((name, age) in users) {
println("$name: $age")
}
4. 最佳实践
利用解构声明简化代码。
8. 枚举类
3.30 Kotlin的枚举类(Enum Class)是什么?
答案:
枚举类用于定义一组常量。
1. 基本语法
enum class Color {
RED, GREEN, BLUE
}
2. 带值的枚举
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
3. 带方法的枚举
enum class Color {
RED {
override fun description() = "Warm color"
},
GREEN {
override fun description() = "Nature color"
};
abstract fun description(): String
}
4. 实际应用
enum class Status {
LOADING, SUCCESS, ERROR
}
fun process(status: Status) {
when (status) {
Status.LOADING -> showLoading()
Status.SUCCESS -> showContent()
Status.ERROR -> showError()
}
}
5. 最佳实践
- 定义常量集合:使用枚举定义常量
- 配合when使用:枚举与when配合使用
- 添加方法:需要行为时添加方法
3.31 枚举类和Java枚举的区别是什么?
答案:
主要区别:
1. 语法
Kotlin
enum class Color {
RED, GREEN, BLUE
}
Java
enum Color {
RED, GREEN, BLUE
}
2. 功能
两者功能基本相同,Kotlin语法更简洁。
3. 最佳实践
理解两者差异,合理使用。
3.32 枚举类的使用场景有哪些?
答案:
枚举类的使用场景:
1. 状态定义
enum class LoadingState {
IDLE, LOADING, SUCCESS, ERROR
}
2. 选项定义
enum class Theme {
LIGHT, DARK, AUTO
}
3. 类型定义
enum class UserType {
ADMIN, USER, GUEST
}
4. 最佳实践
用于定义固定的常量集合。
9. 嵌套类和内部类
3.33 Kotlin的嵌套类是什么?
答案:
嵌套类(Nested Class)是定义在类内部的类。
1. 基本语法
class Outer {
class Nested {
fun display() {
println("Nested class")
}
}
}
// 使用
val nested = Outer.Nested()
2. 特点
- 不能访问外部类的成员
- 类似Java的静态内部类
- 不需要外部类实例
3. 实际应用
class View {
class LayoutParams {
// 布局参数
}
}
val params = View.LayoutParams()
4. 最佳实践
用于逻辑相关的类组织。
3.34 Kotlin的内部类(inner class)是什么?
答案:
内部类(Inner Class)使用inner关键字,可以访问外部类成员。
1. 基本语法
class Outer {
private val value = 10
inner class Inner {
fun display() {
println(value) // 可以访问外部类成员
}
}
}
// 使用
val outer = Outer()
val inner = outer.Inner()
2. 特点
- 可以访问外部类成员
- 需要外部类实例
- 类似Java的非静态内部类
3. 与嵌套类对比
| 特性 | 嵌套类 | 内部类 |
|---|---|---|
| 关键字 | 无 | inner |
| 访问外部成员 | 不能 | 可以 |
| 需要外部实例 | 不需要 | 需要 |
| 类似Java | 静态内部类 | 非静态内部类 |
4. 实际应用
class View {
private var width = 0
inner class LayoutParams {
fun setWidth(w: Int) {
width = w // 可以访问外部类成员
}
}
}
5. 最佳实践
- 需要访问外部成员:使用内部类
- 不需要访问:使用嵌套类
- 避免过度使用:保持代码简洁
3.35 嵌套类和内部类的区别是什么?
答案:
主要区别:
1. 访问外部成员
- 嵌套类:不能访问
- 内部类:可以访问
2. 实例化
- 嵌套类:
Outer.Nested() - 内部类:
outer.Inner()
3. 使用场景
- 嵌套类:逻辑相关但不需要访问外部
- 内部类:需要访问外部类成员
4. 最佳实践
根据需求选择合适的类型。
3.36 嵌套类和Java的静态内部类的区别是什么?
答案:
主要区别:
1. 语法
Kotlin嵌套类
class Outer {
class Nested { }
}
Java静态内部类
public class Outer {
static class Nested { }
}
2. 功能
两者功能相同,Kotlin嵌套类等价于Java静态内部类。
第四章:Kotlin 集合面试题答案
1. 集合基础
4.1 Kotlin的集合类型有哪些?
答案:
Kotlin的集合类型:
1. 基本分类
Kotlin集合分为两大类:
- 可变集合(Mutable):可以修改
- 不可变集合(Immutable):不能修改
2. 三种基本类型
List(列表)
// 不可变
val list: List<String> = listOf("a", "b", "c")
// 可变
val mutableList: MutableList<String> = mutableListOf("a", "b", "c")
Set(集合)
// 不可变
val set: Set<String> = setOf("a", "b", "c")
// 可变
val mutableSet: MutableSet<String> = mutableSetOf("a", "b", "c")
Map(映射)
// 不可变
val map: Map<String, Int> = mapOf("a" to 1, "b" to 2)
// 可变
val mutableMap: MutableMap<String, Int> = mutableMapOf("a" to 1, "b" to 2)
3. 层次结构
Collection
├── List
│ ├── List(不可变)
│ └── MutableList(可变)
├── Set
│ ├── Set(不可变)
│ └── MutableSet(可变)
└── Map(独立接口)
├── Map(不可变)
└── MutableMap(可变)
4. 实际应用
// List:有序、可重复
val names = listOf("Alice", "Bob", "Alice")
// Set:无序、不重复
val uniqueNames = setOf("Alice", "Bob", "Alice") // ["Alice", "Bob"]
// Map:键值对
val ages = mapOf("Alice" to 25, "Bob" to 30)
5. 与Java对比
| Kotlin | Java |
|---|---|
List | List(接口) |
MutableList | ArrayList、LinkedList |
Set | Set(接口) |
MutableSet | HashSet、TreeSet |
Map | Map(接口) |
MutableMap | HashMap、TreeMap |
6. 最佳实践
- 优先不可变集合:默认使用不可变集合
- 需要修改时使用可变集合:明确使用Mutable
- 选择合适的类型:根据需求选择List/Set/Map
4.2 List、Set、Map的区别是什么?
答案:
List、Set、Map的主要区别:
1. List(列表)
特点:
- 有序:元素有顺序
- 可重复:可以有重复元素
- 索引访问:可以通过索引访问
val list = listOf("a", "b", "a", "c")
println(list[0]) // "a"
println(list.size) // 4
println(list) // [a, b, a, c]
2. Set(集合)
特点:
- 无序:元素无顺序(HashSet)或有序(LinkedHashSet)
- 不重复:不能有重复元素
- 无索引:不能通过索引访问
val set = setOf("a", "b", "a", "c")
println(set.size) // 3
println(set) // [a, b, c](去重)
// println(set[0]) // ❌ 编译错误,没有索引
3. Map(映射)
特点:
- 键值对:存储键值对
- 键唯一:键不能重复
- 值可重复:值可以重复
- 键访问:通过键访问值
val map = mapOf("a" to 1, "b" to 2, "c" to 1)
println(map["a"]) // 1
println(map.size) // 3
println(map.keys) // [a, b, c]
println(map.values) // [1, 2, 1]
4. 对比总结
| 特性 | List | Set | Map |
|---|---|---|---|
| 顺序 | 有序 | 无序/有序 | 无序/有序 |
| 重复 | 允许 | 不允许 | 键不重复,值可重复 |
| 索引 | 支持 | 不支持 | 不支持(通过键访问) |
| 访问方式 | list[index] | 遍历 | map[key] |
| 使用场景 | 有序列表 | 去重、查找 | 键值映射 |
5. 实际应用
List:有序列表
val tasks = listOf("Task 1", "Task 2", "Task 3")
tasks.forEachIndexed { index, task ->
println("$index: $task")
}
Set:去重
val numbers = listOf(1, 2, 2, 3, 3, 3)
val unique = numbers.toSet() // [1, 2, 3]
Map:键值映射
val userAges = mapOf(
"Alice" to 25,
"Bob" to 30,
"Charlie" to 25
)
val aliceAge = userAges["Alice"] // 25
6. 最佳实践
- List用于有序数据:需要顺序和索引时使用
- Set用于去重:需要唯一性时使用
- Map用于映射:需要键值关系时使用
- 根据需求选择:根据实际需求选择合适的类型
4.3 可变集合和不可变集合的区别是什么?
答案:
可变集合和不可变集合的区别:
1. 基本区别
不可变集合(Immutable)
val list: List<String> = listOf("a", "b", "c")
// list.add("d") // ❌ 编译错误,不能修改
// list[0] = "x" // ❌ 编译错误,不能修改
可变集合(Mutable)
val list: MutableList<String> = mutableListOf("a", "b", "c")
list.add("d") // ✅ 可以添加
list[0] = "x" // ✅ 可以修改
list.remove("b") // ✅ 可以删除
2. 类型对比
| 不可变 | 可变 |
|---|---|
List | MutableList |
Set | MutableSet |
Map | MutableMap |
3. 创建方式
不可变集合
val list = listOf("a", "b", "c")
val set = setOf("a", "b", "c")
val map = mapOf("a" to 1, "b" to 2)
可变集合
val list = mutableListOf("a", "b", "c")
val set = mutableSetOf("a", "b", "c")
val map = mutableMapOf("a" to 1, "b" to 2)
4. 转换
// 不可变转可变
val immutable = listOf("a", "b", "c")
val mutable = immutable.toMutableList()
// 可变转不可变
val mutable = mutableListOf("a", "b", "c")
val immutable = mutable.toList()
5. 实际应用
不可变集合(推荐)
// 函数式编程风格
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 } // 返回新列表,不修改原列表
可变集合(必要时)
// 需要修改时使用
val list = mutableListOf(1, 2, 3)
list.add(4)
list.remove(2)
6. 最佳实践
- 优先不可变集合:默认使用不可变集合
- 需要修改时使用可变:明确使用Mutable
- 函数式风格:使用不可变集合支持函数式编程
- 线程安全:不可变集合天然线程安全
4.4 Kotlin的集合和Java集合的区别是什么?
答案:
Kotlin集合和Java集合的主要区别:
1. 不可变集合
Kotlin
val list: List<String> = listOf("a", "b", "c")
// list.add("d") // ❌ 编译错误
Java
List<String> list = Arrays.asList("a", "b", "c");
// list.add("d"); // 运行时抛出UnsupportedOperationException
2. 创建方式
Kotlin
val list = listOf("a", "b", "c")
val set = setOf("a", "b", "c")
val map = mapOf("a" to 1, "b" to 2)
Java
List<String> list = Arrays.asList("a", "b", "c");
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c"));
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
3. 函数式操作
Kotlin
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }
Java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubled = numbers.stream()
.map(x -> x * 2)
.collect(Collectors.toList());
4. 空安全
Kotlin
val list: List<String>? = getList()
val size = list?.size ?: 0
Java
List<String> list = getList();
int size = list != null ? list.size() : 0;
5. 类型推断
Kotlin
val list = listOf(1, 2, 3) // 推断为List<Int>
Java
List<Integer> list = Arrays.asList(1, 2, 3); // 需要显式类型
6. 最佳实践
- 利用Kotlin特性:使用Kotlin的简洁语法
- 函数式操作:使用map、filter等函数
- 不可变优先:优先使用不可变集合
2. List
4.5 Kotlin的List创建方式有哪些?
答案:
List的创建方式:
1. listOf()
// 不可变List
val list = listOf("a", "b", "c")
val empty = listOf<String>() // 空列表
2. mutableListOf()
// 可变List
val list = mutableListOf("a", "b", "c")
list.add("d")
3. arrayListOf()
// ArrayList(可变)
val list = arrayListOf("a", "b", "c")
4. emptyList()
// 空列表
val empty: List<String> = emptyList()
5. listOfNotNull()
// 过滤null
val list = listOfNotNull("a", null, "b", null, "c") // [a, b, c]
6. 构建器
// buildList(Kotlin 1.6+)
val list = buildList {
add("a")
add("b")
add("c")
}
7. 从数组创建
val array = arrayOf("a", "b", "c")
val list = array.toList()
8. 从范围创建
val list = (1..10).toList() // [1, 2, 3, ..., 10]
9. 实际应用
// 常用方式
val names = listOf("Alice", "Bob", "Charlie")
val numbers = mutableListOf(1, 2, 3)
val empty: List<String> = emptyList()
10. 最佳实践
- 优先listOf:大多数情况使用listOf
- 需要修改时使用mutableListOf:需要修改时使用
- 空列表使用emptyList:明确表示空列表
4.6 List的常用操作有哪些?
答案:
List的常用操作:
1. 访问元素
val list = listOf("a", "b", "c")
list[0] // "a"
list.first() // "a"
list.last() // "c"
list.getOrNull(10) // null(安全访问)
2. 添加元素(可变List)
val list = mutableListOf("a", "b")
list.add("c") // 添加
list.add(0, "x") // 在索引0插入
list += "d" // 添加
3. 删除元素(可变List)
val list = mutableListOf("a", "b", "c")
list.remove("b") // 删除元素
list.removeAt(0) // 删除索引0
list -= "c" // 删除
4. 查找
val list = listOf("a", "b", "c")
list.contains("a") // true
list.indexOf("b") // 1
list.find { it == "b" } // "b"
5. 过滤
val list = listOf(1, 2, 3, 4, 5)
list.filter { it > 3 } // [4, 5]
list.filterNot { it > 3 } // [1, 2, 3]
6. 映射
val list = listOf(1, 2, 3)
list.map { it * 2 } // [2, 4, 6]
list.mapIndexed { index, value -> "$index: $value" }
7. 排序
val list = listOf(3, 1, 2)
list.sorted() // [1, 2, 3]
list.sortedDescending() // [3, 2, 1]
list.sortedBy { it } // [1, 2, 3]
8. 聚合
val list = listOf(1, 2, 3, 4, 5)
list.sum() // 15
list.average() // 3.0
list.max() // 5
list.min() // 1
9. 实际应用
val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers
.filter { it > 2 }
.map { it * 2 }
.sum() // 24
10. 最佳实践
- 利用函数式操作:使用map、filter等
- 链式调用:组合多个操作
- 安全访问:使用getOrNull等安全方法
4.7 List的过滤、映射、排序操作
答案:
List的过滤、映射、排序操作:
1. 过滤(filter)
val list = listOf(1, 2, 3, 4, 5)
val evens = list.filter { it % 2 == 0 } // [2, 4]
val odds = list.filterNot { it % 2 == 0 } // [1, 3, 5]
2. 映射(map)
val list = listOf(1, 2, 3)
val doubled = list.map { it * 2 } // [2, 4, 6]
val strings = list.map { it.toString() } // ["1", "2", "3"]
3. 排序(sorted)
val list = listOf(3, 1, 2)
val sorted = list.sorted() // [1, 2, 3]
val desc = list.sortedDescending() // [3, 2, 1]
4. 组合操作
val list = listOf(1, 2, 3, 4, 5)
val result = list
.filter { it > 2 } // [3, 4, 5]
.map { it * 2 } // [6, 8, 10]
.sortedDescending() // [10, 8, 6]
5. 最佳实践
- 链式调用:组合多个操作
- 利用函数式:使用函数式编程风格
- 性能考虑:大量数据时考虑使用序列
3. Set
4.8 Kotlin的Set创建方式有哪些?
答案:
Set的创建方式:
1. setOf()
val set = setOf("a", "b", "c")
val empty = setOf<String>()
2. mutableSetOf()
val set = mutableSetOf("a", "b", "c")
set.add("d")
3. hashSetOf()
val set = hashSetOf("a", "b", "c")
4. linkedSetOf()
val set = linkedSetOf("a", "b", "c") // 保持插入顺序
5. sortedSetOf()
val set = sortedSetOf("c", "a", "b") // 自动排序
6. 从List创建
val list = listOf("a", "b", "a", "c")
val set = list.toSet() // [a, b, c](去重)
7. 最佳实践
- 优先setOf:大多数情况使用setOf
- 需要修改时使用mutableSetOf:需要修改时使用
- 去重使用toSet:从List去重时使用
4.9 Set的常用操作有哪些?
答案:
Set的常用操作:
1. 基本操作
val set = setOf("a", "b", "c")
set.contains("a") // true
set.size // 3
set.isEmpty() // false
2. 集合操作
val set1 = setOf("a", "b", "c")
val set2 = setOf("b", "c", "d")
set1.union(set2) // [a, b, c, d](并集)
set1.intersect(set2) // [b, c](交集)
set1.subtract(set2) // [a](差集)
3. 过滤和映射
val set = setOf(1, 2, 3, 4, 5)
set.filter { it > 3 } // [4, 5]
set.map { it * 2 } // [2, 4, 6, 8, 10]
4. 实际应用
// 去重
val list = listOf(1, 2, 2, 3, 3, 3)
val unique = list.toSet() // [1, 2, 3]
// 集合运算
val admins = setOf("Alice", "Bob")
val users = setOf("Bob", "Charlie")
val onlyAdmins = admins.subtract(users) // [Alice]
5. 最佳实践
- 利用集合操作:使用union、intersect等
- 去重功能:使用Set去重
- 查找操作:使用contains快速查找
4. Map
4.10 Kotlin的Map创建方式有哪些?
答案:
Map的创建方式:
1. mapOf()
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
val empty = mapOf<String, Int>()
2. mutableMapOf()
val map = mutableMapOf("a" to 1, "b" to 2)
map["c"] = 3
3. hashMapOf()
val map = hashMapOf("a" to 1, "b" to 2)
4. linkedMapOf()
val map = linkedMapOf("a" to 1, "b" to 2) // 保持插入顺序
5. 构建器
val map = buildMap {
put("a", 1)
put("b", 2)
put("c", 3)
}
6. 从Pair列表创建
val pairs = listOf("a" to 1, "b" to 2)
val map = pairs.toMap()
7. 最佳实践
- 优先mapOf:大多数情况使用mapOf
- 需要修改时使用mutableMapOf:需要修改时使用
- 使用to创建Pair:使用
to关键字创建键值对
4.11 Map的常用操作有哪些?
答案:
Map的常用操作:
1. 访问元素
val map = mapOf("a" to 1, "b" to 2)
map["a"] // 1
map.getValue("a") // 1(如果不存在抛异常)
map.getOrDefault("c", 0) // 0(默认值)
map.getOrElse("c") { 0 } // 0(计算默认值)
2. 添加/修改(可变Map)
val map = mutableMapOf("a" to 1)
map["b"] = 2 // 添加
map.put("c", 3) // 添加
map["a"] = 10 // 修改
3. 删除(可变Map)
val map = mutableMapOf("a" to 1, "b" to 2)
map.remove("a") // 删除键"a"
map -= "b" // 删除键"b"
4. 遍历
val map = mapOf("a" to 1, "b" to 2)
map.forEach { (key, value) ->
println("$key: $value")
}
for ((key, value) in map) {
println("$key: $value")
}
5. 键和值
val map = mapOf("a" to 1, "b" to 2)
map.keys // [a, b]
map.values // [1, 2]
map.entries // 键值对集合
6. 过滤和映射
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
map.filter { it.value > 1 } // {b=2, c=3}
map.mapKeys { it.key.uppercase() } // {A=1, B=2, C=3}
map.mapValues { it.value * 2 } // {a=2, b=4, c=6}
7. 实际应用
val userAges = mapOf(
"Alice" to 25,
"Bob" to 30,
"Charlie" to 25
)
val adults = userAges.filter { it.value >= 18 }
val names = userAges.keys
val totalAge = userAges.values.sum()
8. 最佳实践
- 安全访问:使用getOrDefault或getOrElse
- 遍历使用解构:使用
(key, value)解构 - 利用函数式操作:使用filter、map等
4.12 Map的遍历方式有哪些?
答案:
Map的遍历方式:
1. forEach
val map = mapOf("a" to 1, "b" to 2)
map.forEach { (key, value) ->
println("$key: $value")
}
2. for-in循环
val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) {
println("$key: $value")
}
3. entries遍历
val map = mapOf("a" to 1, "b" to 2)
for (entry in map.entries) {
println("${entry.key}: ${entry.value}")
}
4. keys/values遍历
val map = mapOf("a" to 1, "b" to 2)
for (key in map.keys) {
println(key)
}
for (value in map.values) {
println(value)
}
5. 实际应用
val userAges = mapOf("Alice" to 25, "Bob" to 30)
userAges.forEach { (name, age) ->
println("$name is $age years old")
}
6. 最佳实践
- 使用解构:优先使用
(key, value)解构 - forEach简洁:使用forEach更简洁
- 根据需要选择:根据需求选择合适的遍历方式
5. 集合操作
4.13 Kotlin的集合操作符有哪些?
答案:
Kotlin提供了丰富的集合操作符:
1. 转换操作
map:转换每个元素mapIndexed:带索引的转换flatMap:扁平化转换flatten:扁平化
2. 过滤操作
filter:过滤filterNot:反向过滤filterIndexed:带索引过滤take:取前N个drop:跳过前N个
3. 查找操作
find:查找第一个first:第一个元素last:最后一个元素firstOrNull:第一个或nulllastOrNull:最后一个或null
4. 聚合操作
sum:求和average:平均值max:最大值min:最小值count:计数reduce:归约fold:折叠
5. 排序操作
sorted:排序sortedBy:按条件排序sortedDescending:降序reversed:反转
6. 分组操作
groupBy:分组partition:分割chunked:分块
7. 实际应用
val numbers = listOf(1, 2, 3, 4, 5)
numbers.map { it * 2 } // [2, 4, 6, 8, 10]
numbers.filter { it > 3 } // [4, 5]
numbers.sum() // 15
numbers.groupBy { it % 2 } // {1=[1,3,5], 0=[2,4]}
8. 最佳实践
- 链式调用:组合多个操作
- 利用函数式:使用函数式编程风格
- 性能考虑:大量数据时使用序列
4.14 filter、map、flatMap的区别是什么?
答案:
三个操作符的区别:
1. filter
过滤元素,返回满足条件的元素:
val list = listOf(1, 2, 3, 4, 5)
val evens = list.filter { it % 2 == 0 } // [2, 4]
2. map
转换每个元素,一对一映射:
val list = listOf(1, 2, 3)
val doubled = list.map { it * 2 } // [2, 4, 6]
3. flatMap
转换并扁平化,一对多映射:
val list = listOf("abc", "def")
val chars = list.flatMap { it.toList() } // [a, b, c, d, e, f]
// 等价于
val chars = list.map { it.toList() }.flatten()
4. 对比总结
| 操作符 | 输入 | 输出 | 映射关系 |
|---|---|---|---|
filter | List<T> | List<T> | 过滤,数量可能减少 |
map | List<T> | List<R> | 一对一转换 |
flatMap | List<T> | List<R> | 一对多转换并扁平化 |
5. 实际应用
val words = listOf("hello", "world")
val letters = words.flatMap { it.toList() } // [h, e, l, l, o, w, o, r, l, d]
val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
val doubled = evens.map { it * 2 } // [4, 8]
6. 最佳实践
- filter用于过滤:选择满足条件的元素
- map用于转换:一对一转换
- flatMap用于扁平化:一对多转换并扁平化
4.15 reduce、fold的区别是什么?
答案:
reduce和fold的区别:
1. reduce
从第一个元素开始归约,没有初始值:
val list = listOf(1, 2, 3, 4, 5)
val sum = list.reduce { acc, x -> acc + x } // 15
// 过程:((((1+2)+3)+4)+5)
2. fold
从初始值开始归约,有初始值:
val list = listOf(1, 2, 3, 4, 5)
val sum = list.fold(0) { acc, x -> acc + x } // 15
// 过程:(((((0+1)+2)+3)+4)+5)
3. 区别对比
| 特性 | reduce | fold |
|---|---|---|
| 初始值 | 无(使用第一个元素) | 有 |
| 空集合 | 抛出异常 | 返回初始值 |
| 使用场景 | 非空集合归约 | 需要初始值的归约 |
4. 实际应用
reduce
val numbers = listOf(1, 2, 3, 4, 5)
val product = numbers.reduce { acc, x -> acc * x } // 120
fold
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.fold(0) { acc, x -> acc + x } // 15
val product = numbers.fold(1) { acc, x -> acc * x } // 120
5. 最佳实践
- reduce用于非空集合:确定集合非空时使用
- fold用于需要初始值:需要初始值或可能为空时使用
- 空集合处理:空集合时fold更安全
4.16 groupBy、partition的区别是什么?
答案:
groupBy和partition的区别:
1. groupBy
按条件分组,返回Map:
val numbers = listOf(1, 2, 3, 4, 5)
val grouped = numbers.groupBy { it % 2 }
// {1=[1,3,5], 0=[2,4]}
2. partition
按条件分割,返回Pair:
val numbers = listOf(1, 2, 3, 4, 5)
val (evens, odds) = numbers.partition { it % 2 == 0 }
// evens = [2, 4]
// odds = [1, 3, 5]
3. 区别对比
| 特性 | groupBy | partition |
|---|---|---|
| 返回值 | Map<K, List<T>> | Pair<List<T>, List<T>> |
| 分组数量 | 多个组 | 两个组(true/false) |
| 使用场景 | 多组分类 | 二分分类 |
4. 实际应用
groupBy
val users = listOf(
User("Alice", 25),
User("Bob", 30),
User("Charlie", 25)
)
val byAge = users.groupBy { it.age }
// {25=[Alice, Charlie], 30=[Bob]}
partition
val numbers = listOf(1, 2, 3, 4, 5)
val (evens, odds) = numbers.partition { it % 2 == 0 }
5. 最佳实践
- groupBy用于多组:需要多个分组时使用
- partition用于二分:只需要true/false两组时使用
- 根据需求选择:根据实际需求选择合适的操作
4.17 take、drop、slice的区别是什么?
答案:
三个操作符的区别:
1. take
取前N个元素:
val list = listOf(1, 2, 3, 4, 5)
list.take(3) // [1, 2, 3]
2. drop
跳过前N个元素:
val list = listOf(1, 2, 3, 4, 5)
list.drop(3) // [4, 5]
3. slice
取指定索引范围的元素:
val list = listOf(1, 2, 3, 4, 5)
list.slice(1..3) // [2, 3, 4]
list.slice(listOf(0, 2, 4)) // [1, 3, 5]
4. 对比总结
| 操作符 | 作用 | 示例 |
|---|---|---|
take | 取前N个 | take(3) → 前3个 |
drop | 跳过前N个 | drop(3) → 跳过前3个 |
slice | 取指定范围 | slice(1..3) → 索引1到3 |
5. 实际应用
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.take(5) // [1, 2, 3, 4, 5]
list.drop(5) // [6, 7, 8, 9, 10]
list.slice(2..5) // [3, 4, 5, 6]
6. 最佳实践
- take用于取前N个:需要前N个元素时使用
- drop用于跳过:需要跳过前N个时使用
- slice用于范围:需要指定范围时使用
4.18 distinct、sorted的区别是什么?
答案:
distinct和sorted的区别:
1. distinct
去重,移除重复元素:
val list = listOf(1, 2, 2, 3, 3, 3)
list.distinct() // [1, 2, 3]
2. sorted
排序,按顺序排列:
val list = listOf(3, 1, 2)
list.sorted() // [1, 2, 3]
3. 组合使用
val list = listOf(3, 1, 2, 2, 3)
list.distinct().sorted() // [1, 2, 3](先去重后排序)
4. 实际应用
val numbers = listOf(3, 1, 2, 2, 3, 1)
val unique = numbers.distinct() // [3, 1, 2]
val sorted = numbers.sorted() // [1, 1, 2, 2, 3, 3]
val uniqueSorted = numbers.distinct().sorted() // [1, 2, 3]
5. 最佳实践
- distinct用于去重:需要唯一元素时使用
- sorted用于排序:需要排序时使用
- 组合使用:可以组合使用
4.19 集合的转换操作(toList、toSet、toMap)
答案:
集合之间的转换操作:
1. toList()
转换为List:
val set = setOf("a", "b", "c")
val list = set.toList() // [a, b, c]
2. toSet()
转换为Set(去重):
val list = listOf("a", "b", "a", "c")
val set = list.toSet() // [a, b, c](去重)
3. toMap()
转换为Map:
val pairs = listOf("a" to 1, "b" to 2)
val map = pairs.toMap() // {a=1, b=2}
4. 实际应用
// List转Set(去重)
val list = listOf(1, 2, 2, 3, 3, 3)
val unique = list.toSet() // [1, 2, 3]
// Set转List
val set = setOf("a", "b", "c")
val list = set.toList() // [a, b, c]
// Pair列表转Map
val pairs = listOf("a" to 1, "b" to 2)
val map = pairs.toMap()
5. 最佳实践
- 利用转换:使用转换操作简化代码
- 去重使用toSet:从List去重时使用toSet
- 保持类型:根据需求选择合适的类型
4.20 集合的聚合操作(sum、average、max、min)
答案:
集合的聚合操作:
1. sum()
求和:
val numbers = listOf(1, 2, 3, 4, 5)
numbers.sum() // 15
2. average()
平均值:
val numbers = listOf(1, 2, 3, 4, 5)
numbers.average() // 3.0
3. max() / maxOrNull()
最大值:
val numbers = listOf(1, 2, 3, 4, 5)
numbers.maxOrNull() // 5
4. min() / minOrNull()
最小值:
val numbers = listOf(1, 2, 3, 4, 5)
numbers.minOrNull() // 1
5. count()
计数:
val numbers = listOf(1, 2, 3, 4, 5)
numbers.count() // 5
numbers.count { it > 3 } // 2
6. 实际应用
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.sum() // 15
val avg = numbers.average() // 3.0
val max = numbers.maxOrNull() // 5
val min = numbers.minOrNull() // 1
val count = numbers.count { it > 3 } // 2
7. 最佳实践
- 使用聚合操作:利用内置的聚合函数
- 空集合处理:使用OrNull版本处理空集合
- 条件计数:使用count进行条件计数
第五章:Kotlin 空安全面试题答案
1. 可空类型
5.1 Kotlin的可空类型(Nullable Types)是什么?
答案:
可空类型(Nullable Types)是Kotlin类型系统的核心特性,用于在编译时防止空指针异常(NullPointerException,NPE)。
1. 基本概念
Kotlin的类型系统区分可空类型和非空类型:
// 非空类型(默认)
var name: String = "Kotlin"
// name = null // ❌ 编译错误,不能为null
// 可空类型(使用?标记)
var name: String? = "Kotlin"
name = null // ✅ 允许为null
2. 类型标记
使用?标记可空类型:
// 基本类型可空
var age: Int? = null
var price: Double? = null
var active: Boolean? = null
// 对象类型可空
var user: User? = null
var list: List<String>? = null
3. 设计目的
Java的问题
// Java中所有引用类型都可以为null,容易产生NPE
String name = null;
int length = name.length(); // 运行时抛出NullPointerException
Kotlin的解决方案
// Kotlin类型系统在编译时检查
var name: String? = null
val length = name.length // ❌ 编译错误,不能直接调用
val length = name?.length // ✅ 需要使用安全调用
4. 类型系统优势
- 编译时检查:在编译时就能发现潜在的空指针问题
- 明确意图:可空类型明确表示值可能为null
- 强制处理:必须显式处理可空类型
- 减少崩溃:大大减少运行时NPE
5. 示例
// 非空类型
fun processName(name: String) {
println(name.length) // 安全,name不能为null
}
// 可空类型
fun processName(name: String?) {
// name.length // ❌ 编译错误
println(name?.length ?: 0) // ✅ 安全处理
}
// 调用
processName("Kotlin") // ✅ 可以
processName(null) // ✅ 可以(参数可空)
6. 与Java对比
| 特性 | Kotlin | Java |
|---|---|---|
| 类型区分 | 可空/非空 | 所有引用可空 |
| 检查时机 | 编译时 | 运行时 |
| 默认类型 | 非空 | 可空 |
| NPE风险 | 低 | 高 |
| 强制处理 | 是 | 否 |
7. 实际应用
函数参数
// 可空参数
fun greet(name: String?) {
val greeting = name?.let { "Hello, $it" } ?: "Hello, Guest"
println(greeting)
}
greet("Kotlin") // Hello, Kotlin
greet(null) // Hello, Guest
函数返回值
// 可空返回值
fun findUser(id: Int): User? {
return users.find { it.id == id } // 可能返回null
}
val user = findUser(123)
user?.let { println(it.name) } // 安全处理
属性
class User {
var name: String? = null // 可空属性
var age: Int = 0 // 非空属性
}
8. 最佳实践
- 优先使用非空类型:除非确实需要null
- 明确标记可空:使用
?明确标记 - 安全处理:使用空安全操作符处理可空类型
- 避免滥用:不要过度使用可空类型
5.2 可空类型和非空类型的区别是什么?
答案:
可空类型和非空类型的主要区别:
1. 定义区别
非空类型(默认)
var name: String = "Kotlin"
// name = null // ❌ 编译错误
可空类型(使用?)
var name: String? = "Kotlin"
name = null // ✅ 允许
2. 使用区别
非空类型
var name: String = "Kotlin"
println(name.length) // ✅ 直接使用,不需要空检查
可空类型
var name: String? = null
// println(name.length) // ❌ 编译错误
println(name?.length ?: 0) // ✅ 需要安全调用
3. 赋值区别
// 非空类型不能赋值为null
var name: String = "Kotlin"
// name = null // ❌ 编译错误
// 可空类型可以赋值为null
var name: String? = "Kotlin"
name = null // ✅ 允许
// 非空类型不能接收可空值(需要显式处理)
var name: String = "Kotlin"
var nullableName: String? = null
// name = nullableName // ❌ 编译错误
name = nullableName ?: "Default" // ✅ 需要提供默认值
4. 函数调用区别
非空类型参数
fun process(name: String) {
println(name.length) // 安全,name不能为null
}
process("Kotlin") // ✅
// process(null) // ❌ 编译错误
可空类型参数
fun process(name: String?) {
name?.let { println(it.length) } // 需要安全处理
}
process("Kotlin") // ✅
process(null) // ✅
5. 返回值区别
非空返回值
fun getName(): String {
return "Kotlin" // 必须返回非null值
// return null // ❌ 编译错误
}
可空返回值
fun findName(id: Int): String? {
return names.find { it.id == id }?.name // 可以返回null
}
6. 对比总结
| 特性 | 非空类型 | 可空类型 |
|---|---|---|
| 标记 | 无标记 | ?标记 |
| 可为null | 否 | 是 |
| 直接调用 | 可以 | 不能 |
| 安全调用 | 不需要 | 需要 |
| 编译检查 | 严格 | 强制处理 |
| 使用场景 | 默认 | 可能为null时 |
7. 类型推断
// 非空类型推断
val name = "Kotlin" // 推断为String
// 可空类型推断
val name = null // 推断为Nothing?
val name: String? = null // 显式声明
8. 实际应用
推荐做法
// 优先使用非空类型
val userName: String = getUserName() // 如果确定不为null
// 确实可能为null时使用可空类型
val optionalName: String? = findOptionalName()
// 安全处理
val displayName = optionalName ?: "Unknown"
9. 最佳实践
- 默认非空:优先使用非空类型
- 明确标记:确实需要null时使用可空类型
- 安全处理:正确处理可空类型
- 避免滥用:不要过度使用可空类型
5.3 如何声明可空类型?
答案:
声明可空类型的方法:
1. 基本声明
使用?标记类型:
// 变量声明
var name: String? = null
var age: Int? = null
var price: Double? = null
var active: Boolean? = null
// 常量声明
val name: String? = null
2. 函数参数
// 可空参数
fun greet(name: String?) {
name?.let { println("Hello, $it") }
}
// 调用
greet("Kotlin") // ✅
greet(null) // ✅
3. 函数返回值
// 可空返回值
fun findUser(id: Int): User? {
return users.find { it.id == id }
}
// 使用
val user = findUser(123)
user?.let { println(it.name) }
4. 属性声明
class User {
var name: String? = null // 可空属性
var email: String? = null // 可空属性
var age: Int = 0 // 非空属性
}
5. 集合类型
// 可空元素集合
val list: List<String?> = listOf("a", null, "c")
// 可空集合
val list: List<String>? = null
// 可空元素且可空集合
val list: List<String?>? = null
6. 泛型可空
// 泛型参数可空
fun <T> process(value: T?): T? {
return value?.let { transform(it) }
}
7. 延迟初始化
class User {
// 方式1:可空类型
var name: String? = null
// 方式2:lateinit(仅用于var)
lateinit var email: String
// 方式3:lazy(用于val)
val description: String by lazy {
"User description"
}
}
8. 类型推断
// 明确声明
val name: String? = null
// 类型推断(null推断为Nothing?)
val name = null // 推断为Nothing?
val name: String? = null // 需要显式声明
9. 最佳实践
- 明确声明:使用
?明确标记可空类型 - 优先非空:除非确实需要null
- 延迟初始化:使用
lateinit或lazy替代可空类型 - 安全处理:声明后正确使用空安全操作符
5.4 可空类型的安全调用(Safe Call)是什么?
答案:
安全调用(Safe Call)使用?.操作符,在对象为null时返回null而不是抛出异常。
1. 基本语法
val result = object?.method()
val property = object?.property
2. 基本用法
val name: String? = null
val length = name?.length // 如果name为null,返回null;否则返回length
// 不使用安全调用(编译错误)
// val length = name.length // ❌ 编译错误
// 使用安全调用
val length = name?.length // ✅ 返回Int?
3. 安全调用链
val user: User? = getUser()
val email = user?.profile?.email // 如果任何部分为null,返回null
// 等价于Java的
// String email = user != null && user.profile != null
// ? user.profile.email : null;
4. 与Elvis操作符配合
val name: String? = null
val length = name?.length ?: 0 // 如果为null,返回0
val user: User? = null
val email = user?.profile?.email ?: "no-email"
5. 与let配合
val name: String? = "Kotlin"
name?.let {
println("Name: $it")
process(it)
}
// 如果name为null,整个代码块不执行
6. 实际应用
属性访问
val user: User? = getUser()
val name = user?.name // String?
val age = user?.age // Int?
方法调用
val list: List<String>? = getList()
val size = list?.size // Int?
val first = list?.first() // String?
嵌套访问
val user: User? = getUser()
val street = user?.address?.street // String?
val city = user?.address?.city // String?
7. 与Java对比
Kotlin
val user: User? = getUser()
val email = user?.profile?.email ?: "no-email"
Java
User user = getUser();
String email = user != null && user.getProfile() != null
? user.getProfile().getEmail() : "no-email";
8. 返回值类型
安全调用的返回值是可空类型:
val name: String? = "Kotlin"
val length: Int? = name?.length // Int?(可空)
// 配合Elvis操作符
val length: Int = name?.length ?: 0 // Int(非空)
9. 最佳实践
- 优先使用安全调用:避免NPE
- 配合Elvis操作符:提供默认值
- 配合let使用:执行多个操作
- 避免过度使用:确定非空时不需要安全调用
2. 空安全操作符
5.5 ?. 安全调用操作符的用法
答案:
?.安全调用操作符的详细用法:
1. 基本语法
对象?.属性
对象?.方法()
2. 属性访问
val user: User? = getUser()
val name = user?.name // 如果user为null,返回null
val age = user?.age // 如果user为null,返回null
3. 方法调用
val list: List<String>? = getList()
val size = list?.size // Int?
val first = list?.first() // String?
val isEmpty = list?.isEmpty() // Boolean?
4. 链式调用
val user: User? = getUser()
val street = user?.address?.street?.uppercase() // 任何环节为null,返回null
5. 与Elvis操作符
val name: String? = null
val length = name?.length ?: 0 // 如果name或length为null,返回0
6. 与let配合
val user: User? = getUser()
user?.let {
println("User: ${it.name}")
processUser(it)
} // 如果user为null,整个代码块不执行
7. 实际应用示例
API调用
val response: Response? = apiCall()
val data = response?.body?.data // 安全访问嵌套属性
集合操作
val list: List<String>? = getList()
val processed = list?.map { it.uppercase() } // List<String>?
val filtered = list?.filter { it.isNotEmpty() } // List<String>?
8. 返回值类型
// 安全调用返回可空类型
val name: String? = "Kotlin"
val length: Int? = name?.length // Int?
// 配合Elvis返回非空类型
val length: Int = name?.length ?: 0 // Int
9. 最佳实践
- 优先使用?.:避免NPE
- 配合Elvis:提供默认值
- 避免过度嵌套:考虑提取变量
- 配合let:执行多个操作
5.6 ?: Elvis操作符的用法
答案:
Elvis操作符?:用于在值为null时提供默认值。
1. 基本语法
值 ?: 默认值
2. 基本用法
val name: String? = null
val displayName = name ?: "Unknown" // 如果name为null,返回"Unknown"
val age: Int? = null
val displayAge = age ?: 0 // 如果age为null,返回0
3. 与安全调用配合
val user: User? = null
val name = user?.name ?: "Guest" // 如果user或name为null,返回"Guest"
val length = name?.length ?: 0 // 如果name或length为null,返回0
4. 表达式支持
Elvis操作符右侧可以是表达式:
val name: String? = null
val displayName = name ?: throw IllegalArgumentException("Name required")
val count: Int? = null
val total = count ?: calculateDefault() // 表达式
5. return和throw
Elvis操作符可以配合return和throw使用:
fun process(name: String?) {
val validName = name ?: return // 如果name为null,直接返回
println("Processing: $validName")
}
fun requireName(name: String?): String {
return name ?: throw IllegalArgumentException("Name required")
}
6. 实际应用
默认值
val user: User? = getUser()
val name = user?.name ?: "Unknown"
val email = user?.email ?: "no-email@example.com"
集合默认值
val list: List<String>? = getList()
val items = list ?: emptyList() // 如果为null,返回空列表
数值默认值
val price: Double? = getPrice()
val total = price ?: 0.0 // 如果为null,返回0.0
7. 嵌套使用
val user: User? = getUser()
val street = user?.address?.street ?: "Unknown Street"
val city = user?.address?.city ?: "Unknown City"
8. 与Java对比
Kotlin
val name: String? = null
val displayName = name ?: "Unknown"
Java
String name = null;
String displayName = name != null ? name : "Unknown";
9. 最佳实践
- 提供有意义默认值:不要滥用默认值
- 配合安全调用:组合使用更强大
- 使用表达式:右侧可以是表达式
- 配合return/throw:提前返回或抛出异常
5.7 !! 非空断言操作符的用法
答案:
!!非空断言操作符用于将可空类型强制转换为非空类型,如果值为null会抛出NPE。
1. 基本语法
值!!
2. 基本用法
val name: String? = "Kotlin"
val length = name!!.length // 强制转换为String,返回Int
// 如果name为null,会抛出NullPointerException
val name2: String? = null
// val length2 = name2!!.length // 运行时抛出NPE
3. 风险说明
危险用法
val name: String? = getUserName()
val length = name!!.length // ⚠️ 如果name为null,抛出NPE
4. 使用场景
确定非空时使用
val name: String? = "Kotlin"
// 经过检查确定非空
if (name != null) {
val length = name.length // 智能转换,不需要!!
}
// 如果确实确定非空
val length = name!!.length // 可以使用,但不推荐
5. 最佳实践
不推荐使用
// ❌ 不推荐
val user: User? = getUser()
val name = user!!.name // 可能抛出NPE
推荐做法
// ✅ 推荐:使用安全调用
val user: User? = getUser()
val name = user?.name ?: "Unknown"
// ✅ 推荐:使用let
user?.let {
println(it.name)
}
// ✅ 推荐:使用智能转换
if (user != null) {
println(user.name) // 智能转换
}
6. 何时使用
应该尽量避免使用!!,仅在以下情况考虑:
- 确定非空:有100%把握值不为null
- 测试代码:测试中明确期望NPE
- 遗留代码:与Java互操作时临时使用
7. 实际应用
不推荐
fun process(user: User?) {
val name = user!!.name // ⚠️ 危险
val email = user!!.email // ⚠️ 危险
}
推荐
fun process(user: User?) {
user?.let {
val name = it.name // 安全
val email = it.email // 安全
// 处理
}
}
8. 与安全调用对比
| 操作符 | 行为 | 风险 |
|---|---|---|
?. | null时返回null | 无风险 |
!! | null时抛出NPE | 高风险 |
9. 最佳实践
- 避免使用!!:优先使用安全调用
- 使用安全操作符:
?.、?:、let等 - 智能转换:利用Kotlin的智能转换
- 明确处理:明确处理null情况
5.8 ?.let的用法和作用
答案:
?.let是安全调用操作符与let函数的组合,用于在对象非空时执行代码块。
1. 基本语法
对象?.let {
// 代码块,it指向对象
}
2. 基本用法
val name: String? = "Kotlin"
name?.let {
println("Name: $it") // it指向name
process(it)
}
// 如果name为null,整个代码块不执行
3. 作用
- 空安全:只在对象非空时执行
- 作用域:提供一个作用域,it指向对象
- 返回值:let返回代码块的返回值
- 链式调用:可以链式调用
4. 返回值
val name: String? = "Kotlin"
val length: Int? = name?.let { it.length } // Int?
// 配合Elvis
val length: Int = name?.let { it.length } ?: 0 // Int
5. 实际应用
单次使用
val user: User? = getUser()
user?.let {
println("User: ${it.name}")
sendEmail(it.email)
updateLastLogin(it)
}
链式调用
val user: User? = getUser()
val result = user?.let { it.name }
?.let { it.uppercase() }
?.let { "Hello, $it" }
6. 与普通if对比
使用?.let
val name: String? = getName()
name?.let {
println(it)
process(it)
}
使用if(等价)
val name: String? = getName()
if (name != null) {
println(name)
process(name)
}
7. 优势
- 简洁:代码更简洁
- 作用域:明确作用域
- 链式:支持链式调用
- 函数式:符合函数式编程风格
8. 最佳实践
- 替代if-null检查:更简洁
- 多个操作:执行多个操作时使用
- 链式调用:需要转换时链式调用
- 配合Elvis:提供默认值
3. 空安全实践
5.9 如何避免空指针异常?
答案:
避免空指针异常的方法:
1. 使用非空类型(默认)
// 优先使用非空类型
val name: String = "Kotlin" // 不能为null,避免NPE
2. 正确使用可空类型
// 确实可能为null时使用可空类型
val name: String? = getName()
// 安全处理
val length = name?.length ?: 0
3. 使用安全调用操作符
val user: User? = getUser()
val email = user?.profile?.email ?: "no-email"
4. 使用Elvis操作符
val name: String? = null
val displayName = name ?: "Unknown"
5. 使用let函数
val user: User? = getUser()
user?.let {
processUser(it)
}
6. 使用智能转换
val name: String? = getName()
if (name != null) {
// name自动转换为非空类型
println(name.length) // 不需要?.
}
7. 使用when表达式
val user: User? = getUser()
when (user) {
null -> println("No user")
else -> {
println("User: ${user.name}")
processUser(user)
}
}
8. 避免使用!!
// ❌ 避免
val name = user!!.name
// ✅ 推荐
val name = user?.name ?: "Unknown"
9. 延迟初始化
class User {
// 使用lateinit替代可空类型
lateinit var name: String
// 或使用lazy
val description: String by lazy {
"Description"
}
}
10. 最佳实践总结
- 优先非空类型:默认使用非空类型
- 安全操作符:使用
?.、?:、let - 智能转换:利用类型检查后的智能转换
- 避免!!:不要使用非空断言
- 延迟初始化:使用
lateinit或lazy
5.10 可空类型的最佳实践是什么?
答案:
可空类型的最佳实践:
1. 优先使用非空类型
// ✅ 推荐:优先非空类型
val name: String = "Kotlin"
// ❌ 避免:除非确实需要null
val name: String? = "Kotlin"
2. 明确标记可空
// ✅ 明确标记
val optionalName: String? = findOptionalName()
// ❌ 不明确
val name = findName() // 类型不明确
3. 安全处理可空类型
val name: String? = getName()
// ✅ 使用安全调用
val length = name?.length ?: 0
// ✅ 使用let
name?.let { process(it) }
// ❌ 避免!!
// val length = name!!.length
4. 提供有意义的默认值
// ✅ 有意义的默认值
val name = user?.name ?: "Unknown User"
val email = user?.email ?: "no-email@example.com"
// ❌ 无意义的默认值
val name = user?.name ?: "" // 空字符串可能不是最佳选择
5. 使用延迟初始化
class User {
// ✅ 使用lateinit替代可空类型(如果确定会初始化)
lateinit var name: String
// ✅ 使用lazy(如果计算成本高)
val description: String by lazy {
calculateDescription()
}
// ❌ 避免不必要的可空
// var name: String? = null
}
6. 函数设计
// ✅ 推荐:返回值明确
fun findUser(id: Int): User? {
return users.find { it.id == id }
}
// ✅ 推荐:参数明确
fun processUser(user: User?) {
user?.let { process(it) }
}
// ❌ 避免:返回可空但没有意义
fun getUserName(): String? {
return "Kotlin" // 总是返回非null,不应该声明为可空
}
7. 集合处理
// ✅ 处理可空集合
val list: List<String>? = getList()
val items = list ?: emptyList()
val size = items.size
// ✅ 处理可空元素
val list: List<String?> = listOf("a", null, "c")
val nonNull = list.filterNotNull() // [a, c]
8. 与Java互操作
// ✅ 处理Java可空类型
fun processJavaString(str: String?) {
str?.let { println(it) }
}
// ✅ 使用注解
fun processWithAnnotation(@Nullable name: String?) {
name?.let { process(it) }
}
9. 代码审查要点
- 检查可空使用:是否合理
- 检查安全处理:是否正确处理null
- 检查!!使用:是否必要
- 检查默认值:是否有意义
10. 最佳实践总结
- 默认非空:优先使用非空类型
- 明确标记:确实需要null时使用
? - 安全处理:使用
?.、?:、let - 有意义的默认值:使用Elvis提供有意义默认值
- 延迟初始化:使用
lateinit或lazy - 避免!!:不要使用非空断言
- 代码审查:定期审查可空类型使用
5.11 安全转换(Safe Cast)as?的用法
答案:
安全转换as?用于安全地进行类型转换,失败时返回null而不是抛出异常。
1. 基本语法
值 as? 目标类型
2. 基本用法
val obj: Any = "Kotlin"
val str: String? = obj as? String // 安全转换,返回String?
val obj2: Any = 123
val str2: String? = obj2 as? String // null(转换失败)
3. 与非安全转换对比
| 操作符 | 失败时行为 | 返回类型 |
|---|---|---|
as | 抛出异常 | 非空类型 |
as? | 返回null | 可空类型 |
4. 示例
// 安全转换
val obj: Any = "Kotlin"
val str = obj as? String
str?.let { println(it.length) }
// 非安全转换(不推荐)
val obj2: Any = "Kotlin"
try {
val str2 = obj2 as String // 可能抛出异常
println(str2.length)
} catch (e: ClassCastException) {
// 处理异常
}
5. 与is配合使用
// 方式1:使用is(推荐)
val obj: Any = "Kotlin"
if (obj is String) {
println(obj.length) // 智能转换
}
// 方式2:使用as?(也可以)
val str = obj as? String
str?.let { println(it.length) }
6. when表达式中的使用
fun process(value: Any) {
when (val str = value as? String) {
null -> println("Not a string")
else -> println("String: ${str.length}")
}
}
7. 链式转换
val obj: Any = getUser()
val user = obj as? User
val name = user?.name // 安全访问
8. 实际应用
类型转换
fun processValue(value: Any) {
val str = value as? String
str?.let {
println("String: $it")
} ?: run {
println("Not a string")
}
}
集合转换
val list: List<Any> = listOf("a", 1, "b", 2)
val strings = list.mapNotNull { it as? String } // ["a", "b"]
9. 最佳实践
- 优先使用as?:更安全
- 配合let使用:处理转换结果
- 配合is使用:类型检查后使用智能转换
- 避免!!:不要对as?结果使用!!
5.12 非空断言!!的使用场景和风险
答案:
非空断言!!的使用场景和风险:
1. 基本语法
值!!
2. 行为
val name: String? = "Kotlin"
val length = name!!.length // 强制转换为String,返回Int
val name2: String? = null
val length2 = name2!!.length // 运行时抛出NullPointerException
3. 风险
高风险操作
val user: User? = getUser()
val name = user!!.name // ⚠️ 如果user为null,抛出NPE
val email = user!!.email // ⚠️ 如果user为null,抛出NPE
4. 使用场景(极其有限)
测试代码
@Test
fun testUser() {
val user = createUser()
val name = user!!.name // 测试中明确期望非null
assertEquals("Expected", name)
}
遗留代码互操作
// 与Java互操作时临时使用
val javaValue: String? = javaMethod()
val kotlinValue: String = javaValue!! // 临时转换
5. 避免使用的原因
- 失去空安全优势:失去Kotlin空安全的优势
- 运行时崩溃:可能抛出NPE
- 调试困难:难以定位问题
- 代码质量:表明代码设计有问题
6. 替代方案
不推荐
val user: User? = getUser()
val name = user!!.name // ❌
推荐方案1:安全调用
val user: User? = getUser()
val name = user?.name ?: "Unknown" // ✅
推荐方案2:let函数
val user: User? = getUser()
user?.let {
val name = it.name // ✅ 安全
processUser(it)
}
推荐方案3:智能转换
val user: User? = getUser()
if (user != null) {
val name = user.name // ✅ 智能转换
processUser(user)
}
推荐方案4:when表达式
val user: User? = getUser()
when (user) {
null -> println("No user")
else -> {
val name = user.name // ✅ 智能转换
processUser(user)
}
}
7. 最佳实践
- 避免使用!!:除非绝对必要
- 使用安全操作符:
?.、?:、let - 智能转换:利用类型检查
- 代码审查:检查!!的使用是否必要
- 重构代码:如果经常使用!!,考虑重构
8. 总结
- 风险:高,可能抛出NPE
- 使用场景:极其有限,应避免
- 推荐替代:安全调用、let、智能转换、when
5.13 可空类型的集合处理
答案:
处理可空类型集合的方法:
1. 可空元素集合
val list: List<String?> = listOf("a", null, "c", null, "e")
// 过滤null
val nonNull = list.filterNotNull() // ["a", "c", "e"]
// 安全处理
list.forEach { item ->
item?.let { println(it) }
}
2. 可空集合
val list: List<String>? = getList()
// 提供默认值
val items = list ?: emptyList()
val size = items.size
// 安全访问
val first = list?.first()
val size = list?.size ?: 0
3. 可空元素且可空集合
val list: List<String?>? = getOptionalList()
// 处理
val nonNull = list?.filterNotNull() ?: emptyList()
4. 常用操作
filterNotNull()
val list: List<String?> = listOf("a", null, "c")
val nonNull = list.filterNotNull() // [a, c]
mapNotNull()
val list: List<Int?> = listOf(1, null, 3)
val strings = list.mapNotNull { it?.toString() } // ["1", "3"]
firstOrNull()
val list: List<String?> = listOf(null, "b", "c")
val first = list.firstOrNull { it != null } // "b"
5. 实际应用
处理API响应
val users: List<User?>? = getUsers()
val validUsers = users?.filterNotNull() ?: emptyList()
validUsers.forEach { processUser(it) }
数据处理
val data: List<String?> = getData()
val processed = data.mapNotNull {
it?.uppercase()?.trim()
}
6. 最佳实践
- 使用filterNotNull():过滤null元素
- 使用mapNotNull():转换并过滤null
- 提供默认值:可空集合使用空列表作为默认值
- 安全访问:使用安全调用访问集合
5.14 空安全在Android开发中的应用
答案:
空安全在Android开发中的实际应用:
1. View处理
// findViewById可能返回null
val button: Button? = findViewById(R.id.button)
button?.setOnClickListener {
// 处理点击
}
// 使用lateinit(确定会初始化)
lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
button = findViewById(R.id.button) // 确定非null
}
2. Bundle处理
// Bundle值可能为null
val name = intent.getStringExtra("name") ?: "Unknown"
val age = intent.getIntExtra("age", 0) // 使用默认值
// 安全转换
val user = intent.getParcelableExtra<User>("user")
user?.let { processUser(it) }
3. Fragment参数
// Fragment参数
class DetailFragment : Fragment() {
private val userId: Int by lazy {
arguments?.getInt("userId") ?: throw IllegalArgumentException("userId required")
}
}
4. 生命周期方法
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// savedInstanceState可能为null
savedInstanceState?.let {
restoreState(it)
}
}
5. 网络请求
// 网络响应可能为null
apiService.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
val user = response.body() // User?
user?.let {
updateUI(it)
} ?: run {
showError("User not found")
}
}
override fun onFailure(call: Call<User>, t: Throwable) {
showError(t.message ?: "Unknown error")
}
})
6. 数据库查询
// 查询结果可能为null
val user = database.userDao().getUserById(userId)
user?.let {
displayUser(it)
} ?: run {
showEmptyState()
}
7. SharedPreferences
val preferences = getSharedPreferences("settings", Context.MODE_PRIVATE)
val userName = preferences.getString("user_name", null) ?: "Guest"
val isLoggedIn = preferences.getBoolean("is_logged_in", false)
8. LiveData观察
viewModel.userLiveData.observe(this) { user ->
user?.let {
updateUI(it)
} ?: run {
showLoading()
}
}
9. 最佳实践
- 使用lateinit:View等确定会初始化的对象
- 使用安全调用:处理可能为null的返回值
- 提供默认值:使用Elvis操作符
- 使用let:处理可空对象
- 避免!!:不要使用非空断言
5.15 空安全与Java互操作的问题
答案:
Kotlin与Java互操作时的空安全问题:
1. 平台类型(Platform Types)
Java返回的类型在Kotlin中视为平台类型(可空或非空):
// Java方法
public String getName() {
return name; // 可能为null
}
// Kotlin调用
val name = javaObject.getName() // 平台类型String!
// name.length // ⚠️ 可能NPE,编译不检查
2. 注解处理
使用@Nullable和@NonNull注解:
// Java
public @Nullable String getOptionalName() {
return name;
}
public @NonNull String getName() {
return name;
}
// Kotlin调用
val optional: String? = javaObject.getOptionalName() // 可空
val required: String = javaObject.getName() // 非空
3. 处理Java可空类型
// Java方法可能返回null
fun processJavaString(str: String?) {
str?.let { println(it) }
}
// 调用Java方法
val result = javaObject.getResult() // 平台类型
val safe = result ?: defaultValue // 安全处理
4. 类型映射
Java和Kotlin的类型映射:
| Java类型 | Kotlin类型 |
|---|---|
String | String!(平台类型) |
@Nullable String | String? |
@NonNull String | String |
5. 最佳实践
- 使用注解:Java代码中使用
@Nullable和@NonNull - 安全处理:Java返回值按可空处理
- 尽早转换:Java返回值尽早处理
- 测试验证:测试null情况