美文网首页Kotlin从入门到放弃kotlinAndroid知识
Kotlin —— 这次入门就不用放弃了

Kotlin —— 这次入门就不用放弃了

作者: FeelsChaotic | 来源:发表于2017-06-11 21:28 被阅读10339次

    写在文前

    本文将展示在Android中会遇到的实际问题,并且使用Kotlin怎么去解决它们。一些Android开发者在处理异步、数据库或者处理Activity中非常冗长的listener时发现了很多的问题。通过一个个真实的场景,我们一边解决问题一边学习Kotlin的特性。

    快速上手

    如果不知道如何在Kotlin中写一个相当简单的Java表达式。这里有一个简单的诀窍,就是在AndroidStudio的Java文件中编写一段代码,然后将其粘贴到kt文件中,它会自动转换为Kotlin。

    Kotlin优势

    1. 它更加易表现:这是它最重要的优点之一。你可以编写少得多的代码。

    2. 它更加安全:Kotlin是空安全的,也就是说在我们编译时期就处理了各种null的情况,避免了执行时异常。你可以节约很多调试空指针异常的时间,解决掉null引发的bug。

    3. 它可以扩展函数:这意味着,就算我们没有权限去访问这个类中的代码,我们也可以扩展这个类的更多的特性。

    4. 它是函数式的:Kotlin是基于面向对象的语言。但是就如其他很多现代的语言那样,它使用了很多函数式编程的概念,比如,使用lambda表达式来更方便地解决问题。其中一个很棒的特性就是Collections的处理方式。我稍后会进行介绍。

    5. 它是高度互操作性的:你可以继续使用所有用Java写的代码和库,甚至可以在一个项目中使用Kotlin和Java两种语言混合编程。一行Java一行Kotlin,别提有多风骚了。

    详细实例

    1. 易表现和简洁性

    通过Kotlin,可以更容易地避免模版代码,因为大部分的典型情况都在语言中默认覆盖实现了。

    举个例子,在Java中,如果我们要典型的数据类,我们需要去编写(至少生成)这些代码:

    public class User{
        private long id;
        private String name;
        private String url;
        private String mbid;
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getMbid() {
            return mbid;
        }
    
        public void setMbid(String mbid) {
            this.mbid = mbid;
        }
    
        @Override 
        public String toString() {
            return "User{" +
              "id=" + id +
              ", name='" + name + '\'' +
              ", url='" + url + '\'' +
              ", mbid='" + mbid + '\'' +
              '}';
        }
    }
    

    我们在不使用第三方框架的基础上,需要大量的set get方法和复写基础方法。

    而使用Kotlin,我们只需要通过data关键字:

    data class User(
        var id: Long,
        var name: String,
        var url: String,
        var mbid: String)
    

    这个数据类,它会自动生成所有属性和它们的访问器, 并自动生成相应的 equals、hashcode、toString 方法。

    空口无凭,我们验证一下:

    首先建立一个kt文件,新建一个简单的User类:

    data class User(var name: String)
    

    这时候在命令行使用kotlinc编译,得到一个class文件,反编译成Java文件,可以看到:

    public final class User {
       @NotNull
       private String name;
    
       @NotNull
       public final String getName() {
          return this.name;
       }
    
       public final void setName(@NotNull String var1) {
          Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
          this.name = var1;
       }
    
       public User(@NotNull String name) {
          Intrinsics.checkParameterIsNotNull(name, "name");
          super();
          this.name = name;
       }
     
      // 解构声明
       @NotNull
       public final String component1() {
          return this.name;
       }
    
       @NotNull
       public final User copy(@NotNull String name) {
          Intrinsics.checkParameterIsNotNull(name, "name");
          return new User(name);
       }
    
       // $FF: synthetic method
       // $FF: bridge method
       @NotNull
       public static User copy$default(User var0, String var1, int var2, Object var3) {
          if((var2 & 1) != 0) {
             var1 = var0.name;
          }
    
          return var0.copy(var1);
       }
    
       public String toString() {
          return "User(name=" + this.name + ")";
       }
    
       public int hashCode() {
          return this.name != null?this.name.hashCode():0;
       }
    
       public boolean equals(Object var1) {
          if(this != var1) {
             if(var1 instanceof User) {
                User var2 = (User)var1;
                if(Intrinsics.areEqual(this.name, var2.name)) {
                   return true;
                }
             }
    
             return false;
          } else {
             return true;
          }
       }
    }
    

    事实说明在kotlin中 data 修饰符 = java中 private + getter + setter + toString + equals + hashCode

    2. 空安全

    当我们使用Java开发的时候,如果我们不想遇到NullPointerException,我们就需要在每次使用它之前,不停地去判断它是否为null。

    而Kotlin是空安全的,我们通过一个安全调用操作符?来明确地指定一个对象是否能为空。

    我们可以像这样去写:

    // 这里不能通过编译. User对象不能是null
    var notNullUser: User= null
    
    // User可以是 null
    var user: User? = null
    
    // 无法编译, user可能是null,我们需要进行处理
    user.print()
    
    // 只要在user != null时才会打印
    user?.print()
    
    // 使用Elvis操作符来给定一个在是null的情况下的替代值
    val name = user?.name ?: "empty"
    
    /** 
    如果user为可空类型,又一定要调用它的成员函数和变量,可以用!!操作符
    两种可能,要么正确返回name,要么抛出空指针异常
    当user为null,你不想返回null,而是抛出一个空指针异常,你就可以使用它。
    */
    var name = user!!.name
    

    3. 扩展方法

    我们可以给任何类添加函数(View,Context等)。比起Java的继承机制,更加简洁和优雅。举个例子,我们可以给fragment增加一个显示toast的函数:

    fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) { 
        Toast.makeText(getActivity(), message, duration).show()
    }
    

    我们现在可以这么做:

    fragment.toast("Hello world!")
    

    此处duration已经赋了默认值,所以这个参数可传可不传。

    包括扩展属性,可以直接 类名.属性名:类型

    注意:Kotlin 的方法扩展并不是真正修改了对应的类文件,而是在编译器和 IDE 方面做了处理。使我们看起来像是扩展了方法。

    4. 函数式支持

    • Collections迭代

    Kotlin使用lambda表达式来更方便地解决问题。体现最好的就是Collections的处理方式。

    list.map(
      println(it) //it表示迭代的对象
    )
    

    查看源码,我们可以看到实际上map就是一个扩展方法,给所有可以迭代的集合提供该方法,map方法接收的参数是一个lambda表达式,类型为T,返回值为R类型(意味着任意类型),那这里T类型实际上就是list的元素类型。

    map方法源码.png

    甚至于可以

    list.map(::println)
    

    ::表示方法或类的引用。为什么可以直接传方法引用呢?

    我们看看println方法源码,可以看到println接收一个Any类也就是任意类型,而且返回值为空(Kotlin中空类型为Unit类,此处源码省略了返回值类型声明),所以完全符合map方法的要求。

    println方法源码.png

    注:类似于RxJava对数组的处理,Kotlin也提供了flatMap方法,具体可以自己了解。

    • 事件

    在Java中,每次我们去声明一个点击事件,都不得不去实现一个内部类,而在Kotlin中,可以直接声明我们要做什么。

    view.setOnClickListener { toast("Hello world!") }
    //注:此处的toast方法是Kotlin默认已经提供的扩展方法
    

    5. 互操作性

    Kotlin调用Java和Java调用Kotlin与之前的Java 类之间调用方式没有太大差别,不详细介绍。

    就举个Java调用Kotlin的小例子:

    //Kotlin
    class Overloads {
        fun overloaded(a: Int, b: Int = 0, c: Int = 1){
            println("$a, $b, $c")
        }
    }
    
    //Java
    public class AccessToOverloads {
        public static void main(String... args) {
            Overloads overloads = new Overloads();
            overloads.overloaded(1, 2, 3);
        }
    }
    

    可以看到非常简单,这里要多介绍一个Kotlin注解@JvmOverloads。仍然定义了一个overloaded方法,加上注解后,Kotlin会自动重载成n个方法(n表示参数个数)

    //Kotlin
    class Overloads {
        @JvmOverloads
        fun overloaded(a: Int, b: Int = 0, c: Int = 1){
            println("$a, $b, $c")
        }
    }
    
    /**
    在Java可以调用3个overloaded方法,分别是:
    overloaded(a,b,c)
    overloaded(a,b)
    overloaded(a)
    */
    public class AccessToOverloads {
        public static void main(String... args) {
            Overloads overloads = new Overloads();
            overloads.overloaded(1, 2, 3);
            overloads.overloaded(1);
            overloads.overloaded(1,3);
        }
    }
    

    6. 其他

    • 单例

    首先说说单例的实现方式,在之后的实战中,将会经常接触到object这个关键字。

    先看Java,在Java中,实现一个单例,我们需要:

    1. 保留一个单例对象的静态实例

    2. 提供一个类方法让外界访问唯一的实例

    3. 构造方法采用private修饰符

    而在Kotlin中,一个修饰符就解决了。

    object PlainOldSingleton {
    
    }
    

    怎么做到的?我们看看反编译的结果:

    单例

    可以看到写法和Java是完全一样的,又有一个新问题,在类加载的时候就初始化了实例,这种方式很糟糕,我们最好选择懒加载。那么在Kotlin中懒加载的2种实现方式如下:

    class LazyNotThreadSafe {
          //方式一
        companion object{
            val instance by lazy(LazyThreadSafetyMode.NONE) {
                LazyNotThreadSafe()
            }
    
            //方式二,实际是Java的直译
        private var instance2: LazyNotThreadSafe? = null
    
            fun get() : LazyNotThreadSafe {
                if(instance2 == null){
                    instance2 = LazyNotThreadSafe()
                }
                return instance2!!
            }
        }
    }
    

    如果想要实现线程安全,可以加上@Synchronized注解,这和Java中给类加上Synchronized修饰符是一样的。同样@Volatile注解和Java的Volatile修饰符作用也是一样的。

    或者使用静态内部类的单例方法:

    class LazyThreadSafeStaticInnerObject private constructor(){
        companion object{
            fun getInstance() = Holder.instance
        }
    
        private object Holder{
            val instance = LazyThreadSafeStaticInnerObject()
        }
    }
    
    • 委托

    Kotlin中,委托的实现依靠于关键字 by
    by表示将抽象主题的实例(by后边的实例)保存在代理类实例的内部。

    比如下面这个例子中:BaseImpl类继承于Base接口,并可以Base接口的所有的 public 方法委托给一个指定的对象。

    interface Base {
        fun display()
    }
    
    class BaseImpl : Base {
        override fun display() {
            print("baseimpl display")
        }
    }
    
    class ProxyClass(base: Base) : Base by base
    
    //程序入口
    fun main(args: Array<String>) {
        var base = BaseImpl()
        var proxy = ProxyClass(base)
        proxy.display()
    }
    
    • 泛型

    在Java中,一般使用Gson库来解析Json。调用方法的时候,我们需要传入想要转成的类的Class。我们都知道Java的泛型实际上是伪泛型,对泛型支持的底层实现采用的是类型擦除的方式(只有在编译期才有)。

    所以当使用Gson.fromJson(String json , Class<T> classOf)方法时,虽然传入了类型参数,当实际上这个T仍然是个Object。

    而在Kotlin中,可以使用reified,告别Class。

    reified的意思是具体化。作为Kotlin的一个方法泛型关键字,它代表你可以在方法体内访问泛型指定的JVM类对象。

    inline fun <reified T: Any> Gson.fromJson(json: String): T{
    //封装了`Gson.fromJson(String json , Class<T> classOf)`方法
        return fromJson(json, T::class.java)
    }
    

    这里需要指定T类型为Any,即Object类。

    接着可以不需要传入Class,直接调用

    fun main(args: Array<String>) {
        val json = "{state:0,result:'success',name:'test'}"
        var result : ReifiedBean =  Gson().fromJsonNew(json)
        println(result.name+","+result.result)
    }
    

    这要归功于inline,inline 意味着编译的时候真正要编译到调用点。那么哪个方法调用了它,参数的类型都是确定的。也就不需要传入Class了

    ** 7. 摆脱不必要的依赖**

    Kotlin替换了许多第三方库,如ButterKnife、Google Autovalue、Retrolambda、Lombok和一些RxJava代码。

    但是也是可以100%兼容RxJava的,举个读取本地文本逐个字打印的例子。

    Kotlin中使用RxJava

    好了,言归正传。

    普通的获取View方法,需要一个个去findViewById

    普通的获取View方法

    而使用Kotlin后

    使用Kotlin获取View

    可能有人注意到了,还是需要findViewById啊!!骗子!说好的优雅呢?完全没觉得更加简洁啊!!别急,Kotlin常用的获取控件方式不是这样的,容我介绍个Kotlin库——Anko。

    3. Kotlin库——Anko

    简介
    Anko是Kotlin官方开发的一个让开发Android应用更快速更简单的Kotlin库

    1. 再也不用findViewById

    做过Android开发的人都知道,布局文件写的多了,findViewById也是一个很大的工作量,而且还要先声明变量,在findViewById然后再强转成我们的控件,使用方式一般如下

    TextView username;
    username=(TextView)findViewById(R.id.user);
    
    username.setText("我是一个TextView");
    

    有时候写的是不是想吐,可能有些人说现在不是有一些注解的库,如butterknife,当我们使用注解时可以不用findViewById了,使用方式如下

    @BindView(R.id.user)
    TextView username;
    
    username.setText("我是一个TextView");
    

    确实是这样,使用注解后确实给我们少了一些工作量,不过这依然没有最简单化,最简单的就是我们可以直接给id为user的控件直接赋值,或许你会感觉这有点不可思议。不过Kotlin确实做到了。我们可以直接这样写

    user.text="我是一个TextView"
    

    user就是我们布局文件声明的id,.text就相当于setText(),在Kotlin语言中,我们看不到了像Java中的set/get方法了。

    当我们想这样使用的时候(不用findViewById,直接使用xml控件id)
    我们需要在gradle加入apply plugin: ‘kotlin-android-extensions’,需要加入下面一句代码

    import kotlinx.android.synthetic.main.activity_login.*
    注:activity_login就是我们的布局

    import org.jetbrains.anko.toast
    import org.jetbrains.anko.onClick
    
    class Main2Activity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContentView(R.layout.activity_main2)
            my_textView.text = "kotline test"
            my_textView.textColor = Color.BLUE
            my_button.text = "Click"
            my_button.onClick { toast("aa") }
        }
    }  
    

    为什么Anko不需要.setText可以直接.text呢?其实这是通过扩展函数实现的,我们看下内部的实现细节:

    public var TextView.text: CharSequence        
      get() = getText()           
      set(v) = setText(v)
    

    2. Anko Layout

    通常我们使用xml文件写我们的布局,但是存在有一些缺点:如不是类型安全,不是空安全,解析xml文件消耗更多的CPU和电量等等。

    而Anko Layout可以使用DSL动态创建我们的UI,并且它比我们使用Java动态创建布局方便很多。主要是更简洁,它拥有类似xml创建布局的层级关系,能让我们更容易阅读。

     verticalLayout {
                val textView = textView("textview")
                val name = editText()
                val button=button()
                        button.onClick {
                    toast("${name.text}")
                }
            }
    

    我们在OnCreate方法中可以去掉setContentView,然后加入上面代码就可以显示如下图的效果,即一个垂直的线性布局中,放了一个TextView,一个EditText,和一个Button。并且Button中有一个点击事件,当点击时将EditText的内容以toast显示。

    Anko Layout.png

    在上面创建UI过程中,我们直接把创建UI的代码写在onCreate方法中了,当然,还有一种写法。我们创建一个内部类实行AnkoComponent接口,并重写createView方法,该方法返回一个View,也就是我们创建的布局。修改如下

    inner class UI : AnkoComponent<LoginActivity> {
            override fun createView(ui: AnkoContext<LoginActivity>): View {
               return with(ui){
                   verticalLayout {
                       val textView=textView("我是一个TextView"){
                           textSize = sp(17).toFloat()//自定义字体大小
                           textColor=context.resources.getColor(R.color.red)//自定义颜色
                       }.lparams{
                           margin=dip(10)//它表示将10dp转换为像素
                           height= dip(40)
                           width= matchParent
                       }
                       val name = editText("EditText")
                       button("Button") {
                            onClick { view ->
                                toast("Hello, ${name.text}!")
                            }
                       }
                   }
               }
            }
        }
    

    然后在onCreate方法中加一句代码,即可创建我们的布局页面了。如下

    UI().setContentView(this@LoginActivity)
    

    其中,dip(10),表示将10dp转换为像素的意思,是Anko的扩展函数,说到扩展函数,我发现Kotlin源码里大量地使用扩展函数,这也是Kotlin语言的优势之一。确实很强大,例如dip扩展(摘取View扩展)

    inline fun View.dip(value: Int): Int = context.dip(value)
    fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt()
    

    就如我们之前说的toast、text也是拓展函数一样

    inline fun AnkoContext<*>.toast(message: CharSequence) = ctx.toast(message)
    fun Context.toast(message: CharSequence) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    

    但是为了界面和逻辑分离,界面还是建议使用xml,所以这里就不对Anko Layout多做介绍了。

    3. 其他方面

    比如网络请求AsyncTask

     doAsync {
                //后台执行代码
    
                uiThread { 
                //UI线程
                toast("线程${Thread.currentThread().name}")
    
             }
          }
    

    其他内容可以直接访问Anko

    Kotlin的缺点

    尽管 Kotlin 非常棒,但是它并不完美。我列举了一些我不喜欢的部分。

    1. 没有命名空间

    Kotlin 允许你在文件中定义顶级的函数和属性,但是这会带来困扰——所有从 Kotlin 引用的顶级声明无法区分。这让我们有时候在读代码时很难快速确定用的是哪一个函数。

    例如,你定义这样一个顶级函数:

    fun foo() {...}

    你可以通过 foo() 调用。

    如果你在不同的包里面也存在同样的方法,在调用时就不能明显区分出是调用的哪个方法。你可以通过在前面添加包名的方式去调用,但是如果 Java 约定的包名很深,似乎不太友好。

    一种近似的解决方案是使用单例的 object 类。

    object FooActions { fun foo() {...}}

    这样你在 Kotlin 中可以通过 FooActions.foo() 调用,但是在 Java 中你必须要这样 FooActions.INSTANCE.foo()这样调用,这看起来很麻烦。

    你也可以使用 @JvmStatic 去注解该方法,从而省掉INSTANCE

    其实没有命名空间并不是什么大不了的事,但是如果 Kotlin 能够提供的话,能省不少事。

    2. 没有静态修饰符

    Kotlin为静态函数和属性提供了一个和 Java 不一样的处理方式。并不是说有多烂,只是觉得让代码变得不干净而且没有必要。

    例如,在 Android 的 View 类中定义的静态属性 View.VISIBLE 和静态函数 View.inflate

    public class View { 
      public static final int VISIBLE = 0x00000000; 
      public static final int INVISIBLE = 0x00000004;
      public static View inflate(Context context, int resource) {...}
    }
    

    这个定义是简单的。然而,在 Kotlin 代码中:

    class View { 
      companion object { 
        @JvmField 
        val VISIBLE: Int = 0x00000000 
        @JvmField 
        val INVISIBLE: Int = 0x00000004 
        @JvmStatic 
        fun inflate(context: Context, resource: Int) {...} 
      }
    }
    

    注:companion object为伴生对象

    尽管 Kotlin 的版本并没有那么恐怖,但是它的复杂程度超过了我对这门语言的预期。如果去掉注解,你在 Java 中就不得不使用这样可怕的语法去调用:

    // With annotations:
    View.VISIBLE;
    //Without annotations:
    View.Companion.getVISIBLE();
    
    3. 编译方法数量

    Kotlin 肯定会减少项目中的代码行数,但是它也会提高代码在编译以后的方法数。主要原因就是 Kotlin 属性的实现方式。

    和 Java 不一样,Kotlin 没有提供单独定义域的方式。你必须使用 val 或者 var 来声明变量。这样有一个好处,就是省去了像 Java 一样定义 getters 和 setters 方法。

    但是这需要一定的成本。每一个public的 val 变量都会生成一个「支持域」和一个能被 Java 调用的 getter 方法。每一个public的 var 变量都会生成 getter 和 setter 方法。

    // kt 文件:
    // 默认就是public,无需额外添加public修饰符
    val strValPublic: String = "strValPublic"
    var strVarPublic: String = "strVarPublic"
    
    // 以下是反编译结果:
    public final class VarAndValKt {
       @NotNull
       private static final String strValPublic = "strValPublic";
       @NotNull
       private static String strVarPublic = "strVarPublic";
    
       @NotNull
       public static final String getStrValPublic() {
          return strValPublic;
       }
    
       @NotNull
       public static final String getStrVarPublic() {
          return strVarPublic;
       }
    
       public static final void setStrVarPublic(@NotNull String var0) {
          Intrinsics.checkParameterIsNotNull(var0, "<set-?>");
          strVarPublic = var0;
       }
    }
    

    拓展:Intrinsics.checkParameterIsNotNull 方法其实很简单,原理:

    public static void checkParameterIsNotNull(Object value, String paramName) {
        if (value == null) {
            throwParameterIsNullException(paramName);
        }
    }
    

    其实所有空安全的秘密都在这个类里面了

    庆幸的是,私有属性的 getters 和 setters 会生成域而不是生成方法。

    // kt文件:
    private val strValPrivate: String = "strValPrivate"
    private var strVarPrivate: String = "strVarPrivate"
    
    // 以下是反编译结果:
    public final class VarAndValKt {
       private static final String strValPrivate = "strValPrivate";
       private static String strVarPrivate = "strVarPrivate";
    }
    

    所以如果你把项目中Java代码转成Kotlin,而且之前的 Java 代码中定义了大量的公开域(这在定义常量的时候很常见),你会惊奇的发现最终编译生成的方法数量大幅上升。

    如果你的 Android 应用快接近方法数限制了,我建议你为不需要自定义 getter 方法的常量加上 @JvmField 注解。这样会阻止 getters 方法的生成,从而减少你的方法数。

    // kt 文件:
    @JvmField
    val strValPublic: String = "strValPublic"
    @JvmField
    var strVarPublic: String = "strVarPublic"
    
    // 以下是反编译结果:
    // 注意看,get set方法消失,取而代之的是private修饰符变成了public
    public final class VarAndValKt {
       @JvmField
       @NotNull
       public static final String strValPublic = "strValPublic";
       @JvmField
       @NotNull
       public static String strVarPublic = "strVarPublic";
    }
    
    4. 没有CE机制

    Kotlin官网对CE的解释:


    CE

    翻译一下:
    Kotlin 没有受检的异常。这其中有很多原因,但我们会提供一个简单的例子。
    以下是 JDK 中 StringBuilder 类实现的一个示例接口
    Appendable append(CharSequence csq) throws IOException;
    这个签名是什么意思? 它是说,每次我追加一个字符串到一些东西(一个 StringBuilder、某种日志、一个控制台等)上时我就必须捕获那些 IOException。 为什么?因为它可能正在执行 IO 操作(Writer 也实现了 Appendable)…… 所以它导致这种代码随处可见的出现

    我们看到Java的CE机制被诟病了很久,但是如果你经过理性的分析,就会发现,Java 的有些设计看起来“繁复多余”,实际上却是经过深思熟虑的决定。Java 的设计者知道有些地方可以省略,却故意把它做成多余的。我们不能盲目地以为简短就是好,多写几个字就是丑陋不优雅,其实不是那样的。

    Kotlin有异常机制,但不要求你在函数的类型里面声明可能出现的异常类型,也不使用静态类型系统对异常的处理进行检查和验证。那当我每调用一个函数(不管是标准库函数,第三方库函数,还是队友写的函数,甚至我自己写的函数),我都会疑惑这个函数是否会抛出异常。由于函数类型上不需要标记它可能抛出的异常,为了确保一个函数不会抛出异常,你就需要检查这个函数的源代码,以及它调用的那些函数的源代码,甚至整个调用树!

    在这种疑虑的情况下,你就不得不做最坏的打算,你就得把代码写成:

    try
    {
        foo()
    } 
    catch (e:Exception)
    {
        printf(e)
    }
    

    因为不知道 foo 函数里面会有什么异常出现,所以你的 catch 语句里面也不知道该做什么。大部分人只能在里面放一条 log,记录异常的发生。这是一种非常糟糕的写法,不但繁复,而且可能掩盖运行时错误。

    那么 Java 呢?因为 Java 有 CE,所以当你看到一个函数没有声明异常,就可以放心的省掉 try-catch。所以这个问题,自然而然就被避免了,你不需要在很多地方疑惑是否需要写 try-catch。Java 编译器的静态类型检查会告诉你,在什么地方必须写 try-catch,或者加上 throws 声明。

    结尾

    在学习过程中,我发现,如果有着扎实的Java基础,这东西掌握起来是很快的,所以到底学不学Kotlin,其实是不用着急的。一个新的语言想要快速的普及,那么可能只有在运行效率上有所提升,才是最大的优势,而Kotlin并不具备这样的属性。

    我们可以看下Java和Kotlin的编译速度对比。

    编译速度对比

    我不会试图比较一行代码的编译速度;相反,比较的是将代码从Java转换为Kotlin是否会影响其总体构建的时间。

    在转换之前,App Lock的Java代码有5,491个方法和12,371行代码。 改写后,这些数字下降到4,987方法和8,564行Kotlin代码。 在重写期间没有发生大的架构更改,因此在重写之前和之后测试编译时间应该很好地了解Java和Kotlin之间的构建时间的差异。我写了一个shell来重复执行gradle。所有测试连续进行10次。

    • clean + 不用Gradle daemon Build
      这是两种语言中构建时间最差的情况:从冷启动运行一个clean的构建。 对于这个测试,我禁用了Gradle daemon。
      这里是十个构建所花费的时间:
    Paste_Image.png

    对于没有Gradle daemon 并且clean构建,Java编译比Kotlin快17%,但是大部分人不会这么编译他们的代码。

    • clean +Gradle daemon Build
    Paste_Image.png

    可以看到,Kotlin第一次运行所花费的时间与上一个方案的时间相同,但后续运行的性能逐步提高。

    对于clean + Gralde daemon 编译,Java编译比Kotlin快13%。

    所以Kotlin编译在完整代码情况下比Java慢一点。 但是你通常只会对几个文件进行更改后编译,所以,我们来看看Kotlin在增量编译是否可以赶上Java。

    • 增量编译
    没有更改文件时使用增量编译 更改没有其他文件依赖的UI文件的增量编译 修改的源文件的增量编译

    所以虽然Java在clean构建比Kotlin 快10-15%,但这些情况很少。 对于大多数开发人员来说,更常见的情况是部分构建,随着Gradle daemon运行和增量编译的开启,Kotlin编译速度快或略快于Java。

    所以,还是那句话,一个新的语言想要快速的普及,在运行效率上有所提升,才是最大的优势,Kotlin肯定值得学习的,但并没有传的那么夸张。有精力就去学习,有自己的学习计划也可以放一放,延后再学。

    我想只有用得多了,Kotlin的优势才会慢慢展现出来,这需要一个较为漫长的过渡期。

    转载请注明 原文出处:http://www.jianshu.com/p/f364e3f9cc36
    有错误请多多指正!

    相关文章

      网友评论

      • 5ce3b4bed54b:能集成微信支付之类的第三方吗?
        FeelsChaotic:这个不影响 说到底 Java还是Kotlin 最终都是转为class 这也是Kotlin和Java基本100%互操作的原因 所以之前项目用了什么第三方库、引入了什么开源框架,这些统统不影响
      • 362a1bad14d8:为什么比较编译速度,而不是比较执行效率?
        FeelsChaotic:因为执行效率没差,Kotlin最终也是转为class字节码执行的,没有可比性。和Java最大的差别还是中间代码生成阶段,客户端开发对编译速度也比较敏感,Kotlin这么多语法糖不是没有缺点的
      • KinghomC:楼主 我想问个问题 就是 你们写kotlin的时候 那些注解都是一个字一个字的敲上去的吗?或者有没有方法可以写快点
        FeelsChaotic:1. 注解我们用得非常少,比如@JvmStatic注解就没有用,在Java中直接.INSTANCE去调用,大批量用的情况只能自己敲上去。
        2. Lambda 表达式是Java8的新特性,我反而觉得使用后代码更紧凑,看起来更美观,AS可以支持快捷转Lambda 的,刚开始我也觉得有点别扭,多对比一下转换前后,就会觉得很好用啦
        KinghomC:还有像->这些lamba符号 很不习惯
      • _孑孓_:体会到作者的层次还是挺深的~~
      • 今生挥毫只为你:java写Android代码时,有Android postfix completion后缀补全插件,为什么在kotlin写代码时无效,比如.toast .log
        KinghomC:我也是3.0.1 kotlin确实没有补全代码 所以感觉一个个敲 非常慢
        今生挥毫只为你:@FEELS_CHAOTIC 我用的是studio 3.0.1,在kotlin Android工程里面试了不行,java Android可以,不知道是个人问题,还是kotlin
        FeelsChaotic:你正在用的是什么ide?版本是多少呢?如果是Android Studio,需要升级到 3.0正式版才支持代码自动补全的功能
      • 李安杰克:老哥,很赞,良心贴!
      • ChineseBoy:请问加入Kotlin扩展和Anko库后,那方法数是不是会增加很多
        FeelsChaotic:@ChineseBoy 这个没有验证过
        ChineseBoy:@FEELS_CHAOTIC 如果只是引入Kotlin和其扩展库的话,和ButterKnife 比起来,哪个方法数更少
        FeelsChaotic:是会增加,但是函数扩展没有引入Kotlin增加的方法数多,可以说这么多的语法糖就是因为Kotlin在目标代码生成的过程,给我们自动生成很多方法,做了很多类似于Java封装的事情,比如自动生成方法、Companion修饰符转变成静态类、修改类属性为final等等工作。函数扩展只是Kotlin的一个特性,函数扩展不会增加太多的方法数,引入Kotlin方法数更多
      • lMagal:你好,请问下怎么禁用启动控件?
        我用xxx.isEnabled = true 设置 但是界面不刷新
        FeelsChaotic:没理解 可否私聊说详细点?
      • 109356ba09f2:Kotlin Android有推荐的书没?
        Kotlin 能否开发后台?
        FeelsChaotic:@丶Banana 感谢纠正!
        1fc1898a9aca:@FEELS_CHAOTIC 后台开发是支持的,但是不支持JSP页面内写kotlin代码.毕竟是JVM
        FeelsChaotic:@GeekRubicon
        1、我kotlin的学习来源主要是官网文档和网上博文,暂时没有看书,所以不知道推荐哪本,kotlin其实很好上手,很多特征也可以反编译成java来学习比对,感觉看书大多为了建立知识体系,我觉得kotlin的学习可以以博文为主的
        2、kotlin将支持web和iOS开发了,官方有demo了,后台开发目前还不支持
      • CPLASF1925:.kt文件和activity有什么区别
        FeelsChaotic:@CPLASF1925 kt文件和activity不是一个层级的东西 应该说kt文件和java文件的区别才对
      • 63d00c0f7d95:好文!!refied在java中不支持调用吧?那是不是不能说java kotlin 100%互操作性?
        FeelsChaotic:这个问题很细心,reified必须要和inline 修饰符搭配使用,而inline修饰方法在反编译后修饰符是 private static final(仅支持本类调用,因为如果不这么设计,在其他地方调用需要更改内联调用地点),所以不仅是java没法调用,就连Kotlin其他包其他类都是无法访问的。
      • 超级小秘书:写的非常棒 而且用数据说话 赞
      • Wing_Li:非常棒,感谢
        FeelsChaotic:对开发者有帮助就好 也是初学 有错误请多多指正

      本文标题:Kotlin —— 这次入门就不用放弃了

      本文链接:https://www.haomeiwen.com/subject/xdgyfxtx.html