Kotlin优势浅析 我们为什么应该使用Kotlin开发新项目

Kotlin优势浅析 我们为什么应该使用Kotlin开发新项目

由Jetbrains在圣彼得堡的团队开发,得名于附近的一个Kotlin的小岛。

Jetbrains有多年的Java平台开发经验,他们认为Java编程语言有一定的局限性,而且由于需要向后兼容,它们很难得到解决。因此,他们创建了Kotlin项目,主要目标包括:

  • 兼容Java
  • 编译速度至少同Java一样快
  • 比Java更安全
  • 比Java更简洁
  • 比最成熟的竞争者Scala还简单

与其他JVM上的语言一样,编译成Java字节码

https://www.kotlincn.net/docs/tutorials/ 中文文档

https://kotlinlang.org/docs/reference/comparison-to-java.html 英文文档中对比Java的索引

为什么要使用

非语言层面

  • Jetbrains自己用于开发桌面IDE
  • 谷歌钦定,不用担心跑路没支持
  • 谷歌Demo和很多开源项目开始全面采用,不学看不懂了
  • Android Studio支持完善

语言层面

对下文中提到的Kotlin做出的语言上的改进做一个总结

  • 通过语法层面的改进规范了一些行为
  • “消灭”了NPE
  • 语法更加灵活清晰/减少冗杂的代码
  • 变量声明更加符合直觉
  • 代码逻辑更加收敛/符合直觉
  • 减少样板代码的使用
  • 更舒服的lambda

最终归在一点: 集中精力 提高效率 减少错误

Kotlin做出的改变

变量/类型

// 只读变量声明(更友好) 想想final
val a: Int = 1 // 后置类型声明
// 一般利用类型推断,思维更加顺畅,不用再关心参数是什么类型的问题
val a = 5
val s = String()
val clazz = s.getClass()
val method = clazz.getDeclaredMethod("name", null)  
// 可变变量声明
var x = 5

不再有基本类型的概念,但运行时仍是基本类型表示(除了 Int? …这类 nullable 变量,是包装类型)。

数组用 Array<> 类型表示,现在数组也是不可协变的了。

控制流

if else 语句除了与java一致的用法外,还取代了条件运算符 ?:

val max = if (a > b) a else b // int max = (a > b) ? a : b;

但用法更加灵活:

return if (a > b) {
    print("return a")
    a
} else {
    print("return b")
    b
}

for 的语法糖:

for (i in array.indices) {
    println(array[i])
}
for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}
for (i in 1..3) {
    println(i)
}
for (i in 6 downTo 0 step 2) {
    println(i)
}

when 取代 switch ,更加强大的分支:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}
when (x) {
    in 1..10 -> print("x is in the range")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}
// 有返回值
val hasPrefix = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}
when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

类和对象

引入属性的概念,隐式的 gettersetter

class Test {
    var a = 0 // has setter and getter
    val b = 1 // just has getter
    private c = 2 // no setter and getter
}

val test = Test()
test.a = test.b // test.setA(test.getB())

再也不用写 setter/getter 逻辑了:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value)
    }

// 相当于
    
public String getStringRepresentation() {
    return this.toString();
}

public void setStringRepresentation(String value) {
    setDataFromString(value)  
}

java中已有的 getter/setter 会被“转换”成kotlin属性的形式

Kotlin优势浅析 我们为什么应该使用Kotlin开发新项目

Null安全

Kotlin优势浅析 我们为什么应该使用Kotlin开发新项目

图灵奖得主托尼·霍尔把 Null 这个设计称为十亿美元错误:“它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40 年中造成了十亿美元的损失”

Java中,引入了 @NonNull@Nullable 注解辅助进行静态检查,有局限性。

Java8引入 Optional<> 来解决这个问题,写起来比较恶心,难以推广

Kotlin希望在语言层面去解决这个问题->引入 Nullable 类型概念:

  • 声明为非空的变量永远都不会为空
  • 声明为可空的变量使用时必须判空
  • 利用推断来提高效率
    var nonnull: String = "must be inited with object"
    var nullable: String? = null
    

在语法层面做了诸多改进:

val l = s?.length // s != null, l = s.length else l = null, l: Int?
val l = s!!.length // same as l = s.length in java, l: Int
val l = s?.length ?: 0 //  s != null, l = s.length else l = 0, l: Int
return myValue ?: -1
// 链式使用:
bob?.department?.head?.name // 任一为null不执行

推断的作用(智能转换):

