Kotlin —— 這次入門就不用放棄了

寫在文前

本文將展示在Android中會遇到的實際問題,并且使用Kotlin怎么去解決它們。一些Android開發(fā)者在處理異步、數(shù)據(jù)庫或者處理Activity中非常冗長的listener時發(fā)現(xiàn)了很多的問題。通過一個個真實的場景,我們一邊解決問題一邊學習Kotlin的特性。

快速上手

如果不知道如何在Kotlin中寫一個相當簡單的Java表達式。這里有一個簡單的訣竅,就是在AndroidStudio的Java文件中編寫一段代碼,然后將其粘貼到kt文件中,它會自動轉(zhuǎn)換為Kotlin。

Kotlin優(yōu)勢

  1. 它更加易表現(xiàn):這是它最重要的優(yōu)點之一。你可以編寫少得多的代碼。

  2. 它更加安全:Kotlin是空安全的,也就是說在我們編譯時期就處理了各種null的情況,避免了執(zhí)行時異常。你可以節(jié)約很多調(diào)試空指針異常的時間,解決掉null引發(fā)的bug。

  3. 它可以擴展函數(shù):這意味著,就算我們沒有權(quán)限去訪問這個類中的代碼,我們也可以擴展這個類的更多的特性。

  4. 它是函數(shù)式的:Kotlin是基于面向?qū)ο蟮恼Z言。但是就如其他很多現(xiàn)代的語言那樣,它使用了很多函數(shù)式編程的概念,比如,使用lambda表達式來更方便地解決問題。其中一個很棒的特性就是Collections的處理方式。我稍后會進行介紹。

  5. 它是高度互操作性的:你可以繼續(xù)使用所有用Java寫的代碼和庫,甚至可以在一個項目中使用Kotlin和Java兩種語言混合編程。一行Java一行Kotlin,別提有多風騷了。

詳細實例

1. 易表現(xiàn)和簡潔性

通過Kotlin,可以更容易地避免模版代碼,因為大部分的典型情況都在語言中默認覆蓋實現(xiàn)了。

舉個例子,在Java中,如果我們要典型的數(shù)據(jù)類,我們需要去編寫(至少生成)這些代碼:

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關(guān)鍵字:

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

這個數(shù)據(jù)類,它會自動生成所有屬性和它們的訪問器, 并自動生成相應的 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;
   }
 
  // 解構(gòu)聲明
   @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開發(fā)的時候,如果我們不想遇到NullPointerException,我們就需要在每次使用它之前,不停地去判斷它是否為null。

而Kotlin是空安全的,我們通過一個安全調(diào)用操作符?來明確地指定一個對象是否能為空。

我們可以像這樣去寫:

// 這里不能通過編譯. 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為可空類型,又一定要調(diào)用它的成員函數(shù)和變量,可以用!!操作符
兩種可能,要么正確返回name,要么拋出空指針異常
當user為null,你不想返回null,而是拋出一個空指針異常,你就可以使用它。
*/
var name = user!!.name

3. 擴展方法

我們可以給任何類添加函數(shù)(View,Context等)。比起Java的繼承機制,更加簡潔和優(yōu)雅。舉個例子,我們可以給fragment增加一個顯示toast的函數(shù):

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

我們現(xiàn)在可以這么做:

fragment.toast("Hello world!")

此處duration已經(jīng)賦了默認值,所以這個參數(shù)可傳可不傳。

包括擴展屬性,可以直接 類名.屬性名:類型

注意:Kotlin 的方法擴展并不是真正修改了對應的類文件,而是在編譯器和 IDE 方面做了處理。使我們看起來像是擴展了方法。

4. 函數(shù)式支持

  • Collections迭代

Kotlin使用lambda表達式來更方便地解決問題。體現(xiàn)最好的就是Collections的處理方式。

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

查看源碼,我們可以看到實際上map就是一個擴展方法,給所有可以迭代的集合提供該方法,map方法接收的參數(shù)是一個lambda表達式,類型為T,返回值為R類型(意味著任意類型),那這里T類型實際上就是list的元素類型。

map方法源碼.png

甚至于可以

