Why Kotlin

Posted 陈旭金-小金子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Why Kotlin相关的知识,希望对你有一定的参考价值。

从 2020.11 开始全面使用 Kotlin 开发. 以前没有使用 Kotlin 之前, 我一直以为只是在 Java 的基础上多了很多的语法糖. 但是随着使用的深入, 我发现 Kotlin 是一门新的语言.

现在的我已经喜欢上 Kotlin 了, 相对 Java 来说. Kotlin 的优势是巨大的. 我这边就简单的总结一下, 为什么我现在推荐 Kotlin 了. 如果你还在使用 Java 没有使用 Kotlin, 那你一定要仔细的看完文章哦

参数默认值

这个特性, 直接让 Java 中的大量重载的方法都消失了. 你只要一个函数或者方法就可以啦. 可以节省大量的模板代码

在 Java 中你可能需要这样子定义两个重载方法来支撑需求:

public int getInt(String key) 
  return getInt(key, 0)


public int getInt(String key, int def) 
  // 实现

使用 Kotlin 是这样的

// 这个方法的 def 参数就有了默认值 0, 调用方就可以可选的传 def 的值.
fun getInt(key: String, def: Int = 0) 
  // 实现


// 下面的调用都是可以的. 默认的参数可以不传
val value = getInt(key = "age")
val value = getInt(key = "age", def = 1)

更简洁的 Lambda 表达式

注册一个点击事件为例, Java8 中的 Lambda 是这样的

view.setOnClickListener(v -> 
    // 实现点击 
);

Kotlin 是这样的

view.setOnClickListener  view ->


// 进一步省略参数的名字还可以这样

view.setOnClickListener 
   // 默认名字是 it

空安全处理

Kotlin 中你的空指针问题可以得到最好的解决. 因为使用一个可空的变量的时候, Kotlin 在编译的时候让你处理了. 再也不是像 Java 一样把这种风险带到运行时期. 虽然 JDK 和 android 的大量源码中对返回值或者参数都会使用 @Nullable 或者 @NonNull 注解来标记. 但是这个最多也是起到提醒的作用. 开发人员自己写的代码, 据我多年的观察, 根本连 @Nullable 或者 @NonNull 注解都是懒的标记的. 所以 Kotlin 把这可空问题要求程序员在编译的时候解决是极好的!. 变相着让开发人员在编译期间对每一个变量和返回值的可空还是不可空做了妥善的处理!

比如这里准备对一个可空的 name 调用方法的时候, 编译器是会报错的. 会告诉你使用 ?或者 !! 来处理

? 表示如果不为空, 将会执行调用. 是我们平常使用较多的方式. 而如果此处你认为一定不会为空, 那你可以自信的使用 !!, 但是如果为空, 就直接抛出 NullPointException 哦

扩展属性和函数

Kotlin 提供了扩展函数和属性来让我们的开发工作变得更简单. Kotlin 官方团队对原有的一些 Java 代码提供了大量的扩展. 很多人可能觉得, 扩展方法不就是 Java 中的一些工具类吗? 其实本质是一样的. 但是使用方式不一样. Java 你可能:

  1. 知道工具类是哪个
  2. 会打断编写代码的连贯性

下面列举一些常用的扩展(特别是针对集合有很多好用的扩展):

// 最后一个元素的下标, 是一个扩展属性
listOf(1, 2, 3, 4, 5).lastIndex

// forEach 的增强, 可以同时拿到 index 和循环的 item
listOf(1, 2, 3, 4, 5)
        .forEachIndexed  index, item ->
            //
        

// 找到第一个满足条件的
val result: Int? = listOf(1, 2, 3, 4, 5)

        .find  it == 1 

// 找到最后一个满足条件的

val result: Int? = listOf(1, 2, 3, 4, 5, 2)
        .findLast  it == 2 

// 过滤出能被 2 整除的书
val resultList = listOf(1, 2, 3, 4, 5)
        .filter  it % 2 == 0 

// 是否所有的都满足 > 2
val b = listOf(1, 2, 3, 4, 5)
        .all  it > 2   

