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

写在文前

本文将展示在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
有错误请多多指正!

转自:https://www.jianshu.com/p/f364e3f9cc36

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页