Appearance
Kotlin 空安全机制详解 🛡️
Android 面试必考 Kotlin 空安全,包含可空类型、安全调用、Elvis 操作符、智能转换等核心知识点
目录
1. 可空类型 vs 非空类型
1.1 核心概念
Kotlin 的类型系统明确区分可空类型和非空类型,从编译期避免 NullPointerException:
kotlin
// 非空类型(默认)
var name: String = "Kotlin"
// name = null // ❌ 编译错误:Null can not be a value of a non-null type
// 可空类型(使用 ? 标记)
var nullableName: String? = "Kotlin"
nullableName = null // ✅ 正确
// 类型层级关系
// String 是 String? 的子类型
// String? 是 String 和 null 的并集1.2 可空类型的声明
kotlin
// 基本类型的可空形式
val nullableInt: Int? = null
val nullableBoolean: Boolean? = null
val nullableDouble: Double? = null
// 集合的可空形式
val nullableList: List<String>? = null // 列表本身可空
val listOfNullable: List<String?> = listOf(null, "A", null) // 元素可空
val nullableListOfNullable: List<String?>? = null // 两者都可空
// 函数的可空返回
fun findUser(id: String): User? {
return if (id == "123") User("张三") else null
}
// 可空参数
fun greet(name: String?) {
// name 可能是 null
}1.3 可空类型的初始化
kotlin
// 延迟初始化(lateinit)
class Presenter {
lateinit var view: View // 不能是基本类型,不能是可空类型
// lateinit var count: Int? // ❌ 错误:lateinit 不能用于可空类型
}
// 可空类型 + 延迟赋值
class Activity {
var presenter: Presenter? = null // ✅ 可空类型可以延迟赋值
fun onCreate() {
presenter = Presenter()
}
}
// 使用 by lazy
val config: Config by lazy {
Config.load() // 第一次访问时初始化
}1.4 可空类型与 Java 互操作
kotlin
// Java 代码
// public String getName() { return name; }
// public void setName(String name) { this.name = name; }
// Kotlin 调用 Java 方法
val name: String? = javaObject.getName() // 返回平台类型
// 添加空安全注解(在 Java 端)
// @NotNull
// public String getName() { return name; }
// @Nullable
// public String getOptionalName() { return name; }
// Kotlin 中识别注解
val name: String = javaObject.getName() // @NotNull,非空
val optional: String? = javaObject.getOptionalName() // @Nullable,可空2. 安全调用操作符 ?.
2.1 基础语法
kotlin
val nullableName: String? = "Kotlin"
// 安全调用:如果为 null,返回 null
val length = nullableName?.length // Int?
// 链式安全调用
val city = user?.address?.city?.name // 任何一环为 null,结果为 null
// 安全调用 + 方法
val upperName = nullableName?.uppercase() // String?
// 安全调用 + 数组/集合访问
val firstChar = nullableName?.get(0) // Char?
val firstItem = list?.get(0) // T?2.2 安全调用的实际场景
kotlin
// Android 场景
class UserViewModel : ViewModel() {
private var _user: User? = null
fun getUserName(): String? {
return _user?.name // 安全访问
}
fun getUserCity(): String? {
return _user?.address?.city?.name // 链式访问
}
}
// 数据处理
fun processUserData(user: User?) {
// 安全调用链
val email = user?.contact?.email?.trim()?.lowercase()
// 安全调用 + let
user?.let {
println("User name: ${it.name}")
println("Email: ${it.email}")
}
}
// 集合操作
fun getFirstEmail(users: List<User>?): String? {
return users?.firstOrNull()?.contact?.email
}2.3 安全调用的返回值
kotlin
// 安全调用的返回类型是可空的
val name: String? = null
val length: Int? = name?.length // Int?,不是 Int
// 需要提供默认值时使用 Elvis
val safeLength = name?.length ?: 0 // Int
// 安全调用 + 条件
val result = user?.takeIf { it.isActive }?.name
// 安全调用 + 集合操作
val names = users?.filter { it.age > 18 }?.map { it.name }2.4 安全调用与函数
kotlin
// 安全调用函数
val length = nullableName?.length // 属性
val trimmed = nullableName?.trim() // 方法
// 安全调用扩展函数
fun String?.isNullOrEmpty(): Boolean = this == null || this.isEmpty()
val result1: String? = null
println(result1.isNullOrEmpty()) // true
val result2: String? = ""
println(result2.isNullOrEmpty()) // true
val result3: String? = "Kotlin"
println(result3.isNullOrEmpty()) // false
// 安全调用带参数的函数
val substring = nullableName?.substring(0, 3)
// 安全调用带默认参数的函数
val padded = nullableName?.padEnd(10, '-')2.5 安全调用的陷阱
kotlin
// ❌ 陷阱 1:安全调用后直接使用结果
val length = nullableName?.length // Int?
// val doubled = length * 2 // ❌ 错误:Int? 不能直接运算
// ✅ 正确做法
val doubled = nullableName?.length?.times(2) // Int?
val doubled2 = (nullableName?.length ?: 0) * 2 // Int
// ❌ 陷阱 2:链式调用中某环返回 null
val city = user?.address?.city?.name // 如果 address 为 null,结果为 null
// ✅ 使用 let 处理中间结果
val cityInfo = user?.address?.let { address ->
"${address.city}, ${address.country}"
}
// ❌ 陷阱 3:安全调用后忘记处理 null
fun printName(user: User?) {
println(user?.name) // 可能打印 null
}
// ✅ 提供默认值
fun printName(user: User?) {
println(user?.name ?: "Unknown")
}3. Elvis 操作符 ?:
3.1 基础语法
kotlin
// Elvis 操作符:如果左边为 null,返回右边
val name: String? = null
val displayName = name ?: "Unknown" // "Unknown"
val name2: String? = "Kotlin"
val displayName2 = name2 ?: "Unknown" // "Kotlin"
// 等价于
val displayName = if (name != null) name else "Unknown"3.2 Elvis 的实际应用
kotlin
// 提供默认值
fun getUserName(user: User?): String {
return user?.name ?: "Unknown"
}
fun getUserAge(user: User?): Int {
return user?.age ?: 0
}
// 链式 Elvis
fun getFullAddress(user: User?): String {
return user?.address?.city ?: user?.fallbackCity ?: "Unknown City"
}
// Elvis + 空集合
fun getFirstUser(users: List<User>?): User {
return users?.firstOrNull() ?: User.GUEST
}
// Elvis + 空字符串
fun getDisplayName(name: String?): String {
return name?.trim()?.ifEmpty { null } ?: "Anonymous"
}3.3 Elvis 与 return/throw
kotlin
// Elvis + return
fun validateUser(user: User?): Boolean {
val name = user?.name ?: return false
val email = user.email ?: return false
return name.isNotEmpty() && email.contains("@")
}
// Elvis + throw
fun getUserById(id: String?): User {
val userId = id?.toIntOrNull() ?: throw IllegalArgumentException("Invalid ID")
return userRepository.getById(userId)
}
// Elvis + return 带值
fun processUser(user: User?): String {
val name = user?.name ?: return "No user"
return "Hello, $name"
}
// 实际 Android 场景
fun loadData(user: User?) {
val userId = user?.id ?: return // 没有用户 ID,直接返回
viewModel.loadUserData(userId)
}3.4 Elvis 的高级用法
kotlin
// Elvis + takeIf
val positiveNumber = number?.takeIf { it > 0 } ?: 0
// Elvis + let
val result = nullableValue?.let {
process(it)
} ?: defaultValue
// Elvis + 复杂表达式
val config = getConfigFromCache()
?: getConfigFromNetwork()
?: getDefaultConfig()
// Elvis + 懒加载
val cachedData: Data? = null
val data = cachedData ?: loadData().also { cachedData = it }
// Elvis + 集合操作
val firstAdult = users?.find { it.age >= 18 }?.name ?: "No adults"3.5 Elvis 与空集合
kotlin
// 空集合处理
val items: List<String>? = null
val safeItems = items ?: emptyList() // 转换为空列表
// 空集合检查
fun processItems(items: List<String>?) {
if (items.isNullOrEmpty()) {
showEmptyState()
return
}
// 安全使用 items
}
// Elvis + 集合默认值
fun getFirstItem(items: List<String>?): String {
return items?.firstOrNull() ?: "No items"
}
// 可空集合的安全操作
val count = items?.size ?: 0
val isEmpty = items.isNullOrEmpty()4. 非空断言 !!
4.1 基础语法
kotlin
val nullableName: String? = "Kotlin"
// 非空断言:声称值不为 null,如果为 null 则抛 NPE
val name: String = nullableName!! // String
// 链式断言
val length = nullableName!!.length // 如果 nullableName 为 null,抛 NPE4.2 使用场景
kotlin
// 场景 1:确定值不为 null(有外部保证)
class Activity : AppCompatActivity() {
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
textView = findViewById(R.id.text_view)
// 在 onCreate 之后,textView 一定不为 null
textView!!.text = "Hello" // 可以使用 !!
}
}
// 场景 2:测试代码
@Test
fun testUser() {
val user = userRepository.findById("123")
assertNotNull(user)
// 测试中确定不为 null
val name = user!!.name
assertEquals("张三", name)
}
// 场景 3:与 Java 互操作
fun handleJavaObject(obj: JavaObject?) {
// Java 方法声称返回非空,但实际可能返回 null
val result = obj!!.doSomething()
}4.3 避免使用 !! 的模式
kotlin
// ❌ 不推荐:过度使用 !!
fun processUser(user: User?) {
val name = user!!.name // 可能抛 NPE
val email = user!!.email
}
// ✅ 推荐:使用安全调用
fun processUser(user: User?) {
val name = user?.name ?: return
val email = user.email ?: return
// 使用 name 和 email
}
// ✅ 推荐:使用 let
fun processUser(user: User?) {
user?.let {
println("Name: ${it.name}")
println("Email: ${it.email}")
}
}
// ✅ 推荐:使用 when
fun processUser(user: User?) {
when (user) {
null -> showEmptyState()
else -> showUserData(user)
}
}4.4 !! 的安全替代方案
kotlin
// 替代方案 1:let + Elvis
val result = nullableValue?.let {
process(it)
} ?: run {
// 处理 null 情况
defaultValue
}
// 替代方案 2:run 块
fun processUser(user: User?) {
user?.run {
// this 是 user,非空
println(name)
println(email)
}
}
// 替代方案 3:智能转换
fun processUser(user: User?) {
if (user != null) {
// 智能转换为非空
println(user.name)
}
}
// 替代方案 4:require/check
fun processUser(user: User?) {
requireNotNull(user) { "User must not be null" }
// user 现在是智能转换为非空
println(user.name)
}4.5 !! 的陷阱
kotlin
// ❌ 陷阱 1:链式调用中的 !!
val city = user!!.address!!.city!!.name // 任何一环为 null 都抛 NPE
// ✅ 正确做法
val city = user?.address?.city?.name ?: "Unknown"
// ❌ 陷阱 2:在循环中使用 !!
for (item in items) {
process(item!!) // 如果 items 包含 null,抛 NPE
}
// ✅ 正确做法
for (item in items) {
item?.let { process(it) }
}
// ❌ 陷阱 3:在 lambda 中使用 !!
list.map { it!!.toString() } // 如果 list 包含 null,抛 NPE
// ✅ 正确做法
list.mapNotNull { it?.toString() }5. 安全转换 as?
5.1 基础语法
kotlin
// 不安全转换(可能抛 ClassCastException)
val str: String = obj as String
// 安全转换(失败返回 null)
val str: String? = obj as? String
// 使用示例
fun processObject(obj: Any): String? {
return obj as? String // 如果 obj 不是 String,返回 null
}5.2 安全转换的实际应用
kotlin
// Android 场景:Fragment 参数
class DetailFragment : Fragment() {
private val userId: String? by lazy {
arguments?.getString(ARG_USER_ID)
}
companion object {
fun newInstance(userId: String): DetailFragment {
return DetailFragment().apply {
arguments = bundleOf(ARG_USER_ID to userId)
}
}
}
}
// 类型检查 + 转换
fun handleItem(item: Any) {
when (item) {
is String -> processString(item)
is Int -> processInt(item)
is User -> processUser(item)
else -> handleUnknown(item)
}
}
// 安全转换 + Elvis
fun getStringValue(obj: Any): String {
return (obj as? String) ?: obj.toString()
}
// 安全转换 + let
fun processIfString(obj: Any) {
(obj as? String)?.let { str ->
println("String length: ${str.length}")
}
}5.3 安全转换与泛型
kotlin
// 泛型安全转换
fun <T> Any.safeAs(): T? {
return this as? T
}
// 使用
val obj: Any = "Hello"
val str: String? = obj.safeAs<String>()
val num: Int? = obj.safeAs<Int>() // null
// reified 类型参数
inline fun <reified T> Any.castOrNull(): T? {
return this as? T
}
// 使用
val result = obj.castOrNull<String>()5.4 安全转换的陷阱
kotlin
// ❌ 陷阱 1:忽略转换结果
val str = obj as? String
// println(str.length) // ❌ str 可能是 null
// ✅ 正确做法
val str = obj as? String ?: return
println(str.length)
// ❌ 陷阱 2:链式转换
val result = obj as? String as? StringBuilder // 总是 null
// ✅ 正确做法
val result = obj as? String?.let { StringBuilder(it) }
// ❌ 陷阱 3:泛型擦除
fun checkType(obj: Any) {
// val list = obj as? List<String> // ❌ 泛型信息在运行时被擦除
val list = obj as? List<*> // ✅ 使用星投影
}6. 智能转换
6.1 基础概念
Kotlin 编译器可以自动跟踪条件检查,在特定作用域内将可空类型转换为非空类型:
kotlin
fun printLength(str: String?) {
if (str != null) {
// 智能转换:str 在此作用域内是 String
println(str.length) // 不需要 str!!
}
}
fun printLength2(str: String?) {
if (str == null) return
// str 在此处智能转换为 String
println(str.length)
}6.2 智能转换的场景
kotlin
// 场景 1:null 检查
fun processUser(user: User?) {
if (user != null) {
println(user.name) // 智能转换
}
}
// 场景 2:is 类型检查
fun describe(obj: Any) {
if (obj is String) {
println(obj.length) // 智能转换为 String
} else if (obj is Int) {
println(obj.toDouble()) // 智能转换为 Int
}
}
// 场景 3:when 表达式
fun process(obj: Any): String {
return when (obj) {
is String -> "String: ${obj.length}" // 智能转换
is Int -> "Int: $obj" // 智能转换
else -> "Unknown"
}
}
// 场景 4:&& 组合
fun processUser(user: User?, active: Boolean) {
if (user != null && active) {
println(user.name) // 智能转换
}
}
// 场景 5:|| 组合(有限制)
fun processUser(user: User?) {
if (user == null || user.name.isEmpty()) {
return
}
// user 在此处智能转换为非空
println(user.name)
}6.3 智能转换的限制
kotlin
// ❌ 限制 1:var 属性不会智能转换
class User {
var name: String? = null
}
fun processUser(user: User) {
if (user.name != null) {
// println(user.name.length) // ❌ 错误:user.name 可能在检查后变为 null
}
}
// ✅ 解决方案:使用 val 或局部变量
fun processUser(user: User) {
val name = user.name // 复制到局部变量
if (name != null) {
println(name.length) // ✅ 智能转换
}
}
// ❌ 限制 2:自定义 getter 不会智能转换
class User {
val name: String?
get() = database.getName() // 每次访问都可能返回不同值
}
fun processUser(user: User) {
if (user.name != null) {
// println(user.name.length) // ❌ 错误
}
}
// ❌ 限制 3:开放类(open)的成员不会智能转换
open class Base {
open val name: String? = null
}
fun processBase(base: Base) {
if (base.name != null) {
// println(base.name.length) // ❌ 错误:子类可能重写 getter
}
}6.4 智能转换与 when
kotlin
// sealed class 的智能转换
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
}
fun processResult(result: Result): String {
return when (result) {
is Result.Success -> "Success: ${result.data}" // 智能转换
is Result.Error -> "Error: ${result.message}" // 智能转换
}
}
// 类型层次结构的智能转换
open class Animal
class Dog : Animal()
class Cat : Animal()
fun makeSound(animal: Animal) {
when (animal) {
is Dog -> println("Woof!") // animal 智能转换为 Dog
is Cat -> println("Meow!") // animal 智能转换为 Cat
}
}
// when 作为表达式
fun getDescription(obj: Any): String = when (obj) {
is String -> "String with ${obj.length} chars"
is Int -> "Integer: $obj"
is List<*> -> "List with ${obj.size} elements"
else -> "Unknown type"
}6.5 智能转换最佳实践
kotlin
// ✅ 实践 1:尽早返回
fun processUser(user: User?) {
if (user == null) return
// user 智能转换为非空
showUserName(user.name)
}
// ✅ 实践 2:使用 let
fun processUser(user: User?) {
user?.let {
// it 是非空的 User
showUserName(it.name)
}
}
// ✅ 实践 3:使用 require/check
fun processUser(user: User?) {
requireNotNull(user) { "User must not be null" }
// user 智能转换为非空
showUserName(user.name)
}
// ✅ 实践 4:复制到局部变量
fun processUser(user: User) {
val name = user.name // 复制
if (name != null) {
println(name.length)
}
}7. 平台类型
7.1 什么是平台类型
当 Kotlin 调用 Java 代码时,Kotlin 无法确定 Java 类型是否可空,这种类型称为平台类型:
kotlin
// Java 代码
// public class JavaUser {
// private String name;
// public String getName() { return name; }
// public void setName(String name) { this.name = name; }
// }
// Kotlin 调用
val javaUser = JavaUser()
val name = javaUser.getName() // 类型是 String!(平台类型)
// 平台类型可以当作 String 或 String? 使用
val nonNullName: String = javaUser.getName() // ✅ 可能抛 NPE
val nullableName: String? = javaUser.getName() // ✅ 安全7.2 平台类型的行为
kotlin
// 平台类型的赋值
val javaName = javaUser.getName() // String!
// 可以赋给非空类型(运行时会检查)
val kotlinName: String = javaName // 如果 javaName 为 null,抛 NPE
// 可以赋给可空类型(总是安全)
val kotlinNullable: String? = javaName // 总是安全
// 平台类型的方法调用
val length = javaName.length // 如果 javaName 为 null,抛 NPE
val safeLength = javaName?.length // 安全7.3 使用注解改善平台类型
在 Java 代码中添加空安全注解,Kotlin 可以识别:
java
// Java 代码
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavaUser {
@NotNull
public String getName() {
return name;
}
@Nullable
public String getNickname() {
return nickname;
}
}kotlin
// Kotlin 调用
val name: String = javaUser.getName() // 识别为 String,非空
val nickname: String? = javaUser.getNickname() // 识别为 String?,可空7.4 常见的空安全注解
kotlin
// Jetbrains 注解(Kotlin 原生支持)
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
// Android 注解
import androidx.annotation.NonNull
import androidx.annotation.Nullable
// Spring 注解
import org.springframework.lang.NonNull
import org.springframework.lang.Nullable
// Java 8+ 标准注解
import javax.annotation.Nonnull
import javax.annotation.Nullable7.5 处理平台类型的最佳实践
kotlin
// ✅ 实践 1:假设平台类型可能为 null
fun processJavaUser(javaUser: JavaUser) {
val name = javaUser.getName()?.trim() ?: "Unknown"
}
// ✅ 实践 2:使用安全调用
fun handleJavaObject(obj: JavaObject?) {
obj?.let {
// 安全使用
println(it.getName())
}
}
// ✅ 实践 3:添加显式检查
fun processJavaList(list: java.util.List<String>) {
if (list == null || list.isEmpty()) {
return
}
// 安全使用
}
// ✅ 实践 4:在 Kotlin 端添加注解
// 如果是自己维护的 Java/Kotlin 混合项目,添加注解
@JvmField
@Nullable
var optionalField: String? = null7.6 Java 互操作的实际场景
kotlin
// Android API 互操作
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// savedInstanceState 是 Bundle?(平台类型)
val savedData = savedInstanceState?.getString("key")
// intent 是 Intent(非空)
val action = intent?.action
// arguments 是 Bundle?
val userId = arguments?.getString("user_id")
}
}
// Java 集合互操作
fun processJavaList(javaList: java.util.List<String>) {
// 转换为 Kotlin 集合
val kotlinList = javaList.toList()
// 安全处理
val firstItem = javaList.firstOrNull()
}
// Java 流互操作
fun processJavaStream(javaStream: java.util.stream.Stream<String>) {
javaStream.use { stream ->
val result = stream
.filter { it.isNotEmpty() }
.collect(Collectors.toList())
}
}8. 面试考点汇总
8.1 基础问题
Q1: Kotlin 如何避免 NullPointerException?
kotlin
// 答案要点:
// 1. 类型系统区分可空类型(String?)和非空类型(String)
// 2. 编译期检查 null 安全性
// 3. 提供安全调用操作符(?.)、Elvis 操作符(?:)
// 4. 智能转换减少显式检查
// 5. 与 Java 互操作时使用平台类型和注解
// 示例
val name: String? = null
val safeLength = name?.length ?: 0 // 安全处理Q2: ?. 和 ?: 的区别是什么?
kotlin
// 答案要点:
// 1. ?. 是安全调用操作符,如果为 null 返回 null
// 2. ?: 是 Elvis 操作符,如果左边为 null 返回右边
// 3. 两者经常结合使用
val name: String? = null
// ?. 示例
val length = name?.length // null
// ?: 示例
val displayName = name ?: "Unknown" // "Unknown"
// 结合使用
val safeLength = name?.length ?: 0 // 0Q3: !! 操作符什么时候使用?
kotlin
// 答案要点:
// 1. 确定值不为 null 时(有外部保证)
// 2. 测试代码中
// 3. 与 Java 互操作且确定不为 null
// 4. 尽量避免使用,优先使用安全调用
// 使用场景
class Activity : AppCompatActivity() {
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
textView = findViewById(R.id.text_view)
textView!!.text = "Hello" // 确定已初始化
}
}8.2 进阶问题
Q4: 智能转换的原理和限制?
kotlin
// 答案要点:
// 1. 编译器自动跟踪条件检查
// 2. 在特定作用域内将可空类型转换为非空
// 3. 限制:var 属性、自定义 getter、开放类成员不会智能转换
// 原理
fun process(str: String?) {
if (str != null) {
println(str.length) // 智能转换为 String
}
}
// 限制
class User {
var name: String? = null // var 不会智能转换
}
fun processUser(user: User) {
if (user.name != null) {
// println(user.name.length) // ❌ 错误
}
}Q5: 平台类型是什么?如何处理?
kotlin
// 答案要点:
// 1. Kotlin 调用 Java 代码时的类型
// 2. Kotlin 无法确定 Java 类型是否可空
// 3. 可以当作可空或非空使用
// 4. 使用 @NotNull/@Nullable 注解改善
// 5. 最佳实践:假设可能为 null,使用安全调用
// 处理
val javaName = javaUser.getName() // String!
val safeName = javaName?.trim() ?: "Unknown"Q6: as 和 as? 的区别?
kotlin
// 答案要点:
// 1. as 是不安全转换,失败抛 ClassCastException
// 2. as? 是安全转换,失败返回 null
// 3. 优先使用 as?
val obj: Any = "Hello"
// as 示例
val str1 = obj as String // ✅
val str2 = obj as Int // ❌ ClassCastException
// as? 示例
val str3 = obj as? String // "Hello"
val str4 = obj as? Int // null8.3 原理问题
Q7: Kotlin 空安全的编译原理?
kotlin
// 答案要点:
// 1. 可空类型编译为 JVM 的引用类型(允许 null)
// 2. 非空类型在编译期检查,运行时不保证
// 3. 安全调用编译为 null 检查 + 条件分支
// 4. Elvis 操作符编译为三元运算符
// 5. !! 操作符编译为 null 检查 + 抛异常
// 编译后(伪代码)
// name?.length 编译为:
if (name != null) {
return name.length();
} else {
return null;
}
// name ?: "Unknown" 编译为:
return (name != null) ? name : "Unknown";
// name!! 编译为:
if (name == null) {
throw new NullPointerException();
}
return name;Q8: 智能转换为什么有这么多限制?
kotlin
// 答案要点:
// 1. 编译器需要保证值在检查后不变
// 2. var 属性可能被其他线程修改
// 3. 自定义 getter 每次访问可能返回不同值
// 4. 开放类的成员可能被子类重写
// 5. 这些情况编译器无法保证线程安全和值稳定性
// 解决方案
fun processUser(user: User) {
val name = user.name // 复制到局部变量
if (name != null) {
println(name.length) // ✅ 智能转换
}
}8.4 实战问题
Q9: 如何安全处理 Java 互操作?
kotlin
// 答案要点:
// 1. 假设平台类型可能为 null
// 2. 使用安全调用操作符
// 3. 在 Java 端添加 @NotNull/@Nullable 注解
// 4. 转换为 Kotlin 集合类型
// 5. 使用 requireNotNull 显式检查
// 示例
fun processJavaUser(javaUser: JavaUser) {
// 安全调用
val name = javaUser.getName()?.trim() ?: "Unknown"
// 显式检查
requireNotNull(javaUser) { "JavaUser must not be null" }
// 集合转换
val kotlinList = javaUser.getItems().toList()
}Q10: 如何设计空安全的 API?
kotlin
// 答案要点:
// 1. 参数尽量使用非空类型
// 2. 可选参数使用默认值或可空类型
// 3. 返回值明确标注可空性
// 4. 使用 Result/Either 类型处理错误
// 5. 提供清晰的文档说明
// 示例
// ✅ 好的设计
fun createUser(
name: String, // 必填
email: String? = null, // 可选
age: Int = 0 // 有默认值
): User? { // 明确可能返回 null
if (name.isEmpty()) return null
return User(name, email, age)
}
// ❌ 不好的设计
fun createUser(name: String?, email: String?, age: Int?): User {
// 参数都可空,但返回非空,行为不明确
}Q11: 如何处理多层嵌套的可空对象?
kotlin
// 答案要点:
// 1. 使用链式安全调用
// 2. 使用 let 作用域
// 3. 使用数据类 + copy
// 4. 考虑重构数据结构
// 链式安全调用
val city = user?.address?.city?.name ?: "Unknown"
// let 作用域
val result = user?.let { u ->
u.address?.let { a ->
a.city?.let { c ->
c.name
}
}
} ?: "Unknown"
// 重构建议
data class UserSummary(
val userName: String,
val cityName: String
)
fun getUserSummary(user: User?): UserSummary {
return UserSummary(
userName = user?.name ?: "Unknown",
cityName = user?.address?.city?.name ?: "Unknown"
)
}最佳实践总结
✅ 推荐做法
kotlin
// 1. 优先使用非空类型
val name: String = "Kotlin"
// 2. 使用安全调用
val length = nullableName?.length
// 3. 使用 Elvis 提供默认值
val displayName = name ?: "Unknown"
// 4. 使用 let 处理非空值
user?.let {
println(it.name)
}
// 5. 使用智能转换
if (user != null) {
println(user.name)
}
// 6. 使用 as? 安全转换
val str = obj as? String
// 7. 在 Java 端添加注解
@NotNull
public String getName() { ... }❌ 避免做法
kotlin
// 1. 过度使用 !!
val name = nullableName!! // 可能抛 NPE
// 2. 忽略平台类型
val javaName: String = javaUser.getName() // 可能为 null
// 3. 复杂的嵌套 null 检查
if (user != null) {
if (user.address != null) {
if (user.address.city != null) {
// ...
}
}
}
// 4. 在 var 属性上依赖智能转换
if (user.name != null) {
println(user.name.length) // ❌ 错误
}参考资料
最后更新:2026-04-14