亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

為什么 Kotlin 調用 java 時可以使用 Lambda

為什么 Kotlin 調用 java 時可以使用 Lambda

www說 2019-03-12 15:08:03
為什么 Kotlin 調用 java 時可以使用 Lambda
查看完整描述

2 回答

?
有只小跳蛙

TA貢獻1824條經驗 獲得超8個贊

1. Kotlin 中的 Lambda 表達式

如果你已經開始使用 Koltin, 或者對它有過一些了解的話,那么一定對這種寫法并不陌生了:

// 代碼一:Kotlin 代碼view.setOnClickListener{println("click")}1234

它跟下面這段 Java 代碼是等價的:

// 代碼二:java 代碼view.setOnClickListener(new View.OnClickListener() {    @Overridepublic void onClick(View v) {System.out.println("click");}});1234567

和 Java8 一樣,Kotlin 是支持 Lambda 表達式的,如代碼一所示,就是 Lambda 的一個具體應用。

可見,使用 lambda 減少了很多冗余,使代碼寫起來更簡潔優雅,讀起來也更順暢自然了。

但是,你有沒有想過,為什么 Kotlin 可以這樣寫,這里為什么可以使用 lambda ?

2. 為什么可以這么寫?

在 Kotlin 中,一個 Lambda 就是一個匿名函數。

代碼一其實是對下面代碼三的簡寫:

// 代碼三:Kotlin 代碼view.setOnClickListener({v -> println("click")})1234