// b: String?
if (b != null && b.length > 0) {
    // b: String here
    print("length ${b.length}")
} else {
    print("Empty string")
}

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // automatically cast to `String`
        return obj.length
    }

    // `obj` is still of type `Any` outside of the type-checked branch
    return null
}

如果被Java调用,由于Java无法保证非空(除非已经使用 @NonNull 注解注明),从Java接收的参数必须是可空的。

实际使用中,使得定义变量时必须要考虑是否可为空的问题,在一开始时如果不适应这种思维,可能会滥用可空类型,给调用带来麻烦。

举个例子:

class MyFragment : Fragment() {
    private var manager: MyAPIManager? = null

    @Override
    public void onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        manager = MyAPIManager(context)
        manager.authorize()
    }
}

这里第二行中,由于Kotlin要求我们必须为属性赋予一个初值,但这里的初始化需要用到后面传入的 context ,按照Java的思维习惯,这个地方很容易就直接把类型改成可空的,然后给了个 null 的初值,但是这其实违背了Kotlin提供的特性:

  • 我们知道其实这个 manager 对象一旦被初始化之后就不会再为空,所以这应当是个非空类型
  • 同时我们为了后面去初始化它把它设成了 var ,实际上它并不应当被重新赋值,所以这应当是个 val 对象

Kotlin为我们提供了解决问题的方法:

懒属性(Lazy Property

当这个属性第一次被使用前再执地初始化代码,代码如下:

class MyFragment : Fragment() {
    private val manager: MyAPIManager by lazy {
        MyAPIManager(context)
    }

    @Override
    public void onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        manager.authorize()
    }
}

懒初始化属性(Lateinit Property)

在随后某个确定的时刻初始化,如果在使用时尚未被初始化,会抛出一个未初始化的运行时错误(与NPE略微不同),代码如下:

class MyFragment : Fragment() {
    lateinit var manager: MyAPIManager

    @Override
    public void onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        manager = MyAPIManager(context)
        manager.authorize()
   }
}

但这时 manager 仍是一个 var ,美中不足。

使用这样的机制可以确保这个对象的可空性满足我们的预期,也就是说经过这样的处理的对象,在Kotlin中永远不会报 NPE 。而确实可为空的对象,我们利用 ? 表达式结合合适的默认值,是可以把 NPE 消灭的。

但没有 NPE 是一件好事吗?错误可能会因默认值变得隐含,虽然不会导致Crash,但给定位bug增加了一定难度。

Kotlin也提供了报 NPE 的办法:使用 !!

何时用,用哪个?

  1. lateinit 只用于 var ,而 lazy 只用于 val 。如果是值可修改的变量(即在之后的使用中可能被重新赋值),使用 lateinit 模式
  2. 如果变量的初始化取决于外部对象(例如需要一些外部变量参与初始化),使用 lateinit 模式。这种情况下, lazy 模式也可行但并不直接适用。
  3. 如果变量仅仅初始化一次并且全局共享,且更多的是内部使用(依赖于类内部的变量),请使用 lazy 模式。从实现的角度来看, lateinit 模式仍然可用,但 lazy 模式更有利于封装初始化代码。
  4. 不考虑对变量值是否可变的控制, lateinit 模式是 lazy 模式的超集,你可以在任何使用 lazy 模式的地方用 lateinit 模式替代, 反之不然。 lateinit 模式在函数中暴露了太多的逻辑代码,使得代码更加混乱,所以推荐使用 lazy ,更好的封装了细节,更加安全。

https://kotlinlang.org/docs/reference/null-safety.html

https://dev.to/adammc331/understanding-nullability-in-kotlin

https://stackoverflow.com/questions/35723226/how-to-use-kotlins-with-expression-for-nullable-types

https://medium.com/@agrawalsuneet/safe-calls-vs-null-checks-in-kotlin-f7c56623ab30

函数、扩展方法、Lambda表达式

函数像其他函数式语言一样,成为了“一等公民”

  • 函数可以在任意地方声明(类外部,甚至是在函数内部)
  • 函数可以像对象一样通过参数传递:
    fun dfs() {
    }
    val f = ::dfs
    f(graph)
    

函数参数终于可以有缺省值了(不用 Builder 了):

fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') {
    // do something
}
reformat(str)
reformat(str, wordSeparator = ' ') // 可以使用参数的名字给缺省参数赋值
// 可以通过@JvmOverloads注解生成Java的重载形式便于Java来调用

扩展方法

比如说,给别人的 View 加一个功能,给定一个资源 Id ,去取得它对应的资源:

