Android开发从Java到Kotlin,Kotlin的那些好与不好

0. 背景

Kotlin 已经成为了Android 官方支持的语言了,目前像 androidx 里面的库,基本都已经对kotlin有了支持,而且从官方的姿态来看,以后kotlin才会是主流,而java由于一些历史和法务的原因,将在Android的平台上浅浅被Kotlin取代。

本文中,将从一个Android开发者的角度,来聊一聊Kotlin相对于Java有哪些很不错的特性,当然也有它的缺点,希望大家可以取其精华。

1. Kotlin 的优点

这一章节中,将介绍下Kotlin相对Java的一些优点,也是一些在平时开发中使用比较频繁的功能点。有一些是Kotlin从其他现代语言中借鉴的优点特性,有一些是语法糖,不过都是一些很不多的优点。

1. 空安全

Kotlin 对自己的语言设计很自豪的一个特性,就是“空安全”,声称可以避免大部分的Java里面的NullPointerException。

Kotlin中的空安全总的来说有以下几个方面:

  • 可空的类型声明:在声明一个遍历的时候,必须要指定它是否是可以为空的,可以为空和不可以为空将会是按照不同的类型处理,比如 StringString?是不同的类型;
  • 可空类型的访问:在有了可空类型的声明之后,对变量的使用也加上了判断,对于可空的类型必须有判空或者使用安全访问(?.的语法)

空安全特性在实际使用中确实能带来很大的好处,能够在语言层面强制要求开发者对所有的可能为空的变量进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun demo(arg :String?) {
// 下面的代码将会无法通过编译,一维arg变量是可以为空的,必须对其进行处理
// 处理方式有以下几种可以选择
var length = arg.length
// 处理方式1
length = arg?.length
// 处理方式2
if (arg != null) {
length = arg.length
}
// 处理方式3
arg.let{
length = it.length
}
}

2. 强制 setter 和 getter

熟悉Java的同学,一定知道Java的规范写法中会提倡使用 settergetter 的写法,而不是直接对熟悉进行访问。这样做的好处就不赘述了。只是这样写的话,会带来很多的样板代码,会在项目中出现大量的 setXXXgetXXX 样式的代码。而Kotlin则是直接在语法层面解决了这个问题。

Kotlin中把对熟悉的访问强制使用setter 和 getter,那怕看起来像是直接读取一个成员变量,其实本质上还是访问的它的 getter 方法。这样就直接封死了直接操作成员变量的路。

这样就能完美避免很多 setXXXgetXXX 的方法了,需要一个属性,“直接访问“就是了

1
2
3
4
5
6
class Point{
// 直接访问x其实本质上还是调用它的set 和 get方法
var x:Float
set(value)
get()
}

3. 强大的 when 表达式

Java中逻辑分支的执行有if elseswitch 这样的语法支持,在Kotlin 里面提出了when表达式,这个是综合了它们的优点,并且增强了不少。

when关键字用于条件分支,看起来很像是 switch 语句,不过它和switch语句最大的不同在于,when 语法支持的是一个表达式,而表达式就意味着,它是可以有结果的,可以作为赋值语句的右值。

以下看一个简单的例子,这个needWork 方法可以根据输入的星期的字符串,来判断是否需要上班:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun needWork(arg:String?):Boolean {
// when 表达式可以作为赋值语句的右值
val result = when() {
"星期六","星期日" -> {
false
}
"星期一","星期二","星期三","星期四","星期五" -> {
true
}
else {
// 其他的语句,非法输入
false
}
}
return result
}

怎么样?是不是使用很简单?是不是增强版的switch和 if else。

4. 协程

协程的概念是在go语言的兴起之后才逐渐火起来的。协程是轻量级的线程,因为Java 的线程是资源消耗很大的,它直接对应于底层操作系统的线程,而这就限制了在JVM中无法非常大量地创建线程。而协程相对就轻量很多,可以随便创建成千上万个都无压力。

Kotlin 就官方支持了协程的特性,虽说在Android开发中没有需要创建成千上万个协程的场景,但是有了协程后,一些异步的任务就变得非常得方便了。

1
2
3
4
5
6
7
8
9
fun demo(){
GlobalScope.launch {
// 这里就是一个子线程的异步任务了
}
withContext(Dispatchers.MAIN) {
// 这里是运行在主线程的,可以用来更新UI
}
}

5. lazy 初始化(属性委托)

熟悉Java设计模式的同学一定会知道“委托模式(也称为代理模式)”,而Kotlin则直接将委托的机制实现在了语法层面,那就是使用 by 关键字。

不过这里我们不讲类的委托,而是讲一下 Kotlin 里面的属性委托,属性委托可以把一个成员的熟悉,委托给另一个属性来,最常用的就是用来实现懒加载。

1
2
3
4
5
6
7
class Demo {
// a 成员是使用的Lazy 懒加载,这个变量a并会随着对象的创建初始化,
// 而是会在a第一次被访问的时候进行初始化
val a:String by lazy {
"Hello World"
}
}

6. lambda 表达式

lambda 表达式基本是现代语言人手一个的特性了,你一个编程语言要每个lambda 表示式都不好意思说自己是现代语言。不过这个在Java8之后Java也是支持了的。这个就不讲了

7. 扩展功能(装饰器模式)

Kotlin支持通过扩展功能 的特性来对一些当前已经存在的类方法进行扩展,而不用去改变现有的类。这个其实就是把设计模式中的“装饰器模式”直接内置到语法层面了。

