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

寫在文前

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

快速上手

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

Kotlin優(yōu)勢

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

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

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

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

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

詳細(xì)實(shí)例

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

通過Kotlin,可以更容易地避免模版代碼触幼,因?yàn)榇蟛糠值牡湫颓闆r都在語言中默認(rèn)覆蓋實(shí)現(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 + '\'' +
          '}';
    }
}

我們在不使用第三方框架的基礎(chǔ)上,需要大量的set get方法和復(fù)寫基礎(chǔ)方法媒峡。

而使用Kotlin瘟栖,我們只需要通過data關(guān)鍵字:

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

這個數(shù)據(jù)類,它會自動生成所有屬性和它們的訪問器谅阿, 并自動生成相應(yīng)的 equals半哟、hashcode、toString 方法签餐。

空口無憑寓涨,我們驗(yàn)證一下:

首先建立一個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;
      }
   }
}

事實(shí)說明在kotlin中 data 修飾符 = java中 private + getter + setter + toString + equals + hashCode

2. 空安全

當(dāng)我們使用Java開發(fā)的時候男摧,如果我們不想遇到NullPointerException蔬墩,我們就需要在每次使用它之前,不停地去判斷它是否為null耗拓。

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

我們可以像這樣去寫:

// 這里不能通過編譯. User對象不能是null
var notNullUser: User= null

// User可以是 null
var user: User? = null

// 無法編譯, user可能是null乔询,我們需要進(jìn)行處理
user.print()

// 只要在user != null時才會打印
user?.print()

// 使用Elvis操作符來給定一個在是null的情況下的替代值
val name = user?.name ?: "empty"

/** 
如果user為可空類型樟插,又一定要調(diào)用它的成員函數(shù)和變量,可以用!!操作符
兩種可能竿刁,要么正確返回name黄锤,要么拋出空指針異常
當(dāng)user為null,你不想返回null食拜,而是拋出一個空指針異常鸵熟,你就可以使用它。
*/
var name = user!!.name

3. 擴(kuò)展方法

我們可以給任何類添加函數(shù)(View负甸,Context等)流强。比起Java的繼承機(jī)制痹届,更加簡潔和優(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)賦了默認(rèn)值队腐,所以這個參數(shù)可傳可不傳。

包括擴(kuò)展屬性奏篙,可以直接 類名.屬性名:類型

注意:Kotlin 的方法擴(kuò)展并不是真正修改了對應(yīng)的類文件柴淘,而是在編譯器和 IDE 方面做了處理。使我們看起來像是擴(kuò)展了方法报破。

4. 函數(shù)式支持

  • Collections迭代

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

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

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

map方法源碼.png

甚至于可以

list.map(::println)

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

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

println方法源碼.png

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

  • 事件

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

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

5. 互操作性

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

就舉個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. 其他

  • 單例

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

先看Java,在Java中筹误,實(shí)現(xiàn)一個單例桐早,我們需要:

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

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

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

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

object PlainOldSingleton {

}

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

單例

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

class LazyNotThreadSafe {
      //方式一
    companion object{
        val instance by lazy(LazyThreadSafetyMode.NONE) {
            LazyNotThreadSafe()
        }

        //方式二搀军,實(shí)際是Java的直譯
    private var instance2: LazyNotThreadSafe? = null

        fun get() : LazyNotThreadSafe {
            if(instance2 == null){
                instance2 = LazyNotThreadSafe()
            }
            return instance2!!
        }
    }
}

如果想要實(shí)現(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中敛摘,委托的實(shí)現(xiàn)依靠于關(guān)鍵字 by门烂,
by表示將抽象主題的實(shí)例(by后邊的實(shí)例)保存在代理類實(shí)例的內(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的泛型實(shí)際上是偽泛型薯鳍,對泛型支持的底層實(shí)現(xiàn)采用的是類型擦除的方式(只有在編譯期才有)咖气。

所以當(dāng)使用Gson.fromJson(String json , Class<T> classOf)方法時,雖然傳入了類型參數(shù)挖滤,當(dāng)實(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)
}

這要?dú)w功于inline符欠,inline 意味著編譯的時候真正要編譯到調(diào)用點(diǎn)。那么哪個方法調(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)雅呢?完全沒覺得更加簡潔澳卫痢5煊俊!別急磷杏,Kotlin常用的獲取控件方式不是這樣的溜畅,容我介紹個Kotlin庫——Anko。

3. Kotlin庫——Anko

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

1. 再也不用findViewById

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

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

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

有時候?qū)懙氖遣皇窍胪拢赡苡行┤苏f現(xiàn)在不是有一些注解的庫稿械,如butterknife,當(dāng)我們使用注解時可以不用findViewById了,使用方式如下

@BindView(R.id.user)
TextView username;

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

確實(shí)是這樣选泻,使用注解后確實(shí)給我們少了一些工作量,不過這依然沒有最簡單化,最簡單的就是我們可以直接給id為user的控件直接賦值页眯,或許你會感覺這有點(diǎn)不可思議梯捕。不過Kotlin確實(shí)做到了。我們可以直接這樣寫

user.text="我是一個TextView"

user就是我們布局文件聲明的id窝撵,.text就相當(dāng)于setText()科阎,在Kotlin語言中,我們看不到了像Java中的set/get方法了忿族。

當(dāng)我們想這樣使用的時候(不用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í)這是通過擴(kuò)展函數(shù)實(shí)現(xiàn)的道批,我們看下內(nèi)部的實(shí)現(xiàn)細(xì)節(jié):

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

2. Anko Layout

通常我們使用xml文件寫我們的布局,但是存在有一些缺點(diǎn):如不是類型安全入撒,不是空安全隆豹,解析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中有一個點(diǎn)擊事件章钾,當(dāng)點(diǎn)擊時將EditText的內(nèi)容以toast顯示墙贱。

