前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin 开发中遇到的坑(持续更新)

Kotlin 开发中遇到的坑(持续更新)

原创
作者头像
李林LiLin
修改2021-03-02 10:53:35
4.2K0
修改2021-03-02 10:53:35
举报
文章被收录于专栏:Android进阶编程Android进阶编程

1、空类型安全

1.1、可空类型正确用法

kotlin是强类型判断的,每一个对象都有可以为空和不可以为空之分。

代码语言:javascript
复制
var a: String = "abc"
a = null // 编译错误
 
var b: String? = "abc"
b = null // ok
 
val l = a.length // ok
 
val l = b.length // 编译错误:变量“b”可能为空
val l = b?.length ?: 0

1.2、可能出现异常的一些使用方式

1.2.1、getStringExtra 方法 可能导致的 null 异常

举个例子:

代码语言:javascript
复制
private var mHomeWorkId  = ""
mHomeWorkId = intent.getStringExtra(INPUT_HOME_WORK_ID)

由代码可见,mHomeWorkId 是一个不能为null的String。然后通过intent.getStringExtra 方法给mHomeWorkId赋值。

代码语言:javascript
复制
public String getStringExtra(String name) {
    return mExtras == null ? null : mExtras.getString(name);
}

getStringExtra() 方法的返回值是可能为 null的。因此,当getStringExtra返回值为null时,给mHomeWorkId赋值时,就会报错。

正确写法:

代码语言:javascript
复制
mHomeWorkId = intent.getStringExtra(INPUT_HOME_WORK_ID)?:""

1.2.2、条件判断问题

看下面例子:

代码语言:javascript
复制
var mUser:User? = null
if(mUser?.grade != -1){
    //做一些操作
}

上面代码看上去很简单,定义了一个 User类型的属性mUser,是可以为null的。在if条件中通过判断年级是否等于-1 来做一下操作。我们在写这段代码的时候想的可能是:当mUser不为null,而且年级不是-1的时候,通过条件判断,然后做一下操作。

但是在kotlin中,当mUser为null时,mUser?.grade的取值为null,而null != -1 ,在kotlin是成立的,这就不符合我们实际逻辑了。

解决方法如下:通过 ?: 操作,当mUser等于null时,给左边 一个默认值。

代码语言:javascript
复制
var mUser:User? = null
if(mUser?.grade?:-1 != -1){
    //做一些操作
}

1.2.3、is、as 中的坑

obj is String 之后,作用域之中,类型就已经转换了。

代码语言:javascript
复制
fun testAsIs() {
  var obj: Any? = null
 
  if (obj is String) {// 方法体内的作用域,obj 就是 String
      var length = obj.length
  }
}

as的两种不推荐写法,会抛出异常:TypeCastException: null cannot be cast to non-null type kotlin.String

代码语言:javascript
复制
//错误写法1,text不是String或为空时,会报异常
var strAble1 = text as String
//错误写法2,text不是String时,同样会报异常
var strAble2 = text as String?

as的推荐写法:

代码语言:javascript
复制
//正确写法,转换失败自动转换为空对象
var strAble = text as? String

2、TODO 语句报错问题

在kotlin开发中,当实现某个抽象方法时,会自动生成一条TODO语句。

代码语言:java
复制
override fun cancelRequest() {
    TODO("Not yet implemented")
}

记得把TODO(“not implemented”)注释掉,不然会抛出An operation is not implemented: not implemented异常

3、尽量避免使用 !!

对于 Null 的检查是 Kotlin 的特点之一。强制你在编码过程中考虑变量是否可为 null,因此可以避免很多在 Java 中隐藏的 NullPointerException。!! 表示这个对象一定不为null。因此只有当你百分百确认该对象不可能为null的时候,才能使用!!。

举个例子:

代码语言:javascript
复制
if (mPrimaryData != null) {
    mSecondaryData = mPrimaryData!!.secondaryList[0]
}