// 写一个Util类,作为参数传进去
public class ViewUtils {
    public static int findColor(View view, int resId) {
        return view.getResources().getColor(resId);
    }
}
ViewUtils.findColor(view, resId);

通过扩展方法来解决:

fun View.findColor(id: Int) : Int {
    return this.resources.getColor(id)
}

view.findColor(resId)

一系列这种类型的Java工具类在Kotlin中被“改造”成了扩展方法例如:

Collection.sort(list) 在Kotlin中直接 list.sort() 就可以了。

可以完全取代以往的 Util 类。

Kotlin提供的作用域扩展函数

语法简洁,逻辑连贯的最主要体现。

  • let/run
    • 对象作为参数传入 lambdarun 则作为 this
    • 返回值为 lambda 表达式的返回值
    • 常见场景:
      nullable
      
val length = s?.let {
    doSomething(it) 
    it.length
} ?: 0

// if...else...写法
private fun testIfElse(): Object? {
    return if (a !== null) {
        val b = handleA(a)
        if (b !== null) {
            handleB(b)
        } else {
            null
        }
    } else {
        null
    }
}
 
// ?.let写法
private fun testLet(): Object? {
    return a?.let { handleA(it) }?.let { handleB(it) }
}

  • apply
    • 对象作为 this 传入 lambda
    • 返回值为对象本身
    • 常见场景:
      • 初始化对象
        // old way of building an object
        val andre = Person()
        andre.name = "andre"
        andre.company = "Viacom"
        andre.hobby = "losing in ping pong"
        // after applying 'apply' (pun very much intended)
        val andre = Person().apply {
            name = "Andre"
            company = "Viacom"
            hobby = "losing in ping pong"
        }
        

return itemView.animation_like.apply {
    imageAssetsFolder = "images_feedcell/"
    loop(false)
    setAnimation("like_small.json")
    setOnClickListener(onClickListener)
}

  • also

    • 对象作为参数传入 lambda
    • 返回值为对象本身
    • 常见场景:
      • 链式调用中的副作用
        // transforming data from api with intermediary variable
        val rawData = api.getData()
        Log.debug(rawData)
        rawData.map {  /** other stuff */  }
        // use 'also' to stay in the method chains
        api.getData()
            .also { Log.debug(it) }
            .map { /** other stuff */ }
        

  • takeIf/takeUnless

    • 对象作为参数传入 lambda
    • 返回值为对象本身或 null (根据 lambda 中语句的 true or false
    • 常见场景:
      • 链式调用形式的条件判断
        val outFile 
           = File(outputDir.path).takeIf { it.exists() } ?: return false
        

混合使用举例:

// if...else...写法
private fun testIfElse(): Object? {
    return if (a !== null) {
        val b = handleA(a)
        if (b !== null) {
            handleB(b)
        } else {
            null
        }
    } else {
        null
    }
}
 
// ?.let写法
private fun testLet(): Object? {
    return a?.let { handleA(it) }?.let { handleB(it) }
}

简洁,避免大量判空 if 的使用

File(url).takeIf { it.exists() }
        ?.let {
            JSONObject(NetworkUtils.postFile(20 * 1024, "http://i.snssdk.com/2/data/upload_image/", "image", url))
        }?.takeIf { it.optString("message") == "success" }
        ?.let {
            post(content, contact, it.optJSONObject("data")?.optString("web_uri"))
        } ?: mHandler.post { view?.onFail() }

可以将逻辑划分清楚,直观,避免判空打断思路。

fun getMessages(context: Context, cursor: Int, direction: Int): ModelResult<MessageResponse> {
    return UrlBuilder(LOAD_NOTIFY)
            .apply {
                addParam("cursor", cursor)
                addParam("direction", direction)
            }.let {
                queryDataFromServer(it.build())
            }?.let {
                val statusCode = it.optInt("status_code", -1)
                val statusMessage = it.optString("status_message")
                if (statusCode == 0) {
                    MessageParser.parseMessageList(it.optString("data"))
                            ?.let {
                                ModelResult(true, statusMessage, it)
                            }
                            ?: ModelResult<MessageResponse>()
                } else {
                }
            }
            ?: ModelResult<MessageResponse>())
}

同样是划分逻辑,更加清晰?(需要适应)

附图一张:

Kotlin优势浅析 我们为什么应该使用Kotlin开发新项目

https://medium.com/@elye.project/using-kotlin-takeif-or-takeunless-c9eeb7099c22

