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 普及里的建议,可以在实际开发中,开干了。
避免 npl以及非空判断更优雅
kotlin 默认是非空的,如果你需要声明一个可能为空的变量,那么如下:
private var mName:String = "kk"
private var mSubName:String? = null
这里的 mName 变量声明为,String 类型;mSubName 声明为 String?。前者不能为 null,后者可以为 null。
然后,你可能经常在 java 中写这样的代码:
if (mANRPA != null) {
mANRPA.stop();
}
那么在 kotlin 你这要这样写就行了:
mANRPA?.stop()
使用 ?. 的效果和上面的 java 代码是一样的,表示该变量可能为空,如果不为空则执行后面的表达式。
参考地址:https://www.kotlincn.net/docs/reference/null-safety.html
开发效率和生态环境
-
Kotlin 有助于提升团队开发效率。通过比较 Kotlin 文件和 Java 文件内的平均代码行数,您会发现 Kotlin 可以缩减至少 25% 的代码行数。写的代码少了,测试和维护的的时间成本就会相应降低,团队开发新特性的速度自然也就上去了。此外,您还可以观察一下如果用 Kotlin 和 Java 开发类似的特性,哪种语言的用时会更短。
-
Kotlin 有助于改善应用质量。除了大家熟知的空安全 (null-safety) 特性以外,Kotlin 还提供了许多其它的安全特性,帮助您避免各种类型的代码错误。Pinterest 建议开发者在应用程序迁移的过程中,跟踪记录每个模块的代码错误率。您可以发现错误率呈下降趋势。请点击观看 Android 开发者故事对 Camera360 的专访,或者阅读近期学术研究,进一步了解 Kotlin 如何提高代码质量。
-
Kotlin 是行业趋势所在。在 Google Play Top 1000 榜单中,使用 Kotlin 开发的 Android 应用占比达到 26%,其中包括 Twitter、Pinterest、微信、美国运通卡等超重量级应用。Redmonk 公布的榜单显示 Kotlin 是目前发展第二快的的移动平台编程语言。科技媒体 Dice 也曾发文指出 Kotlin 的相关工作数量呈爆炸式增长。
-
Kotlin 让您的团队更开心。虽然幸福感很难量化,但是您可以通过适当的方式让管理层体会到团队对 Kotlin 的热爱。根据《 StackOverflow 2018 年度开发者调查报告》显示,Kotlin 是目前第二大最受欢迎的编程语言
以上总结,摘抄自官方说明。
根据过去的一年多的实践和推广,kotlin 的 0 吐槽的。
java 语法完全兼容 kotlin
你可以通过几个小时学习 kotlin 的基础语法,包括:
- 怎么定义变量
- 怎么定义方法
- 怎么定义类
- 等等......
然后就可以像 java 一样使用 kotlin,当然这不是我们的最终目的,因为 kotlin 的语法糖才是我们最后的目标。
google 发布了 kotlin 简易教程,大概只需要几个小时,就可以看完:
在实际的开发中,大概第一个星期内,你写代码的速度会下降一些,但是一个星期之后,完全上手了,写代码的速度是有很大的提升的。
kotlin 高级语法(语法糖示例)
1.extension-扩展函数和扩展属性
android ktx 库提供了一系列的优秀的扩展函数 官方说明
简单的说,扩展函数是什么呢?先来看一个例子,在java 里面 你要 remove 一个 View 的 Parent,你可能会这样写:
if(mLoadingView.getParent() != null){
((ViewGroup)mLoadingView.getParent()).removeView(mLoadingView);
}
但是每次都这样写,貌似比较重复,对吧;然后你就可能考虑写一个静态方法,类似如下,比如在 ViewUtils 类似命名里写一个静态函数:
public static void removeSelf(View view){
if(view == null) return;
if(view.getParent() != null){
((ViewGroup)view.getParent()).removeView(view);
}
}
但是在 kotlin 中,使用扩展函数,可以更巧妙更直接的实现这个功能。
写在某个文件里面,编写一个顶级的扩展函数,如下:
inline fun View.removeSelf(): Boolean {
if (this.parent != null && this.parent is ViewGroup) {
(this.parent as ViewGroup).removeView(this)
return true
}
return false
}
然后在需要调用的地方,你直接这样写就行了:
mTopBar?.removeSelf()
当然 kotlin 扩展函数的作用并不仅仅在此,它的思想主要是丰富实际类的语法。
例如,现在让你设计一个功能,点击一个按钮,然后发起一个网络请求,但是需要注意在 View 的生命周期内,如果View 销毁了,取消这个网络请求,并且不会更新UI。那么常规的 java 代码,写起来可能就是一堆的回调,但是使用 kotlin 的 扩展函数+代理,封装之后,你看到的就是下面一行代码。
//这里封装了 onClick() 方法,详细的细节,后面会介绍
mJumpBitmap.onClickAutoDisposable {
//这里是你的网络请求操作
}
2.Delegate And Delagate Properties -委托以及委托属性
代理在设计模式上是非常优秀的,例如 retrofit 框架里,大量的使用动态代理这种设计模式;然后 kotlin 在语法层面去支持了代理,包括代理和代理属性。
代理属性的话,kotlin 支持一些标准的代理属性,例如 by lazy,Observable,Storing;其中 by lazy,提供三种类型的懒加载,包括非线程安全,synchronized 同步线程安全,以及cas 操作线程安全。
示例如下,例如在 PushProto.java 中,需要对 ProtoBuf 解析单例进行初始化,并且要求是线程安全的,原先 java 代码如下,也就是一个 double check 的单例模式。
//double check 的单例模式,这里的 volatile 是为了防止指令重排
private static volatile com.google.protobuf.Parser<PushProto> PARSER;
//这里省略一些代码
if (PARSER == null) { synchronized (com.yy.hiyo.proto.PushProto.class) {
if (PARSER == null) {
PARSER = new DefaultInstanceBasedParser(DEFAULT_INSTANCE);
}
}
如果是用 kotin 则只需要使用 by lazy 懒加载委托属性便能实现类似的效果,kotlin 代码如下:
//by lazy 支持泛型,后面的泛型是 initProtoInstance 的类型
val initProtoInstance by lazy<DefaultInstanceBasedParser> {
//这里其实就是返回值
DefaultInstanceBasedParser();
}
注意:实际上这里使用的并非是是 double check 的模式,而是类似的一种线程安全模式,源码如下:
//by lazy 默认是 SynchronizedLazyImpl 模式
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
@kotlin.jvm.JvmVersion
public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
}
else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
}
by lazy 的三种模式如下:
- LazyThreadSafetyMode.SYNCHRONIZED 类似 double check 线程安全,synchronized 悲观锁
- LazyThreadSafetyMode.PUBLICATION 线程安全,使用 cas 锁,多个线程可以同时执行初始化代码块,但是只返回第一个执行完成的数值,作为初始化
- LazyThreadSafetyMode.NONE 单线程先可用,等于 java 懒汉式单例
其后,其它两种标准属性,参考官方教程。
3.语法层面支持懒加载-by lazy 和 lateinit
懒加载是经常使用的一个功能,传统的 java 懒加载,可能是在使用的是判断一下对象是否为空,但是在 kotlin 里,提供了语法层次的懒加载,表示该变量在使用之前一定会被初始化。
by-lazy 上面说到了,lateinit 简单用法如下:
lateinit var mStirng: String
//初始化的时候,直接赋值
mString = "some"
//使用的时候,可以先判断是否初始化
if(TestActivity::mStirng.isLateinit){
//这里表示初始化过了
}
4.比 java 更强大的类型判断
在 java 中,泛型和反射都是使用频率极高的语法,使用泛型或者接口编程之后,我们经常要使用 instance of 做判断,如下代码:
if (instance instanceof BasePresenter) {
((BasePresenter) instance).onInit(mMvpContext); // 2. init
}
在kotlin 里面,只需要写如下代码:
//Any 类型,等于 java Object 类型
fun checkSting(content:Any){
//kotlin 用 is 判断是否一种类型,等于 instance
if(content is String){
//这里之后 content 会智能 转换为 String,并不需要 java 那种强转,编译器会自动识别
content.substring(1)
}
}
在泛型和接口编程使用广泛的情况下,kotlin 的类型安全存在以下优势:
- is 操作符,支持非操作,比 java 更简洁,在语法层次表面的 非该类型;其次 is 类型判断符,作用域类,类型会智能转换,也就是例子中说的。需要注意是,val 和 var 类型,支持的 is 操作不一样。
- as 操作符,支持空类型转换,也就是 as?,可以转换为 String? 等可空类型,类似 java 操作是 先要判断该对象是否为该类型,然后强转,语法比较绕,然而 kotlin 直接支持可为空类型的转换
5.functions and lambdas - 函数和 lambdas 表达式
在 kotlin 里面函数是第一公民,java 8 也把加入了该特性,但是远远没有 kotlin 的函数功能强大,kotlin 的函数功能如下:
- 参数值和变量可以是函数
- 函数参数支持默认参数
- 支持命名参数
- 支持 lambda
- 支持内联函数
- 支持强大的扩展函数
- 编译器支持内联优化
- 函数可以有一个接收者
函数和 lambdas 表达式对实际开发带来极大的方便,也丰富了编程思想,更多的细节,可以参考官方教程。
6.强大的集合功能
在 kotlin stdlib 里面,支持了强大的集合功能,支持各种高阶函数,主要的高阶函数如下:
- 分组 sort
- 映射 map
- 排序 sort
- 等等
具体可以参考一个专栏 kotlin 学习之路
或者可以直接在 AS 里面搜索类 _Collection.kt,里面可以看到该类的所有的高阶函数
下面是一个简单的示例,比如你在 java 中需要对一个后端返回的 List 进行排序,java 中常见的实现方案有
-
Collections 中static <T extends Comparable<? super T>> void sort(List<T> list)传入该 List,但是 List 中的元素需要实现 Comarable 接口,并且重写 compareTo()方法
-
Collection中,static <T> void sort(List<T> list, Comparator<? super T> c) 入该 List 和 一个Comparator。
-
java 中,支持 lambda 表达式之后,你可以这样写
ballList.sort(Comparator.comparing(Ball::getHits));
表示使用 Ball 的 hits 字段进行比较
-
java 8 中可以使用 stream
如果使用 kotlin,这里支持各种各样的sort() 方式,如下 api:
![](https://img.haomeiwen.com/i633456/c1f62379e2dd4f41.jpg)
那么上面的例子中,kotlin 中这个排序只要使用 Collections 里面的扩展函数就可以做了。
//使用 hits 字段,做降序处理,Collections.sort() 中默认是升序的
ballList.sortByDescending {it.hits}
所以其实写到这里,我们已经见识到了,kotlin 的开发者帮助我们考虑了各种语法场景,我们站立在一个伟大的开发团队上,自然而然能更快的写出更高质量的代码。
7.Standard Lib标准库
Kotlin的标准库提供了一系列提高开发效率的函数,例如 apply{} ,run{},let{} 等,这里的解释都比较简单,具体自己看一下 api 就行了,位于 Standard.kt 文件中,使用例子如下:
EnterRoomConfig.RoomlistEventBean eventBean = new EnterRoomConfig.RoomlistEventBean();
eventBean.setColumn(String.valueOf(position + 1));
eventBean.setRow(baseRoomEventListBean.getRow());
eventBean.setPositionId(String.valueOf(position + 1));
eventBean.setToken(token);
eventBean.setModelId(baseRoomEventListBean.getModelId());
你可能不断的为访问 java bean 或者某个对象的属性或者方法,则需要不停的写 对象实例.属性 或者 对象实例.方法。在 kotlin 中,你完全可以释放自己的双手,如下:
val enterRoomConfig = EnterRoomConfig("").apply {
//这里有个 this 指向前面new 出来的 EnterRoomConfig() 实例
//其实等于 this.isGuideEnter
isGuideEnter = false
isQuickMatch = true
followUid = followUid
pwd_token = ""
}
apply() 的源码如下:
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
这里包含,contract 用法和 带接受者的函数,block() 函数的接收者是T,kotlin 这里其实是提供了一种方便的建造者模式,在 java 里面,你实现建造者模式,需要自己手动去编写接口和 Builder 实现,在 kotlin 则使用这些高阶函数,方便你随意的实现 Builder 模式。
8.编写单例更快了
你只需要使用 Object 就可以编写单例了,如下:
object CloUtils {
fun test(){
}
}
那么kotlin 上的单例和 java 的各种单例模式,性能上有什么区别吗?kotlin 使用 object 关键字声明的单例,翻译成 java 如下:
public final class CloUtils {
public static final CloUtils INSTANCE;
public final void test() {
}
static {
CloUtils var0 = new CloUtils();
INSTANCE = var0;
}
}
也就是 java 常见的类似 饿汉式的单例,这种单例是线程安全的,可能存在一定问题:
-
在类加载的时候,单例会被初始化,所以不要在初始化代码块,做太多操作
-
不能直接在构造函数传递参数,其实是 object 不能够有一个 constractor
image
那么实际上我们可以使用其它方式实现我们需要的效果,例如 懒汉式和 double check 等模式,类似 by lazy。
9.kotlin 和 android-android extension插件
实际上,kotlin 是一门全栈的语言,可以写基于 jvm 的,例如 android 和 后端,也可以写前端,但是在 android 上的表现,可能是最亮眼的,如是说:
在某个 module 的 gradle 文件中:
apply plugin: 'kotlin-android-extensions'
然后你就可以使用 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 进来 import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { val tag = "MainActivity" @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initBut() } private fun initBut(){ //使用的时候,直接用 View 的ID 就行了,xml 代码看下面 mJumpBitmap.setOnClickListener { val intent = Intent(this@MainActivity, BitmapAcitivty::class.java) startActivity(intent) } mJumpFresco.setOnClickListener { val intent = Intent(this@MainActivity, FrescoRecyvlerActivity::class.java) startActivity(intent) } }
原理很简单,就是 extension 插件帮你 findViewById 了类似其它注入框架。
这里有几点注意的,你可以在 Activity 或者 Fragment 或者自定义View 或者 ViewHolder 里面使用这个特性,无需任何其它操作。其次 这个是有Cache 的,每次直接访问id,并不会每次都去 findViewById,只要第一次才会 findViewByID,然后后面会存起来。
-
使用注解实现 Pracelable
在实际开发中,你可以给一个类实现系列化,但是中途可能增加字段,传统的 java 编写中,或者使用 ide 插件编写,增加字段也是非常辛苦,需要在 writeToParcel() 和 createFromParcel() 方法添加代码,如果使用 extension 插件,只需要开启这个设置:
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' //在 build.gradle android { ....... androidExtensions{ experimental = true } }
然后在实现 Parcelable 的类上增加一个注解 @Parcelize,代码如下:
@Parcelize data class Ball(var hits: Int) : Parcelable { }
实际上,gradle 插件会为你自动加上 Parcelable 的实现。
-
还有其它 experimental 的特性等待你去挖掘
参考文档:https://kotlinlang.org/docs/tutorials/android-plugin.html
kotlin Coroutine -kotlin 协程库
Coroutine 协程,是kotlin 上的一个轻量级的线程库,对比 java 的 Executor,主要有以下特点:
- 更轻量级的 api 实现协程
- async 和 await 不作为标准库的一部分
- suspend 函数,也就是挂起函数是比 java future 和 promise 更安全并且更容易使用
那么实际本质上和线程池有什么区别呢?我的理解是这样的,协程是在用户态对线程进行管理的,不同于线程池,协程进一步管理了不同协程切换的上下文,协程间的通信,协程挂起,对于线程挂起,粒度更小,而且一般不会直接占用到CPU 资源,所以在编程发展的过程中,广义上可以认为 多进程->多线程->协程。
协程并不会映射成内核线程或者其他这么重的资源,它的调度在用户态就可以搞定,任务之间的调度并非抢占式,而是协作式的。
协程库的强大,不言而喻,这里是我之前写过的关于协程库的分享,文档链接:
kotlin coroutines 协程教程(一) 介绍及关键API分析
Kotlin -anko 在代码中使用 dsl 编写布局
anko 库,你可以在在代码中编写布局,如下:
//这里是一个垂直布局的LinearLayout
verticalLayout {
backgroundColor = Color.parseColor("#eeeeee")
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)
}
同样的 anko 这边有个插件,可以支持实时预览-你需要在编写代码之后,build 一下,然后就可以预览布局。
由于anko 存在预览问题,并且层级嵌套的时候,不好优化,所以暂时不建议使用 anko 去编写布局
如何理解 kotlin 语法糖
对于 java 的程序员,kotlin 开发者提供了一种方式,帮助开发者去理解 kotlin,也就是可以把 kotlin 编译出来的字节码,反翻译成 java 代码,具体操作 tools-show kotlin bytecod-然后在右侧会看到 kotlin 文件编译之后的字节码文件-然后点击Decompile ,接着就可以看到 kotlin "翻译出"java 的文件,之前讲述 编写单例模块里面,就是一个例子。
旧的 java 代码怎么办
一段时间之后,大家写 kotlin 都写得很爽了,那么总是有一个旧的代码是用 java 写的,那怎么办?如果你想把 java 改成 kotlin,也很简单:
选中你的 java 文件,点击 AS 导航栏 code-covert Java File To Kotlin File,这样就可以转过去了,然后进行简单的修改,也就是把一些报错和 waring 去掉(一般不会有报错)。转换之后的文件可以完美的运行的,这个你不用担心。
![](https://img.haomeiwen.com/i633456/644d434fc3e4f56e.jpg)
Kotlin 的坑
当然 在使用 kotlin 过程中,会发现一些问题或者成本。
成本问题
对个人而言:
- 你需要花费一天时间学习 kotlin 语法
- 你需要花费一两周的时间学习 kotlin 对应的 api
- 使用高阶函数或者 Coroutine 库,等于新使用的一个框架的成本
第一周使用 kotlin 在你的工作中,你会觉得效率下降,而且有点不知所措,甚至有点抵抗 kotlin,但是在两三个星期之后,你一定是这样的:
![](https://img.haomeiwen.com/i633456/0a23d72883c24fc2.jpg)
对于团队而言
- 包大小变大了
- 构建时间会增加一点点
针对问题二,kotlin 的开发者正在优化,同时我们可以在构建的时候,监听构建task 花费的时间,让数据去评测,是否值得引入 kotlin。
选取了某个项目中的 module ,该 module 的基本为 kotlin 代码编写,使用 kotlin 构建时间增加了:
1% :app:kaptGener…ReleaseKotlin (0:07.775)
1% :app:kaptReleaseKotlin (0:05.872)
▇ 2% :app:compileReleaseKotlin (0:21.167)
如果是 java 代码的话,上述的时间可能是一半(猜测是这样的),但是从实际数据看,该 module 有几百个 kotlin 文件,编译所花费的时间,也不过是增加了十几秒,所以并不是特别大的问题。
java 和 kotlin 互调
1.java null 问题
习惯写 java 的同学,一般是不会写这样的声明的,去声明一个参数是可为空的:
fun printSome(content:String?){
}
一般你是这样声明的:
fun printSome1(content:String){
}
在 kotlin 里面,是没问题的,即便是你传入 null,编译器也会提示你,不可以传入null,但是一旦在 java 去调用这个 printSome1() 方法,一旦你传入了 null,如下:
//java 调用 kotlin
CloUtils.INSTANCE.printSome1(null);
依旧是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, "content");
如果传入的参数为 null,则会直接抛出一个异常。
同样的,在 kotlin 里面,如果执行这样的代码:
var nonNullStr:String = null // 不允许的,如果是运行期赋值,则会报错
2.java 方法 Exception 问题
在之前某次迭代中,A 同学写了这样一行代码:
.....
val gson = Gson()
fastSpeech!!.words = gson.fromJson(words, object : TypeToken<List<String>>() {
}.type)
hotWordPopupWindow?.setData(fastSpeech!!.words, fastSpeech!!.classId)
....
作为一个老道的程序员都知道,使用 GSON 的方法,没有 try catch 是不安全的,何况 fromJson 方法签名就有 throw 异常的,如下:
public <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {}
然而你使用 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
如何推动kotlin普及
需要团队成员去学习官网教程 或者 google 提供的快速学习的教程,当然这里只是快速浏览就行了,主要还是得靠实践,先熟悉基本的语法,然后找时间对语法糖进行了解,最后在实践开发中使用高阶特性。
官网教程-中文 https://www.kotlincn.net/docs/reference/
官网教程-英文https://kotlinlang.org/docs/reference/basic-types.html
快速入门教程 几小时上手 kotlin
挺不错的一个教程 EasyKotlin
然后可以在一段时间内需要通过 review 代码,提高大家对 kotlin 的认识和用法,大概持续一个开发周期,之后基本可以很顺畅的写代码了。
最后就是规则~怎么让 java 和 kotlin 共存
- 新文件,尽量用 kotlin 写
- 旧的 java 文件,可以先用 java 写,有时间再把 java 转成 kotlin
最后就是 IDE 对 kotlin 的友好支持,就是你在 commit 的时候,会对代码进行一个 增量的 code analyse,然后虎会给出你一个更好的建议,其中就包括 kotlin 语法的纠正。如下展示:
![](https://img.haomeiwen.com/i633456/8fc0fa16f31e1a78.jpg)
然后点击 commit 之后,点击review
![](https://img.haomeiwen.com/i633456/52c5b4aa1527c5ae.jpg)
最后 IDE 会给出更 合理的代码提示,一般可以改的尽量改一下,如下,这里提示这一行命名是多余的:
![](https://img.haomeiwen.com/i633456/e6a9ec359e31dd63.jpg)
网友评论