但是,当你用插件直接将 Java 代码转换为 Kotlin 时,你会发现有很多 !! 在里面。但其实 !! 意味着「有一个潜在未处理的 KotlinNullPointerException 在这里」。特别是在多线程开发环境中,而java代码又缺少了对null的强检查,这就更容易会出现空异常了。下面给大家减少及个避免 !! 的方法:

3.1、用 val 而不是 var

在 Kotlin 中 val 代表只读,var 代表可变。建议尽可能多的使用 val。val 是线程安全的,并且不需要担心 null 的问题。只需要注意 val 在某些情况下也是可变的就行了。对于普通变量来说,不可变( immutable )和只读( read-only )之间没什么区别。因为你没有办法复写一个 val 变量,所以在这时确实是不可变的。

但如果是对于类的成员变量来说,那只读和不可变的区别可就大了。在 Kotlin 的类中,val 和 var 是用于表示属性是否有 getter/setter:

  • var:同时有 getter 和 setter。
  • val:只有 getter。

但是可以通过自定义 getter 函数来返回不同的值:

代码语言:java
复制
class Person(val birthDay: DateTime) {  
  val age: Int
    get() = yearsBetween(birthDay, DateTime.now())
}

可以看到,虽然没有方法来设置 age 的值,但会随着当前日期的变化而变化。

这种情况下,我建议不要自定义 val 属性的 getter 方法。如果一个只读的类属性会随着某些条件而变化,那么应当用函数来替代:

代码语言:java
复制
class Person(val birthDay: DateTime) {  
  fun age(): Int = yearsBetween(birthDay, DateTime.now())
}

这也是 Kotlin 代码约定中所提到的,当具有下面列举的特点时使用属性,不然更推荐使用函数:

  • 不会抛出异常。
  • 具有 O(1) 的复杂度。
  • 计算时的消耗很少。
  • 同时多次调用有相同的返回值。

3.2、使用 lateinit

有些情况我们不能使用 val,比如,在 Android 中某些属性需要在 onCreate() 方法中初始化。对于这种情况,Kotlin 提供了 lateinit 关键字。

代码语言:java
复制
private lateinit var mAdapter: RecyclerAdapter<Transaction>
 
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   mAdapter = RecyclerAdapter(R.layout.item_transaction)
}
 
fun updateTransactions() {
   if(this::mAdapter.isInitialized){
      mAdapter.notifyDataSetChanged()
   }
}

要注意,访问未初始化的 lateinit 属性会导致 UninitializedPropertyAccessException。因此在使用的时候最好判断是否初始化。

并且 lateinit 不支持基础数据类型,比如 Int。对于基础数据类型,我们可以这样:

代码语言:java
复制
private var mNumber: Int by Delegates.notNull<Int>()

3.3、使用 let 函数

下面是 Kotlin 代码常见的编译错误:

许多开发者都会选择快速修复:

代码语言:java
复制
private var mPhotoUrl: String? = null
fun uploadClicked() {
    if (mPhotoUrl != null) {
        uploadPhoto(mPhotoUrl!!)
    }
}

但这里选择 let 函数是一个更优雅的解决方法:

代码语言:java
复制
private var mPhotoUrl: String? = null
fun uploadClicked() {
    mPhotoUrl?.let { uploadPhoto(it) }
}

3.4、创建全局函数来处理更复杂的情况

let 是一个对于 null 检查很好的替代品,但有时我们会遇到更复杂的情况。比如:

代码语言:java
复制
if (mUserName != null && mPhotoUrl != null) {
   uploadPhoto(mUserName!!, mPhotoUrl!!)
}

你可以选择嵌套两个 let,但这样可读性并不好。这时你可以构建一个全局函数:

代码语言:java
复制
fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
   if (value1 != null && value2 != null) {
       bothNotNull(value1, value2)
   }
}

然后直接调用:

代码语言:java
复制
ifNotNull(name,address){name,address->
    uploadPhoto(name,address)
}