https://proandroiddev.com/the-tldr-on-kotlins-let-apply-also-with-and-run-functions-6253f06d152b

https://proandroiddev.com/the-difference-between-kotlins-functions-let-apply-with-run-and-else-ca51a4c696b8

Lambda表达式

本质上是一个匿名方法(单方法接口)

fun isGreaterThanZero(n: Int): Boolean {
    return n > 0
}

collection.filter(::isGreaterThanZero)
// 使用Lambda
collection.filter{ i: Int -> i > 0 }
collection.filter{ i -> i > 0 }
collection.filter{ it > 0 }

单方法接口都可以传 Lambda

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        showToast();
    }
});

button.setOnClickListener{ showToast() }

内置常用的流处理 Lambda

names
        .filter{ it.startsWith("A") }
        .sortedBy{ it }
        .map{ it.toUpperCase() }
        .forEach{ print(it) }

配合 Rxjava 使用更佳(也有 Rxkotlin )。

在Android上

官方提供的扩展

View Binding

全自动,无需声明,无需 findViewById ,直接使用 layout id 就行

text_view.text = "text"

@Parcelable 注解

一个注解自动实现 Parcelable , 仍在实验阶段

异步

// uiThread如果是Activity isFinish = true是不会调用的
doAsync {
    print("其他线程")
    uiThread { 
        print("UI线程")
    }
}

Gradle DSL

https://docs.gradle.org/current/dsl/index.html

其他黑科技

  • 没有必检异常了
  • 支持运算符重载( String 的比较可以用 == 了)
  • 接口可以有缺省方法
  • Object Class 单例
  • Data Class 数据类型,自动实现 equals/hashCode/toString
  • 协程(没用过)
  • 伴生对象
  • Anko 扩展

https://www.jianshu.com/p/9f720b9ccdea

https://www.tuicool.com/articles/aEbeayN

https://github.com/android/android-ktx

https://github.com/adisonhuang/awesome-kotlin-android 其他开源

与Java协作

结论:100%协同,Kotlin调Java没有问题,Java调Kotlin会有些绕,但不会出问题。

性能对比

运行性能

来源: https://blog.dreamtobe.cn/kotlin-performance/

  • 性能相比Java更差相关
    • varargs 参数展开,Kotlin比Java慢1倍,主要原因是在Kotlin在展开 varargs 前需要全量拷贝整个数组,这个是非常高的性能开销。
    • Delegated Properties 的应用,Kotlin相比Java慢10%。
  • 性能相比Java更优相关
    • Lambda 的使用,Kotlin相比Java快30%,而对用例中的 transaction 添加 inline 关键字配置内联后,发现其反而慢了一点点(约1.14%)。
    • Kotlin对 companion object 的访问相比Java中的静态变量的访问,Kotlin与Java差不多快或更快一点。
    • Kotlin对局部函数( Local Functions )的访问相比Java中的局部函数的访问,Kotlin与Java差不多快或更快一点。
    • Kotlin的非空参数的使用相比没有使用空检查的Java,Kotlin与Java差不多快或更快一点。
  • Kotlin自身比较
    • 对于基本类型范围的使用,无论是否使用常量引用还是直接的范围速度都差不多。
    • 对于非基本类型范围的使用,常量引用相比直接的范围会快3%左右。
    • 对于范围遍历方式中, for 循环方式无论有没有使用 step 速度都差不多,但是如果对范围直接进行 .foreach 速度会比它们慢3倍,因此避免对范围直接使用 .foreach
    • 在遍历中使用 lastIndex 会比使用 indices 快2%左右。

包大小

标准库大小 100k 左右。

新建标准工程(不带Kotlin支持),开启混淆,打release包。

将这个工程文件转为Kotlin实现,引入Kotlin支持,打release包,对比大小:

Kotlin优势浅析 我们为什么应该使用Kotlin开发新项目

增加了约 109k

将某应用一个module转换为Kotlin实现(直接使用AS的工具转换),对比编译生成的所有 class 文件大小:

Kotlin优势浅析 我们为什么应该使用Kotlin开发新项目

增加了约2%的体积。

https://discuss.kotlinlang.org/t/kotlin-generated-class-file-out-of-kt-is-bigger-than-java-file/1520/4

https://blog.dreamtobe.cn/2016/11/30/kotlin/

原文 

http://www.viseator.com/2018/07/11/introduce_kotlin/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Kotlin优势浅析 我们为什么应该使用Kotlin开发新项目

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址