Kotlin 小记

94 阅读3分钟

表达式思维

在 Kotlin 当中,Any 是所有类型的父类,我们可以称之为根类型

Nothing 就是 Kotlin 所有类型的子类型。Nothing 才是底类型,而“Nothing?”则不是底类型。 图片.png

不变性思维

Mutable -可变的

集合是可变的(比如 MutableList),不可变的(比如 List)

  • 第一条准则:尽可能使用条件表达式消灭 var
  • 第二条准则:使用数据类来存储数据,消灭数据类的可变性。
class Person {
    var name: String? = null
    var age: Int? = 0
}

我们可以将 var 都改为 val,就像下面这样:

// var -> val
data class Person(
val name: String?,
val age: Int?
)

而到这里,你可能就会产生一个疑问:Person 的所有属性都改为 val 以后,万一想要修改它的值该怎么办呢? 比如说,直接修改它的值的话,这段代码就会报错:

class ImmutableExample {
    // 修改Person的name,然后返回Person对象
    fun changeUserName(person: Person, newName: String): Person {
        person.name = newName // 报错,val无法修改
        return person
    }
}

这一点也是我们要尤为注意的:我们从 Java、C 那边带来的习惯,会促使我们第一时间想到上面这样的解决方案。但实际上,Kotlin 更加推崇我们用下面的方式来解决:

class ImmutableExample {
    fun changeUserName(person: Person, newName: String): Person =
        person.copy(name = newName)
}
注意:使用copy()方法, 每次都会创建一个新的对象, 如果不注意在for循环里面使用该方法, 很可能会造成内存抖动

在这段代码中,我们并没有直接修改参数 person 的值,而是返回了一个新的 person 对象。我们借助数据类的 copy 方法,快速创建了一份拷贝的同时,还完成了对 name 属性的修改。类似这样的代码模式,就可以极大地减少程序出 Bug 的可能。

  • 第三条准则:尽可能对外暴露只读集合
//方式1
class Model {
    val data: List<String> by ::_data
    private val _data: MutableList<String> = mutableListOf()

    fun load() {
        _data.add("Hello")
    }
}
//方式2
class Model {
    val data: List<String>
        get() = _data // 自定义get
    private val _data: MutableList<String> = mutableListOf()

    fun load() {
        _data.add("Hello")
    }
}
//方式3
class Model {
    private val data: MutableList<String> = mutableListOf()

    fun load() {
        data.add("Hello")
    }

    // 变化在这里
    fun getData(): List<String> = data
}

以上这三种方式,本质上都是对外暴露了一个“不可变的集合”,完成了可变性的封装

  • 第四条准则:只读集合底层不一定是不可变的,要警惕 Java 代码中的只读集合访问行为。
class Model1 {
    val list: List<String> = listOf("hello", "world")
}

public List<String> test() {
    Model model = new Model();
    List<String> data = model.getData();
    data.set(0"Some Data"); // 抛出异常 UnsupportedOperationException
    return data;
}

public class ImmutableJava {
    public List<String> test1() {
        Model1 model = new Model1();
        List<String> data = model.getList();
        System.out.println(data.get(0));
        data.set(0, "some data"); // 注意这里
        System.out.println(data.get(0));
        return data;
    }
}

// 结果
hello
some data

我们在 Java 代码当中调用 data.set() 方法,并没有引起异常,程序也正常执行完毕,并且结果也符合预期。在这种情况下,Kotlin 的 List 被编译器转换成了 java.util.ArraysArrayList类型。因此,我们Kotlin当中的只读集合,在Java当中就变成了一个普通的可变集合了。事实上,对于KotlinList类型来说,在它转换成Java字节码以后,可能会变成多种类型,比如前面我们看到的SingletonListjava.util.ArraysArrayList 类型。因此,我们 Kotlin 当中的只读集合,在 Java 当中就变成了一个普通的可变集合了。 事实上,对于 Kotlin 的 List 类型来说,在它转换成 Java 字节码以后,可能会变成多种类型,比如前面我们看到的 SingletonList、java.util.ArraysArrayList,甚至还可能会变成 java.util.ArrayList。在这里,我们完全不必去深究编译器背后的翻译规则,我们只需要时刻记住,Kotlin 当中的只读集合,在 Java 看来和普通的可变集合是一样的

  • 第五条准则:val 并不意味着绝对的不可变
object TestVal {
    val a: Double
        get() = Random.nextDouble()

    fun testVal() {
        println(a)
        println(a)
    }
}

// 结果
0.0071073054825220305
0.6478886064282862

空安全思维

interface Callback<T: Any> {
    fun onSuccess(data: T)
    fun onFail(throwable: Throwable)
}

泛型边界“T: Any”保证 T 类型一定是非空的。

fun <T> saveSomething(data: T) {}
//   ↑ 
//  等价              
//   ↓                      
fun <T: Any?> saveSomething(data: T) {}
// 增加泛型的边界限制              
//       ↓                      
fun <T: Any> saveSomething(data: T) {
    val set = sortedSetOf<T>()
    set.add(data)
}

fun main() {
//              编译无法通过
//                  ↓
    saveSomething(null)
}

Log工具类:

@file:JvmName("LiLog")

package com.lixiang.car.weather.util

import android.util.Log

const val LITAG = "WeatherApp."

inline fun Any.logd(msg: String) {
    Log.d(LITAG + this::class.java.simpleName, msg)
}

inline fun Any.logd(mTag: String?, msg: String) {
    var tag = LITAG + this::class.java.simpleName
    mTag?.let {
        tag += ".$it"
    }
    Log.d(tag, msg)
}

inline fun Any.logi(msg: String) {
    Log.i(LITAG + this::class.java.simpleName, msg)
}

inline fun Any.logw(msg: String) {
    Log.w(LITAG + this::class.java.simpleName, msg)
}

inline fun Any.loge(msg: String) {
    Log.e(LITAG + this::class.java.simpleName, msg)
}

使用方法

Kotlin:
logd("WeatherDataModel 3.2+++++TEST+++++++")
logd("TEST", "WeatherDataModel 3.2+++++TEST+++++++")

Java:
LiLog.logd(this,"LocationCompatVL logw3+++++TEST+++++++");
LiLog.logd(this,"TAG","LocationCompatVL logw3+++++TEST+++++++");

输出:
 D WeatherApp.LocationCompatVL: LocationCompatVL logw3+++++TEST+++++++
 D WeatherApp.LocationCompatVL.TAG: LocationCompatVL logw3+++++TEST+++++++
 D WeatherApp.WeatherDataModel: WeatherDataModel 3.2+++++TEST+++++++
 D WeatherApp.WeatherDataModel.TEST: WeatherDataModel 3.2+++++TEST+++++++