list.map(::println)

::表示方法或類的引用。為什么可以直接傳方法引用呢?

我們看看println方法源碼,可以看到println接收一個Any類也就是任意類型,而且返回值為空(Kotlin中空類型為Unit類,此處源碼省略了返回值類型聲明),所以完全符合map方法的要求。

println方法源碼.png

注:類似于RxJava對數(shù)組的處理,Kotlin也提供了flatMap方法,具體可以自己了解。

  • 事件

在Java中,每次我們?nèi)ヂ暶饕粋€點擊事件,都不得不去實現(xiàn)一個內(nèi)部類,而在Kotlin中,可以直接聲明我們要做什么。

view.setOnClickListener { toast("Hello world!") }
//注:此處的toast方法是Kotlin默認已經(jīng)提供的擴展方法

5. 互操作性

Kotlin調(diào)用Java和Java調(diào)用Kotlin與之前的Java 類之間調(diào)用方式?jīng)]有太大差別,不詳細介紹。

就舉個Java調(diào)用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表示參數(shù)個數(shù))

//Kotlin
class Overloads {
    @JvmOverloads
    fun overloaded(a: Int, b: Int = 0, c: Int = 1){
        println("$a, $b, $c")
    }
}
/**
在Java可以調(diào)用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. 其他

  • 單例

首先說說單例的實現(xiàn)方式,在之后的實戰(zhàn)中,將會經(jīng)常接觸到object這個關(guān)鍵字。

先看Java,在Java中,實現(xiàn)一個單例,我們需要:

  1. 保留一個單例對象的靜態(tài)實例

  2. 提供一個類方法讓外界訪問唯一的實例

  3. 構(gòu)造方法采用private修飾符

而在Kotlin中,一個修飾符就解決了。

object PlainOldSingleton {

}

怎么做到的?我們看看反編譯的結(jié)果:

單例

可以看到寫法和Java是完全一樣的,又有一個新問題,在類加載的時候就初始化了實例,這種方式很糟糕,我們最好選擇懶加載。那么在Kotlin中懶加載的2種實現(xiàn)方式如下:

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

如果想要實現(xiàn)線程安全,可以加上@Synchronized注解,這和Java中給類加上Synchronized修飾符是一樣的。同樣@Volatile注解和Java的Volatile修飾符作用也是一樣的。

或者使用靜態(tài)內(nèi)部類的單例方法:

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

    private object Holder{
        val instance = LazyThreadSafeStaticInnerObject()
    }
}
  • 委托

Kotlin中,委托的實現(xiàn)依靠于關(guān)鍵字 by,
by表示將抽象主題的實例(by后邊的實例)保存在代理類實例的內(nèi)部。

比如下面這個例子中: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。調(diào)用方法的時候,我們需要傳入想要轉(zhuǎn)成的類的Class。我們都知道Java的泛型實際上是偽泛型,對泛型支持的底層實現(xiàn)采用的是類型擦除的方式(只有在編譯期才有)。

所以當使用Gson.fromJson(String json , Class<T> classOf)方法時,雖然傳入了類型參數(shù),當實際上這個T仍然是個Object。

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

reified的意思是具體化。作為Kotlin的一個方法泛型關(guān)鍵字,它代表你可以在方法體內(nèi)訪問泛型指定的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,直接調(diào)用

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 意味著編譯的時候真正要編譯到調(diào)用點。那么哪個方法調(diào)用了它,參數(shù)的類型都是確定的。也就不需要傳入Class了

** 7. 擺脫不必要的依賴**

Kotlin替換了許多第三方庫,如ButterKnife、Google Autovalue、Retrolambda、Lombok和一些RxJava代碼。

但是也是可以100%兼容RxJava的,舉個讀取本地文本逐個字打印的例子。

Kotlin中使用RxJava

好了,言歸正傳。

普通的獲取View方法,需要一個個去findViewById

普通的獲取View方法

而使用Kotlin后

使用Kotlin獲取View

可能有人注意到了,還是需要findViewById啊??!騙子!說好的優(yōu)雅呢?完全沒覺得更加簡潔?。?!別急,Kotlin常用的獲取控件方式不是這樣的,容我介紹個Kotlin庫——Anko。

3. Kotlin庫——Anko

簡介
Anko是Kotlin官方開發(fā)的一個讓開發(fā)Android應用更快速更簡單的Kotlin庫

1. 再也不用findViewById

做過Android開發(fā)的人都知道,布局文件寫的多了,findViewById也是一個很大的工作量,而且還要先聲明變量,在findViewById然后再強轉(zhuǎn)成我們的控件,使用方式一般如下

TextView username;
username=(TextView)findViewById(R.id.user);

username.setText("我是一個TextView");

有時候?qū)懙氖遣皇窍胪拢赡苡行┤苏f現(xiàn)在不是有一些注解的庫,如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呢?其實這是通過擴展函數(shù)實現(xiàn)的,我們看下內(nèi)部的實現(xiàn)細節(jié):

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

2. Anko Layout

通常我們使用xml文件寫我們的布局,但是存在有一些缺點:如不是類型安全,不是空安全,解析xml文件消耗更多的CPU和電量等等。

而Anko Layout可以使用DSL動態(tài)創(chuàng)建我們的UI,并且它比我們使用Java動態(tài)創(chuàng)建布局方便很多。主要是更簡潔,它擁有類似xml創(chuàng)建布局的層級關(guān)系,能讓我們更容易閱讀。

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

我們在OnCreate方法中可以去掉setContentView,然后加入上面代碼就可以顯示如下圖的效果,即一個垂直的線性布局中,放了一個TextView,一個EditText,和一個Button。并且Button中有一個點擊事件,當點擊時將EditText的內(nèi)容以toast顯示。

Anko Layout.png

在上面創(chuàng)建UI過程中,我們直接把創(chuàng)建UI的代碼寫在onCreate方法中了,當然,還有一種寫法。我們創(chuàng)建一個內(nèi)部類實行AnkoComponent接口,并重寫createView方法,該方法返回一個View,也就是我們創(chuàng)建的布局。修改如下

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轉(zhuǎn)換為像素
                       height= dip(40)
                       width= matchParent
                   }
                   val name = editText("EditText")
                   button("Button") {
                        onClick { view ->
                            toast("Hello, ${name.text}!")
                        }
                   }
               }
           }
        }
    }

然后在onCreate方法中加一句代碼,即可創(chuàng)建我們的布局頁面了。如下

UI().setContentView(this@LoginActivity)

其中,dip(10),表示將10dp轉(zhuǎn)換為像素的意思,是Anko的擴展函數(shù),說到擴展函數(shù),我發(fā)現(xiàn)Kotlin源碼里大量地使用擴展函數(shù),這也是Kotlin語言的優(yōu)勢之一。確實很強大,例如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也是拓展函數(shù)一樣

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. 其他方面

比如網(wǎng)絡請求AsyncTask

 doAsync {
            //后臺執(zhí)行代碼

            uiThread { 
            //UI線程
            toast("線程${Thread.currentThread().name}")

         }
      }

其他內(nèi)容可以直接訪問Anko

Kotlin的缺點

盡管 Kotlin 非常棒,但是它并不完美。我列舉了一些我不喜歡的部分。

1. 沒有命名空間

Kotlin 允許你在文件中定義頂級的函數(shù)和屬性,但是這會帶來困擾——所有從 Kotlin 引用的頂級聲明無法區(qū)分。這讓我們有時候在讀代碼時很難快速確定用的是哪一個函數(shù)。

例如,你定義這樣一個頂級函數(shù):

fun foo() {...}

你可以通過 foo() 調(diào)用。

如果你在不同的包里面也存在同樣的方法,在調(diào)用時就不能明顯區(qū)分出是調(diào)用的哪個方法。你可以通過在前面添加包名的方式去調(diào)用,但是如果 Java 約定的包名很深,似乎不太友好。

一種近似的解決方案是使用單例的 object 類。

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

這樣你在 Kotlin 中可以通過 FooActions.foo() 調(diào)用,但是在 Java 中你必須要這樣 FooActions.INSTANCE.foo()這樣調(diào)用,這看起來很麻煩。

你也可以使用 @JvmStatic 去注解該方法,從而省掉INSTANCE。

其實沒有命名空間并不是什么大不了的事,但是如果 Kotlin 能夠提供的話,能省不少事。

2. 沒有靜態(tài)修飾符

Kotlin為靜態(tài)函數(shù)和屬性提供了一個和 Java 不一樣的處理方式。并不是說有多爛,只是覺得讓代碼變得不干凈而且沒有必要。

例如,在 Android 的 View 類中定義的靜態(tài)屬性 View.VISIBLE 和靜態(tài)函數(shù) 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 中就不得不使用這樣可怕的語法去調(diào)用:

// With annotations:
View.VISIBLE;
//Without annotations:
View.Companion.getVISIBLE();
3. 編譯方法數(shù)量

Kotlin 肯定會減少項目中的代碼行數(shù),但是它也會提高代碼在編譯以后的方法數(shù)。主要原因就是 Kotlin 屬性的實現(xiàn)方式。

和 Java 不一樣,Kotlin 沒有提供單獨定義域的方式。你必須使用 val 或者 var 來聲明變量。這樣有一個好處,就是省去了像 Java 一樣定義 getters 和 setters 方法。

但是這需要一定的成本。每一個public的 val 變量都會生成一個「支持域」和一個能被 Java 調(diào)用的 getter 方法。每一個public的 var 變量都會生成 getter 和 setter 方法。

// kt 文件:
// 默認就是public,無需額外添加public修飾符
val strValPublic: String = "strValPublic"
var strVarPublic: String = "strVarPublic"

// 以下是反編譯結(jié)果:
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"

// 以下是反編譯結(jié)果:
public final class VarAndValKt {
   private static final String strValPrivate = "strValPrivate";
   private static String strVarPrivate = "strVarPrivate";
}

所以如果你把項目中Java代碼轉(zhuǎn)成Kotlin,而且之前的 Java 代碼中定義了大量的公開域(這在定義常量的時候很常見),你會驚奇的發(fā)現(xiàn)最終編譯生成的方法數(shù)量大幅上升。

如果你的 Android 應用快接近方法數(shù)限制了,我建議你為不需要自定義 getter 方法的常量加上 @JvmField 注解。這樣會阻止 getters 方法的生成,從而減少你的方法數(shù)。

// kt 文件:
@JvmField
val strValPublic: String = "strValPublic"
@JvmField
var strVarPublic: String = "strVarPublic"

// 以下是反編譯結(jié)果:
// 注意看,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官網(wǎng)對CE的解釋:


CE

翻譯一下:
Kotlin 沒有受檢的異常。這其中有很多原因,但我們會提供一個簡單的例子。
以下是 JDK 中 StringBuilder 類實現(xiàn)的一個示例接口
Appendable append(CharSequence csq) throws IOException;
這個簽名是什么意思? 它是說,每次我追加一個字符串到一些東西(一個 StringBuilder、某種日志、一個控制臺等)上時我就必須捕獲那些 IOException。 為什么?因為它可能正在執(zhí)行 IO 操作(Writer 也實現(xiàn)了 Appendable)…… 所以它導致這種代碼隨處可見的出現(xiàn)

我們看到Java的CE機制被詬病了很久,但是如果你經(jīng)過理性的分析,就會發(fā)現(xiàn),Java 的有些設計看起來“繁復多余”,實際上卻是經(jīng)過深思熟慮的決定。Java 的設計者知道有些地方可以省略,卻故意把它做成多余的。我們不能盲目地以為簡短就是好,多寫幾個字就是丑陋不優(yōu)雅,其實不是那樣的。

Kotlin有異常機制,但不要求你在函數(shù)的類型里面聲明可能出現(xiàn)的異常類型,也不使用靜態(tài)類型系統(tǒng)對異常的處理進行檢查和驗證。那當我每調(diào)用一個函數(shù)(不管是標準庫函數(shù),第三方庫函數(shù),還是隊友寫的函數(shù),甚至我自己寫的函數(shù)),我都會疑惑這個函數(shù)是否會拋出異常。由于函數(shù)類型上不需要標記它可能拋出的異常,為了確保一個函數(shù)不會拋出異常,你就需要檢查這個函數(shù)的源代碼,以及它調(diào)用的那些函數(shù)的源代碼,甚至整個調(diào)用樹!

在這種疑慮的情況下,你就不得不做最壞的打算,你就得把代碼寫成:

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

因為不知道 foo 函數(shù)里面會有什么異常出現(xiàn),所以你的 catch 語句里面也不知道該做什么。大部分人只能在里面放一條 log,記錄異常的發(fā)生。這是一種非常糟糕的寫法,不但繁復,而且可能掩蓋運行時錯誤。

那么 Java 呢?因為 Java 有 CE,所以當你看到一個函數(shù)沒有聲明異常,就可以放心的省掉 try-catch。所以這個問題,自然而然就被避免了,你不需要在很多地方疑惑是否需要寫 try-catch。Java 編譯器的靜態(tài)類型檢查會告訴你,在什么地方必須寫 try-catch,或者加上 throws 聲明。

結(jié)尾

在學習過程中,我發(fā)現(xiàn),如果有著扎實的Java基礎,這東西掌握起來是很快的,所以到底學不學Kotlin,其實是不用著急的。一個新的語言想要快速的普及,那么可能只有在運行效率上有所提升,才是最大的優(yōu)勢,而Kotlin并不具備這樣的屬性。

我們可以看下Java和Kotlin的編譯速度對比。

編譯速度對比

我不會試圖比較一行代碼的編譯速度;相反,比較的是將代碼從Java轉(zhuǎn)換為Kotlin是否會影響其總體構(gòu)建的時間。

在轉(zhuǎn)換之前,App Lock的Java代碼有5,491個方法和12,371行代碼。 改寫后,這些數(shù)字下降到4,987方法和8,564行Kotlin代碼。 在重寫期間沒有發(fā)生大的架構(gòu)更改,因此在重寫之前和之后測試編譯時間應該很好地了解Java和Kotlin之間的構(gòu)建時間的差異。我寫了一個shell來重復執(zhí)行g(shù)radle。所有測試連續(xù)進行10次。

  • clean + 不用Gradle daemon Build
    這是兩種語言中構(gòu)建時間最差的情況:從冷啟動運行一個clean的構(gòu)建。 對于這個測試,我禁用了Gradle daemon。
    這里是十個構(gòu)建所花費的時間:
Paste_Image.png

對于沒有Gradle daemon 并且clean構(gòu)建,Java編譯比Kotlin快17%,但是大部分人不會這么編譯他們的代碼。

  • clean +Gradle daemon Build
Paste_Image.png

可以看到,Kotlin第一次運行所花費的時間與上一個方案的時間相同,但后續(xù)運行的性能逐步提高。

對于clean + Gralde daemon 編譯,Java編譯比Kotlin快13%。

所以Kotlin編譯在完整代碼情況下比Java慢一點。 但是你通常只會對幾個文件進行更改后編譯,所以,我們來看看Kotlin在增量編譯是否可以趕上Java。

  • 增量編譯
沒有更改文件時使用增量編譯
更改沒有其他文件依賴的UI文件的增量編譯
修改的源文件的增量編譯

所以雖然Java在clean構(gòu)建比Kotlin 快10-15%,但這些情況很少。 對于大多數(shù)開發(fā)人員來說,更常見的情況是部分構(gòu)建,隨著Gradle daemon運行和增量編譯的開啟,Kotlin編譯速度快或略快于Java。

所以,還是那句話,一個新的語言想要快速的普及,在運行效率上有所提升,才是最大的優(yōu)勢,Kotlin肯定值得學習的,但并沒有傳的那么夸張。有精力就去學習,有自己的學習計劃也可以放一放,延后再學。

我想只有用得多了,Kotlin的優(yōu)勢才會慢慢展現(xiàn)出來,這需要一個較為漫長的過渡期。

轉(zhuǎn)載請注明 原文出處:http://www.itdecent.cn/p/f364e3f9cc36
有錯誤請多多指正!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容