总结下,最近开发中使用 Kotlin 的一些小 tips。

1. 尽可能使用 val 声明 Kotlin 的变量。

val 声明的变量表示不可变的,必须初始化,其他地方不能修改该值,是线程安全的。

2. Elvis操作符 ?:

在无法判断变量是否有可能为空的情况下,不要写成显式的非 null 操作符 !!,尽量用 Elvis操作符 ?: ,特别是数值类型的时候,
var count = list?.length ?: 0

3. 尽量不要使用非空断言 !! (除非确保变量不为空)

可以使用 ? , ?: 或 lateinit 来避免编译器的可空提示
但使用 lateinit 时,务必确保使用的时候已经赋值(否则会报 kotlin.UninitializedPropertyAccessException: lateinit property bitmap has not been initialized 异常),并且确保赋的值一定不能为空(lateinit定义的值是非空的)

为了避免因为未初始化引起的异常问题,Kotlin 语言为每一个 lateini 属性实例提供了一个判断是否已经初始化的属性值 isInitialized (但要依赖于 Kotlin 的反射)

1
2
3
4
private lateinit var user: User
if (this::user.isInitialized) {
    //
}

4. 安全转换 “as?”

注意类型转换关键字 as, 也有可能转换失败的情况(直接抛异常),要使用 as? ,转换失败时返回null, 对 null 做判断;

1
var value = obj as? User ?: User()      //表示如果 obj 强转为 User 失败时,new 一个 User 对象

5. open 关键字

Kotlin 中类和类的方法默认是 final 的,即不允许继承和重写。这主要是为了减少继承,避免脆弱基类的问题。想要实现继承或重写,需要加 open 关键字修饰

6. 注意声明或调用 Kotlin 的函数是,参数是否可为 null

定义 Kotlin 函数时,需要注意传入的参数是否有可能为 null;如果外部传参有可能会传 null 的话,要加一个可为 null 操作符 ? 的逻辑,否则如果直接传 null 会造成程序异常,特别是外部传参是 Java 代码传的情况,编译器是不会提示的不可为 null 的(是可以编译通过的,但 Kotlin 不能编译通过),例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//kotlin 
class Test {
    fun testMethod(str : String) {
        println(str)
    }
}
 
//java,可以编译通过,但运行时会报 java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull
Test test = new Test();
test.testMethod(null);
 
//kotlin,无法编译通过,编译器会直接提示 Null can not be a value of a non-null type xxx
var test = Test()
test.testMethod(null)

7. Kotlin object 的特点

通过 object 实现的单例是一个饿汉式的单例,并且实现了线程安全;
object: 也可以用来创建匿名类的对象;

8. top-level property / function 顶层声明

把属性和函数的声明不写在 class 里面,这个在 Kotlin 里是允许的。这样写的属性和函数,不属于任何 class,而是直接属于 package,它和静态变量、静态函数一样是全局的,但用起来更方便:你在其它地方用的时候,就连类名都不用写;
如果在不同文件中声明命名相同的函数,使用的时候不会混淆,会自动导入不同包名

9. Kotlin 泛型

  • 和 Java 的泛型一样,Kotlin 中泛型本身也是不可变的,需要用关键字 outin才能设置型变
    a. 使用关键字out支持协变,等同于 Java 中的上界通配符? extends;
    b. 使用关键字in支持逆变,等同于 Java 中的下界通配符? super;
    例如,参考 Kotlin 的泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//out
class Producer<out T> {
fun produce(): T {
...
}
}
val producer: Producer<TextView> = Producer<Button>() // 👈 这里不写 out 也不会报错
val producer: Producer<out TextView> = Producer<Button>() // 👈 out 可以但没必要
//in
class Consumer<in T> {
fun consume(t: T) {
...
}
}
val consumer: Consumer<Button> = Consumer<TextView>() // 👈 这里不写 in 也不会报错
val consumer: Consumer<in Button> = Consumer<TextView>() // 👈 in 可以但没必要
  • Kotin 的*星号投射是什么?
    也就是类似 Java 的?泛型通配符(相对于 ? extends Object),kotlin 也就是out Any,例如:var list: List<*>

  • where 关键字
    Java 设置多个边界可以通过&符号连接,而对应 Kotlin 的则是where关键字,例如:

    1
    2
    3
    4
    5
    //Java: T 的类型必须同时是 Animal 和 Food 的子类型
    class Monster<T extends Animal & Food>{
    }
    //Kotlin
    class Monster<T> where T : Animal, T : Food

10. 多行输入符——三个双引号

三引号的形式用来输入多行文本,也就是说在三引号之间输入的内容将被原样保留,之中的单号和双引号不用转义,其中的不可见字符比如/n和/t都会被保留。比如 OpenGLshader 代码用三引号的方式,代码更加直接直观。

1
2
3
4
5
6
7
val str = """ 
        one
        two
      """
// 等价于          
val str = "one\ntwo"       
val str =  "one" +"\n"+"two"

更多 tips 可以参考:不要用Java的语法思维来写Kotlin

—EOF—