// 是否有一个满足 > 2
val b = listOf(1, 2, 3, 4, 5)
        .any  it > 2 

// 对数据进行累加, 累乘的话 a + b 变成 a * b 即可
val sum = listOf(1, 2, 3, 4, 5)
        .reduceOrNull  a, b -> a + b

// 如果集合就是数字类型的. 还可以有直接对的 sum 方法
val sum = listOf(1, 2, 3, 4, 5).sum()

// 对集合数据进行翻转
val list = listOf(1, 2, 3, 4, 5).reversed()

// 对集合去重
val list = listOf(1, 2, 3, 4, 5, 2).distinct()

// String 转字节数组
val name: String = "123123"
val arr: ByteArray = name.encodeToByteArray()

// 重复 N 个字符串
val result = "hello".repeat(4)

// 格式化字符串
val str: String = "name = %s"
val result = str.format("小金子")

// 更新 View 的 LayoutParams
val view: View = xxx
view.updateLayoutParams  
  this.width = xxx
  this.height = xxx


// 更新 View 的 pandding
val view: View = xxx
view.updatePadding(
    top = 10, bottom = 20
)

// 复制文件
val file1 = File("xxxx")
val file2 = File("xxxx")

// Kotlin 提供的 copyTo 扩展, 可以将 file1 复制到 file2
file1.copyTo(target = file2)

// RxJava disposable 的添加
val disposables = CompositeDisposable()
Observable.just(1,2,3,4)
    .subscribeBy 
      println("value = $it")
    
    .addTo(disposables)

// 还有很多很多好用的扩展, 等着你自己去发现吧. 
// 因为真的太多了, 目前基本上所有常用的都有对应的扩展, 所以平时没事多看看有哪些扩展吧.
// 就可平常的方法一样, 在对象的后面用 . 让 Idea 提示你有哪些方法即可
// ........................

扩展方法、属性 可以让我们编写代码的时候更加连贯.

但是这个特性也容易被滥用. 但是用的好了会让开发十分的开心

协程

在充斥着异步环境下的 Android 开发中. 开发人员经常需要在子线程处理任务, 然后在 Callback 中继续处理后续的事情.

在没有协程出现的时候. 大家使用 RxJava 来切换线程, 借此来组织多个任务之间的顺序. 但是我这里希望你明白, RxJava 本质上是一个基于观察者实现的流呀. 切换线程只是恰好是它提供的一个操作符而已.

但是我们开发者最初的诉求是:

  1. 想让多个代码块顺序的执行.
  2. 每个代码块可以执行在不同的线程

所以在之前没有协程的选择下, 大家发现 RxJava 能满足需求. 以至于几年前全都是吹 RxJava 的. 但是 RxJava 适不适合呢?其实是不适合的. 原因有以下几点:

  1. RxJava 学习门槛很高, 就算一个老手, 也不能百分百写对代码(这点是最最最最重要的)
  2. 正因为第一点, 你不能保证一个团队的人对 RxJava 认知都是停留在一个比较高的水平的.
  3. RxJava 本质上是一个基于观察者实现的流, 虽然他确实能实现上面我们的诉求, 但是我们平常的代码就是一行一行的语句. 和流其实没有啥关系. 如果你想实现的类似于 “多 N 个图片进行异步上传” 这种功能, 那么 RxJava 是适合的.

现在协程出现了, 它能满足我们上述的诉求之外, 它还能让我们开发并发的代码是比较容易的.

这里演示了一个很简单的异步获取数据更新 UI 的场景. 你会协程可以让你用简单的顺序的代码, 就可以编写出异步的需求

// 此方法是耗时的, 需要在子线程中执行, 此方法需要在协程中被调用
suspend fun getData(): String 
  return withContext(Dispatchers.IO) 
    // 这里同步做耗时的操作即可
    "TestResult"
  


fun main() 
  GlobalScope.launch(Dispatchers.Main) 
    // UI 线程拿到 IO 线程执行的结果
    val result = getData()
    // 更新 UI
    println("拿到数据, 更新 UI")
  