比如基于现有的String类,给它扩展一个 hellowWorld出来:

1
2
3
4
5
6
7
8
9
10
fun String.hellowWorld(){
println("Hello World")
}
fun main(args[]:List<String>) {
val s = String("Demo")
// 会打印 Hello World
s.helloWorld()
}

不过需要注意的是,Kotlin中的扩展功能,并不是真的修改了原有的类的结构,它只是允许给声明了扩展功能的类,能够使用”.” 点访问而已,其实本质上是一个语法糖。

当然,Kotlin 的扩展功能也是不支持多态的,在某一个类上进行扩展,那就只是支持这个类,而不是去动态分发给它的实际的子类类型。

不过用起来是挺爽。

8. 扩展的集合

有了上一章节提到的“扩展功能的支持”,其实就能搞一些好玩的事情了,可以对一些内置类的功能进行一些扩展,比如:Kotlin对Java的集合类就进行了扩展。

Kotlin中的集合类,底层也是基于Java的集合类框架的,同时在上层使用扩展功能 来给原来的Java集合类框架添加了一些很实用的其他功能。比如说分组、聚合操作(求最大值、最小值、平均值等等)等。

这里有非常多的实用方法,具体可以参考:
https://www.kotlincn.net/docs/reference/collections-overview.html

9. 对象声明

单例模式在实践中非常常见,在Kotlin里面也有了专门针对单例模式的支持,那就是对象声明:

1
2
3
object SomeSingleInstance {
// 这就是一个单例对象,可以在这里做其他的操作
}

声明一个单例模式的对象变得如此简单(终于不用再搞单例模式的茴香豆写法了~)

10. 类型推断

类型推断也是很多语言都已经有了的,而且在Java的新版本中也是已经支持了的。

对于实际的开发来说,类型推断真的是一个非常非常好用的功能,能够给开发者节省很多的时间。

类型推断其实简单来说就一句话:当编译器能明确知道一个变量的类型的话,那么开发者就可以不用再写出来了。

1
2
3
4
fun demo(){
// 很明显,s是 String 类型,就可以省去类型声明了
val s = "Hello World"
}

其他

当然还有其他的很多不错的特性,比如说支持函数式编程,再比如可以使用tailrec关键字来在代码层面指定进行尾递归优化等等,这里就不一一展开了,这里只是介绍我本人在实践中觉得好用而且常用的一些特性。包括但不限于这些:

  • 字符串插值(或者成为字符串模版)
  • 条件表达式:即条件判断语句if else 可以具有返回值

2. Kotlin 的缺点

当前,有优点,就肯定有缺点,目前还没有发现什么完美的事物是无缺的。那么接下来的章节,就来说一说Kotlin的缺点

1. 与Java结合使用的空安全

上面的章节提到了Kotlin中很好的特性“空安全”,这个特性用起来稍有不慎就会有坑,比如说在和Java库结合使用的时候,Kotlin和Java的互相调用,你会发现稍不留神就会出现一个 KotlinNullPointerException

这种问题就是在声明了非空的Kotin方法中,接收到了来自Java库传进来的一个null。毕竟在Java中没有这样类似的空类型检测,所以无法保证传递给Kotlin 的是非空的。

所以在Kotlin和Java相互调用的时候一定要万分小心。

2. 语法层面抛弃了static类型

Kotlin里面抛弃了Java里面的 static 关键字。反而是提出了类似的 companion object 来完成静态成员的功能。

这个怎么说呢,可以理解Kotlin是为了实现 “一切皆对象” 的设计目标,如果保留了静态成员就达不到这个目标了。

但是这个粗暴地去掉了 static 关键字,也还是有很多不方便的地方的,这样就无法定义一些类级别的成员了,而伴生对象本质上还是一些成员对象,也无法做到和Class相关。

3. 抛弃了 Checked Exception

最后说这个,Kotlin 里面抛弃了对 Checked Exception。

先说下什么是 Checked Exception,在Java中,当一个方法的声明中有 throws 关键字来说明这个方法可能会抛出一些异常的时候,那么调用者则需要处理这些异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Demo {
private void a throws IOException(){
}
pribate void b (){
try{
// 由于方法a() 声明了 Checked Exception,所以调用者必须使用
// try catch 代码块来对异常进行捕获
// 如果不加上 try catch 的话,代码编译是通过不了的
a();
} catch(IOException e) {
// 处理异常
}
}
}

而在Kotlin中则取消了这个特性,即便调用了声明 Checked Exception 的方法而不加try catch,在Kotlin中仍然是可以编译通过的。

我认为这个做法确实是一个糟糕的设计。官方的说法是在大型项目中过多地使用 Checked Exception 会导致生产力降低生产力(https://www.kotlincn.net/docs/reference/exceptions.html)。这个观点我是无法认同的。Checked Exception 虽然在代码里面增加了很多 try catch 的代码,会让代码看起来比较丑陋,但是这个这样做却可以保证整体代码的下限不会太低,而去掉了这个Checked Exception 则使得这个下限低了很多。因为我们无法确定我们调用的方法底层有哪些地方声明了Checked Exception,这就使得我们的代码暴漏在潜在的风险里面,在某些不确定的时候将会出现Exception。

而这些Exception原本是可以通过 try catch 搞定的。

坚持原创技术分享,您的支持将鼓励我继续创作!