3.5、使用 ?: 操作符

代码语言:java
复制
fun getName():String{
    if(name!=null){
        return name!!
    }else{
        return "android coder"
    }
}

上面方法可以简化为:

代码语言:java
复制
fun getName():String{
    return name?:"android coder"
}

3.6、自定义崩溃信息

如果我们使用 !!,那么当这个变量为 null 时,只会简单的抛出一个 KotlinNullPointerException。这时我们可以用 requireNotNull 或 checkNotNull 来附带异常信息,方便我们调试。

代码语言:java
复制
uploadPhoto(requireNotNull(intent.getStringExtra("PHOTO_URL"), { 
    "Activity parameter 'PHOTO_URL' is missing" 
}))

总而言之,绝大多数情况下你都不需要 !!,可以用上面提到的 6 个技巧来消除 !!。这样能让代码更安全、更容易 debug 并且更干净。

4、Gson与Kotlin碰撞出的不安全操作

4.1、使用 data class 没有设置无参构造函数

在 Kotlin 中,不需要自己动手去写一个 JavaBean,可以直接使用 DataClass,使用 DataClass 编译器会默默地帮我们生成一些函数。例如:

代码语言:java
复制
data class Person(var name: String, var age: Int) {}

这个Bean是用于接收服务器数据,通过Gson转化为对象的。例如:

代码语言:java
复制
val gson = Gson()
val person = gson.fromJson<Person>("{\"age\":\"12\"}", Person::class.java)
println(person.name)

我们传递了一个json字符串,但是没有包含key为name的值,并且注意:

在Person中name的类型是String,也就是说是不允许name=null的

输出结果:

代码语言:java
复制
null

是不是有些奇怪,感觉意外绕过了Kotlin的空类型检查。那么是什么原因导致的呢?

原因是:Person在被转Java代码时,只会生成一个包含两个参数的构造方法,没有提供默认的构造方法。Gson在通过反射创建对象时,会优先尝试获取无参构造函数。如果没有找到无参构造函数时,它就直接通过Unsafe的方法,绕过了构造方法,直接构建了一个对象。

因此我们在使用 data class,在遇到上面类似需求的时候,最好提供一个无参构造方法。

具体原因可以看这篇文章:https://cloud.tencent.com/developer/article/1788617

4.2、bean类继承了父类并在主构造函数中覆盖了父类的属性

使用Gson解析json时,如果bean类继承了父类并在主构造函数中覆盖了父类的属性,那么会报错:declares multiple JSON fields named name(声明多个名为name的JSON字段) 比如:

代码语言:java
复制
open class Person: Serializable{
    open var name: String? = null
}
class SpecialPerson(override var name: String?) : Person() {
    override fun toString(): String {
        return name?: ""
    }
}

解决方法是,在子类中用init初始化块将构造函数中获取到的属性值赋给继承的属性,即:

代码语言:java
复制
class SpecialPerson(var specialName: String?) : Person() {
    init {
        name = specialName
    }
    override fun toString(): String {
        return name?: ""
    }
}

5、Arouter中使用kotlin编写的Interceptor不生效的问题

问题原因在于kotlin文件中的@Interceptor注解没有被正确处理,因此没有将自定义的Interceptor加入到Interceptor集合中,解决方法为在module的build.gradle文件中:

第一:加入

代码语言:java
复制
apply plugin: 'kotlin-kapt'

第二:使用

代码语言:java
复制
kapt {
    arguments {
        arg("moduleName", project.getName())
    }
}

代替:

代码语言:java
复制
javaCompileOptions {
    annotationProcessorOptions {
        arguments = [moduleName: project.getName()]
    }
}

第三:使用

代码语言:java
复制
kapt 'com.alibaba:arouter-compiler:1.0.4'

代替:

代码语言:java
复制
annotationProcessor 'com.alibaba:arouter-compiler:1.0.4'

kapt 可以替代annotationProcessor 注释java类