之所以簡寫成代碼一的樣子,是基于這兩點特性:

  • 如果 lambda 是一個函數的唯一參數,那么調用這個函數時可以省略圓括號

  • 如果 lambda 所表示的匿名函數只有一個參數,那么可以省略它的聲明以及->符號(默認會用it來給省略的參數名命名)

  • OK,從代碼三的結構中,能夠更清晰的看出,這里的 view.setOnClickListener 函數是接收了一個 lambda 作為參數。而在 Kotlin 中,什么樣的函數才能把lambda(也即另一個函數)作為參數呢?—— 對,就是高階函數。

    什么是高階函數?

    高階函數是將函數用作參數或返回值的函數。

    這是 Kotlin 和 Java 的區別之一,java 中并沒有高階函數的支持(java8是有高階函數的)。當我們在 java 中需要用到類似的概念時,通常的做法是傳遞一個匿名類作為參數,然后實現其中的某些抽象方法 —— 就比如上面的代碼二。

    事實上,如果在 Android Studio 中,從 Kotlin 的代碼查看 view.setOnClickListener 函數的定義,就會發現,看到的函數簽名就是一個高階函數的定義:

    函數簽名提示

    如上圖,所看到函數簽名是:

    public final fun setOnClickListener(l: ((v:View!)->Unit)!): Unit

    當然,因為方法是在 Java 中定義的,所以它也列出了 Java 的聲明,是這樣:

    public void setOnClickListener(OnClickListener l)

    我們知道,Kotlin 跟 Java 的很多類型都有差異,所以它們在互相調用的時,會有一個按照對應關系的轉換。

    對于上面的對 setOnClickListener 方法的轉換,別的地方都好理解,比較難懂的是,為什么會把參數從 OnClickListener 類型轉換成了 (View) -> Unit。

    (View) -> Unit 是一個函數類型,它表示這樣一個函數:接收1個View類型的參數,返回Unit。

    正是這個對參數類型的轉換,使得 setOnClickListener 方法在 Kotlin 中變成了一個高階函數,這樣正是它之所以能夠使用 lambda 作為參數的原因。

    而這種轉換,就是我們題目中所說到這篇文章的主角 —— SAM 轉換 (Single Abstract Method Conversions)。

    3. 什么是 SAM 轉換?

    好吧,說了這么多,終于到正題了。

    SAM 轉換,即 Single Abstract Method Conversions,就是對于只有單個非默認抽象方法接口的轉換 —— 對于符合這個條件的接口(稱之為 SAM Type ),在 Kotlin 中可以直接用 Lambda 來表示 —— 當然前提是 Lambda 的所表示函數類型能夠跟接口的中方法相匹配。

    而 OnClickListener 在 java 中的定義是這樣的:

  • // 代碼四:OnClickListener 接口在 java 中的定義public interface OnClickListener {    void onClick(View v);


  • }1234


  • —— 恰好它就是一個符合條件的 SAM Type,onClick 函數的類型即是 (View) -> Unit。所以,在 Kotlin 中,能夠用 lambda 表達式 { println("click")} 來代替 OnClickListener 作為 setOnClickListener 函數的參數。

    4. SAM 轉換的歧義消除

    SAM 轉換的存在,使得我們在 Kotlin 中調用 java 的時候能夠更得心應手了,它在大部分的時間都能工作的很好。

    當然,也偶爾會有例外,比如,考慮下面的這段代碼:

  • // 代碼五public class TestSAM {


  •    SamType1 sam1,;


  •    SamType2 sam2,;    public void setSam(SamType1 sam1) {        this.sam1 = sam1;


  •    }    public void setSam(SamType2 sam2) {        this.sam2 = sam2;


  •    }    public interface SamType1 {        void doSomething(int value);


  •    }    public interface SamType2 {        void doSomething2(int value);


  •    }


  • }123456789101112131415161718


  • —— TestSAM 有兩個重載的 setSam 方法,—— 并且它們的參數( SamType1、SamType2 )都是 SAM Type 的接口?!?并且 SamType1 跟 SamType2 的唯一抽象方法的函數類型都是 (Int) -> Unit 。

    o(╯□╰)o

    這種情況比較吊軌,但是還有有可能會出現的。這時候,如果在 Kotlin 中直接使用代碼一類似的方式,就會報錯了:

  • // 代碼六:kotlin中調用,這段代碼是編譯不過的TestSAM().setSam {


  •    println("dodo")  


  • }1234


  • 會提示這里歧義,編譯器不知道這個 Lambda 代表是 SamType1 跟 SamType2 中的哪一個接口。

    解決的辦法就是手動標明 Lambda 需要代替的接口類型,有兩種方式可以來標明:

  • // 代碼七: 歧義消除// 方式一TestSAM().setSam (SamType1 { println("dodo")  })


  • // 方式二TestSAM().setSam ({ println("dodo") } as SamType1)12345


  • 當然,也還有一種方法是不再使用 SAM 轉換的機制,而是直接使用一個 SamType1 的實例作為參數:

  • // 代碼八: 使用一個實現接口的匿名類作為參數TestSAM().setSam(object : TestSAM.SamType1 {    override fun doSomething(value: Int) {


  •        println("dodo")


  •    }


  • })123456


  • 這種方法當然也是可以的,只是跟 lambda 相比起來,就顯得不那么優雅了(優雅很重要?。。。?。

    5. SAM 轉換的限制

    SAM 轉換的限制主要有兩點 :

    5.1 只支持 java

    即只適用與 Kotlin 中對 java 的調用,而不支持對 Kotlin 的調用

    官方的解釋是 Kotlin 本身已經有了函數類型和高階函數等支持,所以不需要了再去轉換了。

    如果你想使用類似的需要用 lambda 做參數的操作,應該自己去定義需要指定函數類型的高階函數。

    5.2 只支持接口,不支持抽象類。

    這個官方沒有多做解釋。

    我想大概是為了避免混亂吧,畢竟如果支持抽象類的話,需要做強轉的地方就太多了。而且抽象類本身是允許有很多邏輯代碼在內部的,直接簡寫成一個 Lambda 的話,如果出了問題去定位錯誤的難度也加大了很多。

    6. 總結

    OK,講完了??偨Y起來就是 SAM 轉換就是 kotlin 在調用 java 代碼時能使用 Lambda 的原因。了解了其原理,能夠讓我們在寫代碼更自如,在偶爾出問題的時候也能更好更快地解決。



查看完整回答
反對 回復 2019-03-19
?
慕神8447489

TA貢獻1780條經驗 獲得超1個贊

kotlin java都是jvm上的語言,對于jvm來說,沒有java,沒有kotlin,這些語言,只有一個特定格式的 class文件,只要這個class文件的格式滿足jvm的格式標準,jvm就可以運行這份class文件,java 跟kotlin的語法不一樣,但是他們有不同的編譯器,不同的編譯器,會把對應的源文件,轉換成標準的class格式,從我們看源代碼的方式來看,代碼是不一樣的,但是從jvm運行的角度來看,都是標準的class文件格式,kotlin使用Lambda的語法,經過編譯器的時候,會把這種語法轉換成標準的class格式就可以了,

查看完整回答
反對 回復 2019-03-19
  • 2 回答
  • 0 關注
  • 982 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號