协程可以让程序员编写异步的代码更加的简单, 并且入门简单. 这点是极好极好的.

了解更多, 可以自行查阅相关文章, 这里不做过多的赘述. 这里给几个相关的文档地址:

  1. https://rengwuxian.com/kotlin-coroutines-1/
  2. https://rengwuxian.com/kotlin-coroutines-2/
  3. https://rengwuxian.com/kotlin-coroutines-3/
  4. https://kotlinlang.org/docs/coroutines-overview.html

前三个花费 30 分钟看完, 你基本就上手了. 就是这么简单

操作符重载

什么是操作符重载, 简单来说就是 Kotlin 提供了一些固定的操作符比如:

+, -, *, / 等等还有其他的

我们可以定义扩展方法使用官方固定的名称作为方法名称. 就可以重载操作符

官方有提供一些重载的实例. 可以让我们的开发更加的贴近实际场景. 先来看看具体使用的场景:
比如重载了 + 和 - 操作符的集合相加

val list1 = listOf(1, 2, 3, 4)
val list2 = listOf(7,8,9,0)
val list3 = list1 + list2
val list4 = list1 - list2

这就是重载 + 号操作符的扩展方法. 具体信息, 请查阅相关资料. 这里给出官方的文档地址:

https://www.programiz.com/kotlin-programming/operator-overloading

委托模式

这个模式我真心推荐使用呀. 特别是类的委托. 举个例子:

  1. 以前我们使用装饰者模式去增强一个原有类的功能, 需要把所有方法都转发给目标对象. 然后针对性的对个别方法进行额外的编码. 现在使用 by 委托, 可以把那些模板代码都给删了. Kotlin 自动会帮我们转发
  2. 还有一个很重要的用法是. 以前我们拆分了 N 个(接口 + 实现类)去编写不同的业务逻辑. 但是我们很多时候, 是需要组合其中 N 个功能为一个来使用的. 这时候 by 的作用又体现出来了. 我们可以声明一个 Api1, 让这个 Api1 继承 Api2 和 Api3, 然后把 Api2 和 Api3 的方法都委托给 ApiImpl2 和 ApiImpl3. 这样子可以让 ApiImpl1 的实现中没有那些转发的模板代码. 同时又让 ApiImpl1 有了 Api2 和 Api2 的功能

例子:

// 功能1
interface UseCase1 

class UseCaseImpl1: UseCase1 


// 功能1
interface UseCase2 

class UseCaseImpl2: UseCase2 


// 这样子 UseCaseImpl 就可以轻松继承了 UseCase1 和 UseCase2 对的功能. 
// 可以自由的组合各种细小的功能!
class UseCaseImpl: UseCase1 by UseCaseImpl1(), UseCase2 by UseCaseImpl2() 

StreamApi

Kotlin 很对集合提供了很多很好用的扩展方法. 比 Java8 中的 StreamApi 还要好用很多. 这也是我很喜欢的一点. 可以看出 Kotlin 团队为了开发人员使用的更舒适做了很多的努力.

这些 api 大多和 RxJava 的操作符的功能一致. 所以对于我来说, 使用的非常开心. 这里就不做举例了, 因为上面的扩展方法一处我列举了不少

其他

  1. 省去了 new 关键字去创建对象
  2. kt 文件中可以直接编写函数, 顶级的函数, 任何地方可调用
  3. 协程中的 Flow 可以对标 RxJava. 热流也正在开发. 但是流的这块可能不是很稳定, 希望这几年能稳定下来. 越来越好
  4. 未完待续…

以上是关于Why Kotlin的主要内容,如果未能解决你的问题,请参考以下文章

访问公共类 (Kotlin)

Kotlin 在片段中找不到按钮 ID,为啥?

Kodein + Ktor = 冻结 kotlin.collections.HashMap 的突变尝试 - 为啥?

Kotlin 编译器抱怨在属性定义中使用 SPeL 表达式。为啥?

带有注释的 kotlin 数据类,为啥 @DateTimeFormat 注释可以在没有定位的情况下工作

Kotlin 处理位操作Flag 快捷方法