本文作者
作者:kotlon
链接:
https://www.jianshu.com/p/dcf6cd7c59a3
本文由作者授权发布。
1 kotlin 优势kotlin 在 17 年 google io 大会上确定为亲儿子,android studio canary 3.0 版本开始,直接支持 kotlin 语言,不需要额外安装 as plugin。
kotlin 的出现,给 android 开发者带来了极大的活力,现在的 android 工业开发,讲究的是语法糖,效率,性能,高质量,以及可拓展性,而 kotlin 的出现给 java 开发者带来了极大的福音。
kotlin 官网教程英文
https://kotlinlang.org/docs/reference/basic-types.html
kotlin 官网教程中文
https://www.kotlincn.net/docs/reference/
阅读此文章大约半小时~
如何阅读?
浏览kotlin 优势和kotlin 坑这两大章节,然后去学习 kotlin 基础语法,根据第三章,kotlin 普及里的建议,可以在实际开发中,开干了。
kotlin 默认是非空的,如果你需要声明一个可能为空的变量,那么如下:
private
这里的 mName 变量声明为,String 类型;mSubName 声明为 String?。前者不能为 null,后者可以为 null。
然后,你可能经常在 java 中写这样的代码:
if (mANRPA !=
那么在 kotlin 你这要这样写就行了:
mANRPA?
使用 ?. 的效果和上面的 java 代码是一样的,表示该变量可能为空,如果不为空则执行后面的表达式。
参考地址:
https://www.kotlincn.net/docs/reference/null-safety.html
java 语法完全兼容 kotlin
你可以通过几个小时学习 kotlin 的基础语法,包括:
怎么定义变量
怎么定义方法
怎么定义类
等等......
然后就可以像 java 一样使用 kotlin,当然这不是我们的最终目的,因为 kotlin 的语法糖才是我们最后的目标。
google 发布了 kotlin 简易教程,大概只需要几个小时,就可以看完:
几小时上手 kotlin
在实际的开发中,大概第一个星期内,你写代码的速度会下降一些,但是一个星期之后,完全上手了,写代码的速度是有很大的提升的。
2 kotlin 高级语法(语法糖示例)关于 扩展函数的说明:
https://www.kotlincn.net/docs/reference/extensions.html
android ktx 库提供了一系列的优秀的扩展函数 官方说明
https://android.github.io/android-ktx/core-ktx/index.html
简单的说,扩展函数是什么呢?先来看一个例子,在java 里面 你要 remove 一个 View 的 Parent,你可能会这样写:
if(mLoadingView.getParent() !=
但是每次都这样写,貌似比较重复,对吧;然后你就可能考虑写一个静态方法,类似如下,比如在 ViewUtils 类似命名里写一个静态函数:
public static void removeSelf(View view){
但是在 kotlin 中,使用扩展函数,可以更巧妙更直接的实现这个功能。
写在某个文件里面,编写一个顶级的扩展函数,如下:
inline
然后在需要调用的地方,你直接这样写就行了:
mTopBar?
当然 kotlin 扩展函数的作用并不仅仅在此,它的思想主要是丰富实际类的语法。
例如,现在让你设计一个功能,点击一个按钮,然后发起一个网络请求,但是需要注意在 View 的生命周期内,如果View 销毁了,取消这个网络请求,并且不会更新UI。那么常规的 java 代码,写起来可能就是一堆的回调,但是使用 kotlin 的 扩展函数+代理,封装之后,你看到的就是下面一行代码。
//这里封装了 onClick() 方法,详细的细节,后面会介绍
mJumpBitmap.onClickAutoDisposable {
//这里是你的网络请求操作
}
代理在设计模式上是非常优秀的,例如 retrofit 框架里,大量的使用动态代理这种设计模式;然后 kotlin 在语法层面去支持了代理,包括代理和代理属性。
代理属性的话,kotlin 支持一些标准的代理属性,例如 by lazy,Observable,Storing;其中 by lazy,提供三种类型的懒加载,包括非线程安全,synchronized 同步线程安全,以及cas 操作线程安全。
示例如下,例如在 PushProto.java 中,需要对 ProtoBuf 解析单例进行初始化,并且要求是线程安全的,原先 java 代码如下,也就是一个 double check 的单例模式。
//double check 的单例模式,这里的 volatile 是为了防止指令重排
如果是用 kotin 则只需要使用 by lazy 懒加载委托属性便能实现类似的效果,kotlin 代码如下:
//by lazy 支持泛型,后面的泛型是 initProtoInstance 的类型
注意:实际上这里使用的并非是是 double check 的模式,而是类似的一种线程安全模式,源码如下:
//by lazy 默认是 SynchronizedLazyImpl 模式
by lazy 的三种模式如下:
LazyThreadSafetyMode.SYNCHRONIZED 类似 double check 线程安全,synchronized 悲观锁
LazyThreadSafetyMode.PUBLICATION 线程安全,使用 cas 锁,多个线程可以同时执行初始化代码块,但是只返回第一个执行完成的数值,作为初始化
LazyThreadSafetyMode.NONE 单线程先可用,等于 java 懒汉式单例
其后,其它两种标准属性,参考官方教程。
懒加载是经常使用的一个功能,传统的 java 懒加载,可能是在使用的是判断一下对象是否为空,但是在 kotlin 里,提供了语法层次的懒加载,表示该变量在使用之前一定会被初始化。
by-lazy 上面说到了,lateinit 简单用法如下:
var mStirng:
在 java 中,泛型和反射都是使用频率极高的语法,使用泛型或者接口编程之后,我们经常要使用 instance of 做判断,如下代码:
if (instance
在kotlin 里面,只需要写如下代码:
//Any 类型,等于 java Object 类型
在泛型和接口编程使用广泛的情况下,kotlin 的类型安全存在以下优势:
is 操作符,支持非操作,比 java 更简洁,在语法层次表面的 非该类型;其次 is 类型判断符,作用域类,类型会智能转换,也就是例子中说的。需要注意是,val 和 var 类型,支持的 is 操作不一样。
as 操作符,支持空类型转换,也就是 as?,可以转换为 String? 等可空类型,类似 java 操作是 先要判断该对象是否为该类型,然后强转,语法比较绕,然而 kotlin 直接支持可为空类型的转换
在 kotlin 里面函数是第一公民,java 8 也把加入了该特性,但是远远没有 kotlin 的函数功能强大,kotlin 的函数功能如下:
参数值和变量可以是函数
函数参数支持默认参数
支持命名参数
支持 lambda
支持内联函数
支持强大的扩展函数
编译器支持内联优化
函数可以有一个接收者
函数和 lambdas 表达式对实际开发带来极大的方便,也丰富了编程思想,更多的细节,可以参考官方教程。
在 kotlin stdlib 里面,支持了强大的集合功能,支持各种高阶函数,主要的高阶函数如下:
分组 sort
映射 map
排序 sort
等等
具体可以参考一个专栏 kotlin 学习之路
https://zhuanlan.zhihu.com/LearningKotlin
或者可以直接在 AS 里面搜索类 _Collection.kt,里面可以看到该类的所有的高阶函数.
下面是一个简单的示例,比如你在 java 中需要对一个后端返回的 List 进行排序,java 中常见的实现方案有
1. Collections 中static > void sort(List list)传入该 List,但是 List 中的元素需要实现 Comarable 接口,并且重写 compareTo()方法
2. Collection中,static void sort(List list, Comparator super T> c) 入该 List 和 一个Comparator。
3. java 中,支持 lambda 表达式之后,你可以这样写
ballList
表示使用 Ball 的 hits 字段进行比较
4. java 8 中可以使用 stream
如果使用 kotlin,这里支持各种各样的sort() 方式,如下 api:
那么上面的例子中,kotlin 中这个排序只要使用 Collections 里面的扩展函数就可以做了。
//使用 hits 字段,做降序处理,Collections.sort() 中默认是升序的
所以其实写到这里,我们已经见识到了,kotlin 的开发者帮助我们考虑了各种语法场景,我们站立在一个伟大的开发团队上,自然而然能更快的写出更高质量的代码。
Kotlin的标准库提供了一系列提高开发效率的函数,例如 apply{} ,run{},let{} 等,这里的解释都比较简单,具体自己看一下 api 就行了,位于 Standard.kt 文件中,使用例子如下:
new EnterRoomConfig.RoomlistEventBean();
你可能不断的为访问 java bean 或者某个对象的属性或者方法,则需要不停的写 对象实例.属性 或者 对象实例.方法。在 kotlin 中,你完全可以释放自己的双手,如下:
"").apply {
apply() 的源码如下:
public
这里包含,contract 用法和 带接受者的函数,block() 函数的接收者是T,kotlin 这里其实是提供了一种方便的建造者模式,在 java 里面,你实现建造者模式,需要自己手动去编写接口和 Builder 实现,在 kotlin 则使用这些高阶函数,方便你随意的实现 Builder 模式。
你只需要使用 Object 就可以编写单例了,如下:
object CloUtils {
那么kotlin 上的单例和 java 的各种单例模式,性能上有什么区别吗?kotlin 使用 object 关键字声明的单例,翻译成 java 如下:
public
也就是 java 常见的类似 饿汉式的单例,这种单例是线程安全的,可能存在一定问题:
1. 在类加载的时候,单例会被初始化,所以不要在初始化代码块,做太多操作
2. 不能直接在构造函数传递参数,其实是 object 不能够有一个 constractor
那么实际上我们可以使用其它方式实现我们需要的效果,例如 懒汉式和 double check 等模式,类似 by lazy。
实际上,kotlin 是一门全栈的语言,可以写基于 jvm 的,例如 android 和 后端,也可以写前端,但是在 android 上的表现,可能是最亮眼的,如是说:
在某个 module 的 gradle 文件中:
apply plugin:
然后你就可以使用 android extension 全家桶了,可以帮你做啥呢?
View Binding 取代繁琐的 findViewById()方法
例如某段 java 代码:
//数据域声明各种View
private YYTextView titleTv, roomNameTv, onlineTv, tagTv;
private YYImageView moreIv, lockIv;
private YYLinearLayout titleLayout;
private ViewGroup onlineCountLayout;
private BubblePopupWindow mPopupWindow;
//然后各种 findViewByID
titleTv = findViewById(R.id.tv_title_when_popup);
titleLayout = findViewById(R.id.layout_title_main);
roomNameTv = findViewById(R.id.tv_room_name);
onlineTv = findViewById(R.id.tv_online_count);
tagTv = findViewById(R.id.tv_tag);
moreIv = findViewById(R.id.iv_more);
lockIv = findViewById(R.id.iv_lock);
onlineTv.setTypeface(FontUtils.getTypeFace(FontUtils.FontType.DINMittelschriftAlternate));
roomNameTv.setOnClickListener(this);
tagTv.setOnClickListener(this);
findViewById(R.id.iv_back).setOnClickListener(this);
moreIv.setOnClickListener(this);
在 kotlin 中,你使用 extension 插件,就可以在这样访问控件:
//如果你使用了 extension 插件,这句话会自动 import 进来
原理很简单,就是 extension 插件帮你 findViewById 了类似其它注入框架。
这里有几点注意的,你可以在 Activity 或者 Fragment 或者自定义View 或者 ViewHolder 里面使用这个特性,无需任何其它操作。其次 这个是有Cache 的,每次直接访问id,并不会每次都去 findViewById,只要第一次才会 findViewByID,然后后面会存起来。
使用注解实现 Pracelable
在实际开发中,你可以给一个类实现系列化,但是中途可能增加字段,传统的 java 编写中,或者使用 ide 插件编写,增加字段也是非常辛苦,需要在 writeToParcel() 和 createFromParcel() 方法添加代码,如果使用 extension 插件,只需要开启这个设置:
'com.android.application'
然后在实现 Parcelable 的类上增加一个注解 @Parcelize,代码如下:
@Parcelize
实际上,gradle 插件会为你自动加上 Parcelable 的实现。
还有其它 experimental 的特性等待你去挖掘
参考文档:https://kotlinlang.org/docs/tutorials/android-plugin.html
3kotlin Coroutine -kotlin 协程库Coroutine 协程,是kotlin 上的一个轻量级的线程库,对比 java 的 Executor,主要有以下特点:
更轻量级的 api 实现协程
async 和 await 不作为标准库的一部分
suspend 函数,也就是挂起函数是比 java future 和 promise 更安全并且更容易使用
那么实际本质上和线程池有什么区别呢?我的理解是这样的,协程是在用户态对线程进行管理的,不同于线程池,协程进一步管理了不同协程切换的上下文,协程间的通信,协程挂起,对于线程挂起,粒度更小,而且一般不会直接占用到CPU 资源,所以在编程发展的过程中,广义上可以认为 多进程->多线程->协程。
协程并不会映射成内核线程或者其他这么重的资源,它的调度在用户态就可以搞定,任务之间的调度并非抢占式,而是协作式的。
协程库的强大,不言而喻,这里是我之前写过的关于协程库的分享,文档链接:
https://www.jianshu.com/p/c531eefef4c6
4Kotlin -anko 在代码中使用 dsl 编写布局anko 库,你可以在在代码中编写布局,如下:
//这里是一个垂直布局的LinearLayout
同样的 anko 这边有个插件,可以支持实时预览-你需要在编写代码之后,build 一下,然后就可以预览布局。
由于anko 存在预览问题,并且层级嵌套的时候,不好优化,所以暂时不建议使用 anko 去编写布局.
5如何理解 kotlin 语法糖对于 java 的程序员,kotlin 开发者提供了一种方式,帮助开发者去理解 kotlin,也就是可以把 kotlin 编译出来的字节码,反翻译成 java 代码,具体操作 tools-show kotlin bytecod-然后在右侧会看到 kotlin 文件编译之后的字节码文件-然后点击Decompile ,接着就可以看到 kotlin "翻译出"java 的文件,之前讲述 编写单例模块里面,就是一个例子。
一段时间之后,大家写 kotlin 都写得很爽了,那么总是有一个旧的代码是用 java 写的,那怎么办?如果你想把 java 改成 kotlin,也很简单:
选中你的 java 文件,点击 AS 导航栏 code-covert Java File To Kotlin File,这样就可以转过去了,然后进行简单的修改,也就是把一些报错和 waring 去掉(一般不会有报错)。转换之后的文件可以完美的运行的,这个你不用担心。
6Kotlin 的坑当然 在使用 kotlin 过程中,会发现一些问题或者成本。
对个人而言:
你需要花费一天时间学习 kotlin 语法
你需要花费一两周的时间学习 kotlin 对应的 api
使用高阶函数或者 Coroutine 库,等于新使用的一个框架的成本
第一周使用 kotlin 在你的工作中,你会觉得效率下降,而且有点不知所措,甚至有点抵抗 kotlin,但是在两三个星期之后,你一定是这样的:
对于团队而言
包大小变大了
构建时间会增加一点点
针对问题二,kotlin 的开发者正在优化,同时我们可以在构建的时候,监听构建task 花费的时间,让数据去评测,是否值得引入 kotlin。
选取了某个项目中的 module ,该 module 的基本为 kotlin 代码编写,使用 kotlin 构建时间增加了:
:app:kaptGener…
如果是 java 代码的话,上述的时间可能是一半(猜测是这样的),但是从实际数据看,该 module 有几百个 kotlin 文件,编译所花费的时间,也不过是增加了十几秒,所以并不是特别大的问题。
7java 和 kotlin 互调习惯写 java 的同学,一般是不会写这样的声明的,去声明一个参数是可为空的:
fun printSome(content:String?){
一般你是这样声明的:
fun printSome1(content:String){
在 kotlin 里面,是没问题的,即便是你传入 null,编译器也会提示你,不可以传入null,但是一旦在 java 去调用这个 printSome1() 方法,一旦你传入了 null,如下:
//java 调用 kotlin
依旧是ok的,但是一旦运行的时候,就会报错:
2019-05-05 11:09:05.705 11674-11674/com.yy.yylite.kotlinshare E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.yy.yylite.kotlinshare, PID: 11674
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.yy.yylite.kotlinshare/com.yy.yylite.kotlinshare.bitmapcompress.BitmapAcitivty}: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter content
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2740)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2801)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1548)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:163)
at android.app.ActivityThread.main(ActivityThread.java:6368)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:791)
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter content
at com.yy.yylite.kotlinshare.collectionutils.CloUtils.printSome1(CloUtils.kt)
at com.yy.yylite.kotlinshare.flow.ff.testJavaCallKotlin(ff.java:31)
at com.yy.yylite.kotlinshare.bitmapcompress.BitmapAcitivty.onCreate(BitmapAcitivty.kt:29)
at android.app.Activity.performCreate(Activity.java:6861)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2693)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2801)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1548)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:163)
at android.app.ActivityThread.main(ActivityThread.java:6368)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:791)
具体的原因在于,编译器层会帮我们加这样一行代码:
Intrinsics.checkParameterIsNotNull(content,
如果传入的参数为 null,则会直接抛出一个异常。
同样的,在 kotlin 里面,如果执行这样的代码:
var nonNullStr:
在之前某次迭代中,A 同学写了这样一行代码:
.....
val gson = Gson()
fastSpeech!!.words = gson.fromJson(words, object : TypeTokenString>>() {
}.type)
hotWordPopupWindow?.setData(fastSpeech!!.words, fastSpeech!!.classId)
....
作为一个老道的程序员都知道,使用 GSON 的方法,没有 try catch 是不安全的,何况 fromJson 方法签名就有 throw 异常的,如下:
public
然而你使用 java 的时候,编译器会提示你需要 catch 异常,但是在 kotlin 里面,确实不会有的。
原因是因为 kotlin 开发者,认为所有的 Exception 都是 unchecked 的,编译器不会提示你 catch 住,但是在过去的 java 开发里面,effect java 认为,处理 checked Exception 是优雅合理的;
原文如下:
Checked Exceptions
In Kotlin, all exceptions are unchecked, meaning that the compiler does not force you to catch any of them. So, when you call a Java method that declares a checked exception, Kotlin does not force you to do anything。
在过去的实践里发现,在处理 json,网络,IO 的时候,需要开发者关注,是否需要 try catch,当然了,这可能也是一个好处,毕竟写代码的人关心的细节更多了。
关于 java 和 kotlin 互调参考
https://kotlinlang.org/docs/reference/java-interop.html
8如何推动kotlin普及需要团队成员去学习官网教程 或者 google 提供的快速学习的教程,当然这里只是快速浏览就行了,主要还是得靠实践,先熟悉基本的语法,然后找时间对语法糖进行了解,最后在实践开发中使用高阶特性。
官网教程-中文
https://www.kotlincn.net/docs/reference/
官网教程-英文
https://kotlinlang.org/docs/reference/basic-types.html
快速入门教程
几小时上手 kotlin
然后可以在一段时间内需要通过 review 代码,提高大家对 kotlin 的认识和用法,大概持续一个开发周期,之后基本可以很顺畅的写代码了。
最后就是规则~怎么让 java 和 kotlin 共存
新文件,尽量用 kotlin 写
旧的 java 文件,可以先用 java 写,有时间再把 java 转成 kotlin
最后就是 IDE 对 kotlin 的友好支持,就是你在 commit 的时候,会对代码进行一个 增量的 code analyse,然后虎会给出你一个更好的建议,其中就包括 kotlin 语法的纠正。如下展示:
然后点击 commit 之后,点击review
最后 IDE 会给出更 合理的代码提示,一般可以改的尽量改一下,如下,这里提示这一行命名是多余的:
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
J 神的ButterKnife竟然还隐藏着这样的黑科技?
Android Q 要来了,一个影响国内 90% App 的适配项! ConstraintLayout 2.0新特性 MotionLayout制作炫酷动画扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!