Kotlin-null的处理

本文探讨了在Kotlin中如何处理NullPointerException(NPE),包括避免NPE的原因,如使用!!操作符可能导致的NPE,以及Kotlin提供的安全调用(?.)、Elvis操作符(?:)和安全类型转换等解决方案。通过对这些特性的了解,开发者可以在Kotlin编程中更好地避免NPE问题。

NullPointerException,俗称NPE,不管菜鸟还是老鸟们,都是不可避免,经常遇到的一个异常,解释起来很简单,就“空指针”三个字。总是在一次不小心,而掉进这个陷阱里。Kotlin 的设计目标就是希望消除代码中 null 引用带来的危险, 也就是所谓的造成十亿美元损失的大错误.

NPE的原因

尽管Kotlin希望消除代码中的NPE,我们总是不小心,总会不小心又掉进NPE的陷阱,下面是可能NPE的原因:

  • 明确调用 throw NullPointerException()
  • 使用 !! 操作符
  • 外部的 Java 代码导致这个异常
  • 初始化过程中存在某些数据不一致 (在构造器中使用了未初始化的 this)

避免NPE

在Kotlin中,明确区分可以指向 null 的引用 (可为 null 引用) 与不可以指向null的引用 (非null引用). 比如, 一个通常的 String 类型变量不可以指向 null:

var a: String = "abc"
a = null // 编译错误

此时,你对a变量的任何调用都是安全的,因为它为非null,你可以对该引用做任何操作而不会报NPE。就算对a赋值给Null,编译器都会报错,不会让你通过。

可是,实际开发时,不可能所有的变量不会null,总会有情况,变量是null的,此时该如何设定该变量呢?要允许null值的变量, 我们可以将变量声明为可为null的字符串, 写作 String? :

var b: String? = "abc"
b = null // ok

这样,我们是解决了变量可以设置为null的问题,可NPE的这个陷阱又设上了,如果再粗心大意,NPE又来找麻烦了。

如果我们仍然需要访问这个属性, 对不对? 有以下几种方法可以实现:

在条件语句中进行null检查

通过条件语句,对变量检查是否为null,对 null 和非 null 的两种情况分别处理:

if (b != null && b.length > 0)
    print("String of length ${b.length}")
else
    print("Empty string")


注:

该方案只是针对当前b的值,b的值在检查以后,如果b的值被修改仍需对此值做非null检查,也就是说每次修改b的值,都必须对b作非null验证,这也导致了代码的冗余。

安全调用

什么是安全调用?看起来有点不理解,既然是null怎么还会有安全呢?

b?.length

在Kotlin中,允许使用 “?.”操作符调用变量,其含义是如果b不是null,这个表达式将会返回b.length,否则返回 null.如果使用了”?.”,其表达式的值也应为 可为null的,比如Int?,否则编译器会报错。

bob?.department?.head?.name

安全调用在链式调用的情况下非常有用.这样的链式调用, 只要属性链中任何一个属性为 null, 整个表达式就会返回 null.

?:操作符

如果 ?: 左侧的表达式值不是null, 就会返回表达式的的值,否则, 返回右侧表达式的值.

val l = b?.length ?: -1

如果b不为null,将返回b的长度,如果为null,将返回-1


注:

  • 只有在左侧表达式值为 null 时, 才会计算右侧表达式.
  • 由于在 Kotlin 中 throw 和 return 都是表达式, 因此它们也可以用在 Elvis 操作符的右侧. 这种用法可以带来很大的方便, 比如, 可以用来检查函数参数值是否合法。

    fun foo(node: Node): String? {
    val parent = node.getParent() ?: return null
    val name = node.getName() ?: throw IllegalArgumentException(“name expected”)
    // …
    }

!! 操作符

对于b不为null的情况, 这个表达式将会返回这个非null值(比如, 在我们的例子中就是一个 String 类型值), 如果b是 null, 这个表达式就
会抛出一个 NPE:

val l = b!!.length()

当b为null时就抛出一个异常,你可以捕获它,而不是在不知道在某一角落里调用时,才报出异常,抓头挠腮半天,才找到NPE在哪里。

安全的类型转换

如果对象不是我们期望的目标类型, 那么通常的类型转换就会导致ClassCastException,可以选择使用安全的类型转换, 如果转换不成功, 它将会返回 null,这样避免了转换异常的抛出。

