Kotlin 怎么学 ?遇到过哪些坑?

Posted 鸿洋

tags:

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

本文作者


链接:

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 普及里的建议,可以在实际开发中,开干了。


避免 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


java 语法完全兼容 kotlin


你可以通过几个小时学习 kotlin 的基础语法,包括:


  1. 怎么定义变量

  2. 怎么定义方法

  3. 怎么定义类

  4. 等等......


然后就可以像 java 一样使用 kotlin,当然这不是我们的最终目的,因为 kotlin 的语法糖才是我们最后的目标。


google 发布了 kotlin 简易教程,大概只需要几个小时,就可以看完:



在实际的开发中,大概第一个星期内,你写代码的速度会下降一些,但是一个星期之后,完全上手了,写代码的速度是有很大的提升的。


2
kotlin 高级语法(语法糖示例)


1.extension-扩展函数和扩展属性


关于 扩展函数的说明:

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() != null){
    ((ViewGroup)mLoadingView.getParent()).removeView(mLoadingView);
}


但是每次都这样写,貌似比较重复,对吧;然后你就可能考虑写一个静态方法,类似如下,比如在 ViewUtils 类似命名里写一个静态函数:


public static void removeSelf(View view){
    if(view == nullreturn;
    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 的三种模式如下:


  1. LazyThreadSafetyMode.SYNCHRONIZED 类似 double check 线程安全,synchronized 悲观锁

  2. LazyThreadSafetyMode.PUBLICATION 线程安全,使用 cas 锁,多个线程可以同时执行初始化代码块,但是只返回第一个执行完成的数值,作为初始化

  3. 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 学习之路

https://zhuanlan.zhihu.com/LearningKotlin


或者可以直接在 AS 里面搜索类 _Collection.kt,里面可以看到该类的所有的高阶函数.


下面是一个简单的示例,比如你在 java 中需要对一个后端返回的 List 进行排序,java 中常见的实现方案有


1. Collections 中static <T extends Comparable<? super T>> void sort(List<T> list)传入该 List,但是 List 中的元素需要实现 Comarable 接口,并且重写 compareTo()方法


2. Collection中,static <T> void sort(List<T> list, Comparator<? super T> c) 入该 List 和 一个Comparator。


3. java 中,支持 lambda 表达式之后,你可以这样写


ballList.sort(Comparator.comparing(Ball::getHits));


表示使用 Ball 的 hits 字段进行比较


4. java 8 中可以使用 stream


如果使用 kotlin,这里支持各种各样的sort() 方式,如下 api:



那么上面的例子中,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 常见的类似 饿汉式的单例,这种单例是线程安全的,可能存在一定问题:


1. 在类加载的时候,单例会被初始化,所以不要在初始化代码块,做太多操作


2. 不能直接在构造函数传递参数,其实是 object 不能够有一个 constractor

    Kotlin 怎么学 ?遇到过哪些坑?


那么实际上我们可以使用其它方式实现我们需要的效果,例如 懒汉式和 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


3
kotlin Coroutine -kotlin 协程库


Coroutine 协程,是kotlin 上的一个轻量级的线程库,对比 java 的 Executor,主要有以下特点:


  1. 更轻量级的 api 实现协程

  2. async 和 await 不作为标准库的一部分

  3. suspend 函数,也就是挂起函数是比 java future 和 promise 更安全并且更容易使用


那么实际本质上和线程池有什么区别呢?我的理解是这样的,协程是在用户态对线程进行管理的,不同于线程池,协程进一步管理了不同协程切换的上下文,协程间的通信,协程挂起,对于线程挂起,粒度更小,而且一般不会直接占用到CPU 资源,所以在编程发展的过程中,广义上可以认为 多进程->多线程->协程。


协程并不会映射成内核线程或者其他这么重的资源,它的调度在用户态就可以搞定,任务之间的调度并非抢占式,而是协作式的。


协程库的强大,不言而喻,这里是我之前写过的关于协程库的分享,文档链接:

https://www.jianshu.com/p/c531eefef4c6


4
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 去编写布局.


5
如何理解 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 去掉(一般不会有报错)。转换之后的文件可以完美的运行的,这个你不用担心。


Kotlin 怎么学 ?遇到过哪些坑?


6
Kotlin 的坑


当然 在使用 kotlin 过程中,会发现一些问题或者成本。


成本问题


对个人而言:


  • 你需要花费一天时间学习 kotlin 语法

  • 你需要花费一两周的时间学习 kotlin 对应的 api

  • 使用高阶函数或者 Coroutine 库,等于新使用的一个框架的成本


第一周使用 kotlin 在你的工作中,你会觉得效率下降,而且有点不知所措,甚至有点抵抗 kotlin,但是在两三个星期之后,你一定是这样的:


Kotlin 怎么学 ?遇到过哪些坑?


对于团队而言


  • 包大小变大了

  • 构建时间会增加一点点


针对问题二,kotlin 的开发者正在优化,同时我们可以在构建的时候,监听构建task 花费的时间,让数据去评测,是否值得引入 kotlin。


选取了某个项目中的 module ,该 module 的基本为 kotlin 代码编写,使用 kotlin 构建时间增加了:


        1% :app:kaptGenerReleaseKotlin (0:07.775)
        1% :app:kaptReleaseKotlin (0:05.872)
    ▇  2% :app:compileReleaseKotlin (0:21.167)


如果是 java 代码的话,上述的时间可能是一半(猜测是这样的),但是从实际数据看,该 module 有几百个 kotlin 文件,编译所花费的时间,也不过是增加了十几秒,所以并不是特别大的问题。


7
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> 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


8
如何推动kotlin普及


需要团队成员去学习官网教程 或者 google 提供的快速学习的教程,当然这里只是快速浏览就行了,主要还是得靠实践,先熟悉基本的语法,然后找时间对语法糖进行了解,最后在实践开发中使用高阶特性。


官网教程-中文

https://www.kotlincn.net/docs/reference/


官网教程-英文

https://kotlinlang.org/docs/reference/basic-types.html


快速入门教程


然后可以在一段时间内需要通过 review 代码,提高大家对 kotlin 的认识和用法,大概持续一个开发周期,之后基本可以很顺畅的写代码了。


最后就是规则~怎么让 java 和 kotlin  共存


  • 新文件,尽量用 kotlin 写

  • 旧的 java 文件,可以先用 java 写,有时间再把 java 转成 kotlin


最后就是 IDE 对 kotlin 的友好支持,就是你在 commit 的时候,会对代码进行一个 增量的 code analyse,然后虎会给出你一个更好的建议,其中就包括 kotlin 语法的纠正。如下展示:


Kotlin 怎么学 ?遇到过哪些坑?


然后点击 commit 之后,点击review


Kotlin 怎么学 ?遇到过哪些坑?


最后 IDE 会给出更 合理的代码提示,一般可以改的尽量改一下,如下,这里提示这一行命名是多余的:






推荐阅读






如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

以上是关于Kotlin 怎么学 ?遇到过哪些坑?的主要内容,如果未能解决你的问题,请参考以下文章

那些年写Kotlin遇到的各种坑,您需要收藏啦

从viewPager片段(Kotlin)中获取用户输入信息

在面试中遇到过的哪些坑?看看有你踩过的吗?

[react] 在使用react过程中你都踩过哪些坑?你是怎么填坑的?

Kotlin 兼容 Java 遇到的最大的“坑”

如何在 kotlin 中使用片段