6、Kotlin 复写 Java 父类中的方法,这里有坑

Java 父类定义 onDialogCreate 方法

代码语言:java
复制
// JavaKengBase.java
public class JavaKengBase {
    public void onDialogCreate(Object savedInstanceState) {
        // todo nothings
    }
}

Kotlin 继承并复写 JavaKengBase

代码语言:java
复制
class Keng : JavaKengBase() {
    override fun onDialogCreate(savedInstanceState: Any) {// 注意:此处,是Any,不是Any?
        super.onDialogCreate(savedInstanceState)
    }
}

利用 Java 多态特性,调用 onDialogCreate,并传入 null 参数

代码语言:java
复制
public class KengJava {
    public static void main(String[] args) {
        JavaKengBase keng = new Keng();
        keng.onDialogCreate(null);// 注意:空参数
    }
}

这里可以有两个问题:

第一个:"overrides nothing"

原因就在 onDialogCreate(savedInstanceState: Any) 方法定义中的:Any,不是Any?上。

注意:不要相信 AS 编译器,使用快捷键 Override Method 时,还是需要额外关注参数是否 Nullable?

第二个:IllegalArgumentException: Parameter specified as non-null is null

就算通过了编译,但在运行时,可能会抛出 Parameter specified as non-null is null异常,这个异常也是Java与Kotlin混合开发中的高频异常。

综上:上述问题,很好解决,只需要在方法参数后面,增加一个?即可。

代码语言:java
复制
override fun onDialogCreate(savedInstanceState: Any?) 

7、kotlin中的单例模式

代码语言:java
复制
class Singleton private constructor() {
    companion object {
        val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Singleton()
        }
    }
}

8、Kotlin 使用@Parcelize注解实现Parcelable

这里不介绍@Parcelize注解的具体使用,只记录使用过程中遇到的问题。使用方法大家可以自己百度下,很简单。

8.1、apply plugin:'kotlin-android-extensions'引用问题

大家都知道@Parcelize使用需要在module的build.gradle中配置两个地方:

代码语言:java
复制
//这个插件提供了很多新特性
apply plugin: 'kotlin-android-extensions'
代码语言:java
复制
android{
    androidExtensions {
        //新版本不需要这个了
        experimental = true
        // 这个配置是为了禁用除了parcelize外的其他功能
        features = ["parcelize"]
    }
}

但是遇见了一个问题,添加完上面两个地方后,@Parcelize注解死活不能用,根本不能识别。

原来,第一处的kotlin-android扩展插件写的顺序是有要求的。我们必须先写apply plugin: 'kotlin-android',然后再写apply plugin: 'kotlin-android-extensions',如果顺序写反了就会出现不能识别的情况。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、空类型安全
    • 1.1、可空类型正确用法
      • 1.2、可能出现异常的一些使用方式
        • 1.2.1、getStringExtra 方法 可能导致的 null 异常
        • 1.2.2、条件判断问题
        • 1.2.3、is、as 中的坑
    • 2、TODO 语句报错问题
    • 3、尽量避免使用 !!
      • 3.1、用 val 而不是 var
        • 3.2、使用 lateinit
          • 3.3、使用 let 函数
            • 3.4、创建全局函数来处理更复杂的情况
              • 3.5、使用 ?: 操作符
                • 3.6、自定义崩溃信息
                • 4、Gson与Kotlin碰撞出的不安全操作
                  • 4.1、使用 data class 没有设置无参构造函数
                    • 4.2、bean类继承了父类并在主构造函数中覆盖了父类的属性
                    • 5、Arouter中使用kotlin编写的Interceptor不生效的问题
                    • 6、Kotlin 复写 Java 父类中的方法,这里有坑
                    • 7、kotlin中的单例模式
                    • 8、Kotlin 使用@Parcelize注解实现Parcelable
                      • 8.1、apply plugin:'kotlin-android-extensions'引用问题
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档