val aInt: Int? = a as? Int
<think>好的,我现在需要帮助用户迁移他们的项目,从废弃的`kotlin-android-extensions`插件转向使用View Binding和`kotlin-parcelize`插件。首先,我应该回顾用户之前的对话,确保理解他们的需求。 用户之前已经询问过如何在Gradle中修改步骤2来启用View Binding,现在他们明确想了解整个迁移过程,包括替换`kotlin-android-extensions`和使用替代方案。我需要综合之前的回答和提供的引用内容,给出一个完整的迁移指南。 首先,用户提到的引用[1]指出`kotlin-android-extensions`已被废弃,建议使用View Binding和`kotlin-parcelize`。所以迁移的第一步应该是移除旧的插件,并添加新的插件。同时,用户可能需要更新他们的Gradle配置,确保正确应用这些变化。 引用[2]提到了`plugins`作用域中的插件应用,以及依赖项的配置。因此,在迁移过程中,需要检查项目的`build.gradle`文件,移除旧插件的引用,并添加新的`kotlin-parcelize`插件。同时,确保依赖项中没有残留的旧插件相关的内容。 引用[3]提到了Java版本的问题,这可能与Gradle插件版本相关。虽然用户当前的问题是关于迁移,但需要提醒他们确保Java版本兼容,例如升级到Java 11,以避免构建错误。不过这可能属于另一个问题,但最好简要提及。 引用[4]讨论了如何通过配置提升Lint性能,这可能不是用户当前的主要需求,但如果用户在迁移后遇到性能问题,可以建议他们启用构建缓存。不过这可能作为附加建议。 接下来,我需要组织迁移步骤。根据之前的回答,迁移包括以下几个步骤: 1. 移除`kotlin-android-extensions`插件。 2. 添加`kotlin-parcelize`插件。 3. 启用View Binding。 4. 修改代码中的引用,从合成视图转换为View Binding。 5. 处理Parcelable的实现,使用`@Parcelize`注解。 需要详细说明每个步骤的具体操作,例如在哪个Gradle文件中修改,如何修改插件部分,如何启用View Binding等。同时,要提供代码示例,让用户能够直接复制粘贴。 例如,在模块级的`build.gradle`中,用户需要删除`apply plugin: 'kotlin-android-extensions'`或从plugins块中移除`id 'kotlin-android-extensions'`。然后,在plugins中添加`id 'kotlin-parcelize'`。接着,在android块中启用View Binding,并同步项目。 然后,用户需要修改他们的Activity或Fragment代码,使用View Binding来替代之前的合成属性。例如,使用`ActivityMainBinding`来inflate布局,并访问视图。同时,对于需要序列化的数据类,使用`@Parcelize`注解,并确保类继承自`Parcelable`。 此外,需要提醒用户可能遇到的常见问题,例如Java版本不匹配(引用[3]),如果他们在迁移过程中遇到相关错误,需要检查Java版本是否为11,并更新Gradle配置。 最后,总结整个迁移流程,确保用户能够一步步跟随,并验证是否成功。可能还需要提供一些附加提示,例如清理项目、处理多模块情况等。 在整个过程中,需要保持回答结构清晰,步骤分明,使用中文,并正确引用提供的资料。同时,根据用户之前的对话,他们已经了解步骤2的配置位置,因此可以简要提及,但详细说明整个迁移过程。</think>### 迁移 `kotlin-android-extensions` 到 View Binding 和 `kotlin-parcelize` 的完整步骤 #### **1. 移除废弃的 `kotlin-android-extensions` 插件** 在模块级 `build.gradle`(`app/build.gradle`)中删除以下内容: ```groovy // 移除以下任意一种形式的插件声明 plugins { id 'kotlin-android-extensions' // 删除此行 [^1] } // 或移除旧的 apply 语法 apply plugin: 'kotlin-android-extensions' // 删除此行 [^1] ``` --- #### **2. 添加 `kotlin-parcelize` 插件** 在模块级 `build.gradle` 的 `plugins` 块中添加: ```groovy plugins { id 'kotlin-parcelize' // 替代 Parcelable 实现 [^1] } ``` --- #### **3. 启用 View Binding** 在模块级 `build.gradle` 的 `android {}` 块中添加配置: ```groovy android { buildFeatures { viewBinding true // 启用 View Binding [^1][^3] } } ``` **注意**:修改后需点击 **Sync Now** 同步项目 [^3]。 --- #### **4. 代码迁移:View Binding** **Activity 中替换合成属性**: ```kotlin // 旧方式(废弃) import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) textView.text = "Hello" // 直接通过 id 访问视图 } } // 新方式(View Binding) private lateinit var binding: ActivityMainBinding // 自动生成的 Binding 类 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.textView.text = "Hello" // 通过 binding 对象访问视图 } } ``` **Fragment 中替换合成属性**: ```kotlin // 新方式(View Binding) class MyFragment : Fragment() { private var _binding: FragmentMyBinding? = null private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentMyBinding.inflate(inflater, container, false) return binding.root } override fun onDestroyView() { _binding = null super.onDestroyView() } } ``` --- #### **5. 代码迁移:`@Parcelize` 替代 Parcelable 实现** **旧方式**(依赖 `android-extensions` 的 Parcelable 支持): ```kotlin @Parcelize // 需要 kotlin-android-extensions 插件 data class User(val name: String, val age: Int) : Parcelable ``` **新方式**(使用 `kotlin-parcelize`): ```kotlin @Parcelize // 现在由 kotlin-parcelize 插件提供支持 [^1] data class User(val name: String, val age: Int) : Parcelable ``` --- ### **附加验证与优化** 1. **Java 版本检查** 确保 Java 11 已配置(避免引用[3]错误): - 修改 `gradle.properties` 添加: ```properties org.gradle.java.home=/path/to/jdk11 ``` 2. **清理残留代码** 全局搜索并删除 `import kotlinx.android.synthetic.main.*`。 3. **多模块项目处理** 每个模块需单独启用 View Binding 和 `kotlin-parcelize`。 --- ### **常见问题解决** - **View Binding 类未生成**:检查 `build.gradle` 配置后重新同步项目。 - **`@Parcelize` 注解错**:确认 `kotlin-parcelize` 插件已正确添加。 - **Java 版本冲突**:参考引用[3]升级 Java 或修改 Gradle 配置。 --- ### **引用来源** [^1]: [Android 官方迁移指南](https://developer.android.com/topic/libraries/view-binding/migration) [^3]: [Gradle 配置要求](https://developer.android.com/studio/releases/gradle-plugin#jdk-11)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值