Anko Layout.png

在上面創(chuàng)建UI過程中,我們直接把創(chuàng)建UI的代碼寫在onCreate方法中了贱傀,當(dāng)然惨撇,還有一種寫法。我們創(chuàng)建一個內(nèi)部類實(shí)行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的擴(kuò)展函數(shù),說到擴(kuò)展函數(shù)帆卓,我發(fā)現(xiàn)Kotlin源碼里大量地使用擴(kuò)展函數(shù)巨朦,這也是Kotlin語言的優(yōu)勢之一米丘。確實(shí)很強(qiáng)大,例如dip擴(kuò)展(摘取View擴(kuò)展)

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)絡(luò)請求AsyncTask

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

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

         }
      }

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

Kotlin的缺點(diǎn)

盡管 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矢腻。

其實(shí)沒有命名空間并不是什么大不了的事,但是如果 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 的版本并沒有那么恐怖,但是它的復(fù)雜程度超過了我對這門語言的預(yù)期沮趣。如果去掉注解屯烦,你在 Java 中就不得不使用這樣可怕的語法去調(diào)用:

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

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

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

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

// kt 文件:
// 默認(rèn)就是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 方法其實(shí)很簡單窃躲,原理:

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

其實(shí)所有空安全的秘密都在這個類里面了

慶幸的是,私有屬性的 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";
}

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

如果你的 Android 應(yīng)用快接近方法數(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機(jī)制

Kotlin官網(wǎng)對CE的解釋:


CE

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

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

Kotlin有異常機(jī)制抛虏,但不要求你在函數(shù)的類型里面聲明可能出現(xiàn)的異常類型材蛛,也不使用靜態(tài)類型系統(tǒng)對異常的處理進(jìn)行檢查和驗(yàn)證凝化。那當(dāng)我每調(diào)用一個函數(shù)(不管是標(biāo)準(zhǔn)庫函數(shù)归榕,第三方庫函數(shù)惧笛,還是隊(duì)友寫的函數(shù)榛泛,甚至我自己寫的函數(shù))综液,我都會疑惑這個函數(shù)是否會拋出異常薛窥。由于函數(shù)類型上不需要標(biāo)記它可能拋出的異常叉橱,為了確保一個函數(shù)不會拋出異常挫以,你就需要檢查這個函數(shù)的源代碼,以及它調(diào)用的那些函數(shù)的源代碼窃祝,甚至整個調(diào)用樹掐松!

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

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

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

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

結(jié)尾

在學(xué)習(xí)過程中擅腰,我發(fā)現(xiàn)蟋恬,如果有著扎實(shí)的Java基礎(chǔ),這東西掌握起來是很快的趁冈,所以到底學(xué)不學(xué)Kotlin歼争,其實(shí)是不用著急的拜马。一個新的語言想要快速的普及,那么可能只有在運(yùn)行效率上有所提升沐绒,才是最大的優(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)更改,因此在重寫之前和之后測試編譯時間應(yīng)該很好地了解Java和Kotlin之間的構(gòu)建時間的差異漆魔。我寫了一個shell來重復(fù)執(zhí)行g(shù)radle坷檩。所有測試連續(xù)進(jìn)行10次。

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

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

  • clean +Gradle daemon Build
Paste_Image.png

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

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

所以Kotlin編譯在完整代碼情況下比Java慢一點(diǎn)爹谭。 但是你通常只會對幾個文件進(jìn)行更改后編譯枷邪,所以,我們來看看Kotlin在增量編譯是否可以趕上Java诺凡。

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

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

所以芥吟,還是那句話侦铜,一個新的語言想要快速的普及,在運(yùn)行效率上有所提升钟鸵,才是最大的優(yōu)勢钉稍,Kotlin肯定值得學(xué)習(xí)的,但并沒有傳的那么夸張棺耍。有精力就去學(xué)習(xí)贡未,有自己的學(xué)習(xí)計劃也可以放一放,延后再學(xué)烈掠。

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市矫限,隨后出現(xiàn)的幾起案子哺哼,更是在濱河造成了極大的恐慌,老刑警劉巖叼风,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件取董,死亡現(xiàn)場離奇詭異,居然都是意外死亡无宿,警方通過查閱死者的電腦和手機(jī)茵汰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孽鸡,“玉大人蹂午,你說我怎么就攤上這事”蚣睿” “怎么了豆胸?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長巷疼。 經(jīng)常有香客問我晚胡,道長,這世上最難降的妖魔是什么嚼沿? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任估盘,我火速辦了婚禮,結(jié)果婚禮上骡尽,老公的妹妹穿的比我還像新娘忿檩。我一直安慰自己,他們只是感情好爆阶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布燥透。 她就那樣靜靜地躺著沙咏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪班套。 梳的紋絲不亂的頭發(fā)上肢藐,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音吱韭,去河邊找鬼吆豹。 笑死,一個胖子當(dāng)著我的面吹牛理盆,可吹牛的內(nèi)容都是我干的痘煤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼猿规,長吁一口氣:“原來是場噩夢啊……” “哼衷快!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起姨俩,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蘸拔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后环葵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體调窍,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年张遭,在試婚紗的時候發(fā)現(xiàn)自己被綠了邓萨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡菊卷,死狀恐怖缔恳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情的烁,我是刑警寧澤褐耳,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布诈闺,位于F島的核電站渴庆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏雅镊。R本人自食惡果不足惜襟雷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仁烹。 院中可真熱鬧耸弄,春花似錦、人聲如沸卓缰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捌显,卻和暖如春茁彭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扶歪。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工理肺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人善镰。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓妹萨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炫欺。 傳聞我的和親對象是個殘疾皇子乎完,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容