您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關(guān)Kotlin中擴展函數(shù)的實現(xiàn)機制是什么,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
// JAVA,20多行代碼,充斥著findViewById,類型轉(zhuǎn)換,匿名內(nèi)部類這樣的無意義代碼 public class MainJavaActivity extends Activity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView label = (TextView) findViewById(R.id.label); Button btn = (Button) findViewById(R.id.btn); label.setText("hello"); label.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("Glen","onClick TextView"); } }); btn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { Log.d("Glen","onClick Button"); } }); } }
再來看Kotlin
// Kotlin,沒有了冗余的findViewById,我們可以直接對資源id進行操作,也不需要匿名內(nèi)部類的聲明,更關(guān)注函數(shù)的實現(xiàn)本身,拋棄了復(fù)雜的格式 class MainKotlinActivity:Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) R.id.label.setText("hello") R.id.label.onClick { Log.d("Glen","onClick TextView") } R.id.btn.onClick { Log.d("Glen","onClick Button") } } }
實現(xiàn)這些需要借助Kotlin的擴展函數(shù)與高階函數(shù),本文主要介紹一下擴展函數(shù)。
什么是擴展函數(shù)?
擴展函數(shù)數(shù)是指在一個類上增加一種新的行為,甚至我們沒有這個類代碼的訪問權(quán)限。這是一個在缺少有用函數(shù)的類上擴展的方法,Kotlin能夠為我們做到那些令人關(guān)注的事情,而這些Java做不到。
在Java中,通常會實現(xiàn)很多帶有static方法的工具類,而Kotlin中擴展函數(shù)的一個優(yōu)勢是我們不需要在調(diào)用方法的時候把整個對象當(dāng)作參數(shù)傳入,它表現(xiàn)得就像是屬于這個類的一樣,而且我們可以使用this關(guān)鍵字和調(diào)用所有public方法。
1. Kotlin 擴展函數(shù)與擴展屬性(Kotlin Extensions)
Kotlin 能夠擴展一個類的新功能而無需繼承該類,或者對任意的類使用像“裝飾者(Decorator)”這樣的設(shè)計模式。這些都是通過叫做“擴展(extensions)”的特殊聲明實現(xiàn)的。Kotlin擴展聲明既支持擴展函數(shù)也支持擴展屬性,本文主要討論擴展函數(shù),至于擴展屬性實現(xiàn)的機制類似。
擴展函數(shù)的聲明非常簡單,他的關(guān)鍵字是.,此外我們需要一個“接受者類型(recievier type)”來作為他的前綴。以類MutableList<Int>為例,現(xiàn)在為它擴展一個swap方法,如下:
fun MutableList<Int>.swap(index1:Int,index2:Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }
MutableList<T>是kotlin提供的基礎(chǔ)庫collection中的List容器類,這里在聲明里作為“接受者類型”,.作為聲明關(guān)鍵字,swap是擴展函數(shù)名,其余和Kotlin聲明一個普通函數(shù)并無區(qū)別。
額外提一句,Kotlin的this語法要比JAVA更靈活,這里擴展函數(shù)體里的this代表的是接受者類型對象。
如果我們想要調(diào)用這個擴展函數(shù),可以這樣:
fun use(){ val list = mutableListOf(1,2,3) list.swap(1,2) }
2. Kotlin擴展函數(shù)是怎么實現(xiàn)的
擴展函數(shù)的調(diào)用看起來就像是原生方法一樣自然,使用起來也非常順手,但是這樣的方法會不會帶來性能方面的掣肘呢?有必要探究一下Kotlin是如何實現(xiàn)擴展函數(shù)的,直接分析Kotlin源碼難度還是挺大,還好Android Studio提供了一些工具,我們可以通過Kotlin ByteCode指令,查看Kotlin語言轉(zhuǎn)換的字節(jié)碼文件,仍以MutableList<Int>,swap為例,轉(zhuǎn)換為字節(jié)碼之后的文件如下:
// ================com/example/glensun/demo/extension/MutableListDemoKt.class ================= // class version 50.0 (50) // access flags 0x31 public final class com/example/glensun/demo/extension/MutableListDemoKt { // access flags 0x19 // signature (Ljava/util/List<Ljava/lang/Integer;>;II)V // declaration: void swap(java.util.List<java.lang.Integer>, int, int) public final static swap(Ljava/util/List;II)V @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0 L0 ALOAD 0 LDC "$receiver" INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V L1 LINENUMBER 8 L1 ALOAD 0 ILOAD 1 INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; CHECKCAST java/lang/Number INVOKEVIRTUAL java/lang/Number.intValue ()I ISTORE 3 L2 LINENUMBER 9 L2 ALOAD 0 ILOAD 1 ALOAD 0 ILOAD 2 INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; INVOKEINTERFACE java/util/List.set (ILjava/lang/Object;)Ljava/lang/Object; POP L3 LINENUMBER 10 L3 ALOAD 0 ILOAD 2 ILOAD 3 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; INVOKEINTERFACE java/util/List.set (ILjava/lang/Object;)Ljava/lang/Object; POP L4 LINENUMBER 11 L4 RETURN L5 LOCALVARIABLE tmp I L2 L5 3 LOCALVARIABLE $receiver Ljava/util/List; L0 L5 0 LOCALVARIABLE index1 I L0 L5 1 LOCALVARIABLE index2 I L0 L5 2 MAXSTACK = 4 MAXLOCALS = 4 @Lkotlin/Metadata;(mv={1, 1, 7}, bv={1, 0, 2}, k=2, d1={"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\u0008\n\u0002\u0008\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\u0008\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003\u00a8\u0006\u0006"}, d2={"swap", "", "", "", "index1", "index2", "production sources for module app"}) // compiled from: MutableListDemo.kt } // ================META-INF/production sources for module app.kotlin_module =================
這里的字節(jié)碼已經(jīng)相當(dāng)直觀,更令人驚喜的是Android Studio還具備將字節(jié)碼轉(zhuǎn)為JAVA文件的能力,點擊上面的Decompile按鈕,可以得到如下JAVA代碼:
import java.util.List; import kotlin.Metadata; import kotlin.jvm.internal.Intrinsics; import org.jetbrains.annotations.NotNull; @Metadata( mv = {1, 1, 7}, bv = {1, 0, 2}, k = 2, d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003¨\u0006\u0006"}, d2 = {"swap", "", "", "", "index1", "index2", "production sources for module app"} ) public final class MutableListDemoKt { public static final void swap(@NotNull List $receiver, int index1, int index2) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); int tmp = ((Number)$receiver.get(index1)).intValue(); $receiver.set(index1, $receiver.get(index2)); $receiver.set(index2, Integer.valueOf(tmp)); } }
從得到的JAVA文件分析,擴展函數(shù)的實現(xiàn)非常簡單,它沒有修改接受者類型的成員,僅僅是通過靜態(tài)方法來實現(xiàn)的。這樣,我們雖然不必擔(dān)心擴展函數(shù)會帶來額外的性能消耗,但是它也不會帶來性能上的優(yōu)化。
3.更復(fù)雜的情況
下面來討論一些更特殊的情況。
3.1 當(dāng)發(fā)生繼承時,擴展函數(shù)由于本質(zhì)上是靜態(tài)方法,它會嚴格按照參數(shù)類型去執(zhí)行調(diào)用,而不會去優(yōu)先執(zhí)行或者主動執(zhí)行父類的方法,如下的例子所示:
open class A class B:A() fun A.foo() = "a" fun B.foo() = "b" fun printFoo(a:A){ println(a.foo()) } println(B())
上述例子的輸出結(jié)果是a,因為擴展函數(shù)的入?yún)㈩愋褪茿,他將會嚴格按照入?yún)㈩愋蛨?zhí)行函數(shù)調(diào)用。
3.2 如果擴展函數(shù)和現(xiàn)有的類成員發(fā)生沖突,kotlin將會默認使用類成員,這一步選擇是在編譯期處理的,生成的字節(jié)碼是將會是調(diào)用類成員的方法,如下例子:
class C{ fun foo() {println("Member")} } fun C.foo() {println("Extension")} println(C().foo())
上述的例子將會輸出Member。Kotlin不允許擴展一個已有的成員,原因也很好理解,我們不希望擴展函數(shù)成為調(diào)用三方sdk的漏洞,不過如果你試圖使用重載的方式創(chuàng)建擴展函數(shù),這樣是可行的。
3.3 Kotlin嚴格區(qū)分了可能為空和不為空的入?yún)㈩愋?,同樣也?yīng)用在擴展函數(shù)的中,為了聲明一個可能為空的接受者類型,可以參考如下例子:
fun <T> MutableList<T>?.swap(index1:Int,index2:Int){ if(this == null){ println(null) return } val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }
3.4 我們有時候還希望能夠添加類似JAVA的“靜態(tài)函數(shù)”的擴展函數(shù),這時需要借助“伴隨對象(Companion Object)”來實現(xiàn),如下這個例子:
class D{ companion object{ val m = 1 } } fun D.Companion.foo(){ println("$m in extension") } D.foo()
上面的例子會輸出1 in extension,注意這里調(diào)用foo這個擴展函數(shù)時,并不需要類D的實例,類似于JAVA的靜態(tài)方法。
3.5 如果留意前面的例子,我們會發(fā)現(xiàn)kotlin的this語法和JAVA不同,使用范圍更靈活,僅以擴展函數(shù)為例,當(dāng)在擴展函數(shù)里調(diào)用this時,指代的是接受者類型的實例,那么如果這個擴展函數(shù)聲明在一個類內(nèi)部,我們?nèi)绾瓮ㄟ^this獲取到類的實例呢?可以參考下面的例子:
class E{ fun foo(){ println("foo in Class E") } } class F{ fun foo(){ println("foo in Class F") } fun E.foo2(){ this.foo() this@F.foo() } } E().foo2()
這里使用了kotlin的this指定語法,關(guān)鍵字是@,后接指定的類型,上述例子的輸出結(jié)果是
foo in Class E foo in Class F
4. 擴展函數(shù)的作用域
一般來說,我們習(xí)慣將擴展函數(shù)直接定義在包內(nèi),例如:
package com.example.extension fun MutableList<Int>.swap(index1:Int,index2:Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }
這樣,在同一個包內(nèi)可以直接調(diào)用改擴展函數(shù),如果我們需要跨包調(diào)用擴展函數(shù),我們需要通過import來指明,以上述的例子為例,可以通過import com.example.extension.swap來指定這個擴展函數(shù),也可以通過import com.example.extension.*表示引入該包內(nèi)的所有擴展函數(shù)。得益于Android Studio具備的自動聯(lián)想能力,通常不需要我們主動輸入import指令。
有時候,我們也會把擴展函數(shù)定義在類的內(nèi)部,例如:
class G { fun Int.foo(){ println("foo in Class G") } }
這里的Int.foo()是一個定義在類G內(nèi)部的擴展函數(shù),在這個擴展函數(shù)里,我們直接使用Int類型作為接受者類型,因為我們將擴展函數(shù)定義在了類的內(nèi)部,即使我們設(shè)置訪問權(quán)限為public,它也只能在該類或者該類的子類中被訪問,如果我們設(shè)置訪問權(quán)限為private,那么在子類中也不能訪問這個擴展函數(shù)。
5. 擴展函數(shù)的實際應(yīng)用
5.1 Utils工具類
在JAVA中,我們習(xí)慣將工具類命名成*Utils,例如FileUtils,StringUtils等等,著名的java.util.Collections也是這么實現(xiàn)的。調(diào)用這些方法的時候,總覺得這些類名礙手礙腳的,例如這樣:
// Java Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list)); Collections.max(list));
通過靜態(tài)引用,能讓情況看起來好一點,例如這樣:
// Java swap(list, binarySearch(list, max(otherList)), max(list));
但是這樣既沒有IDE的自動聯(lián)想提示,方法調(diào)用的主體也顯得不明確。如果能做成下面這樣就好了:
// Java list.swap(list.binarySearch(otherList.max()), list.max());
但是list是JAVA默認的基礎(chǔ)類,在JAVA語言里,如果不使用繼承,肯定是沒法做到這樣的,而在Kotlin中就可以借助擴展函數(shù)來實現(xiàn)啦。
5.2 Android View 膠水代碼
回到最開始的例子,對于Android開發(fā)來說,對findViewById()這個方法一定不會陌生,為了獲取一個View對象,我們總得先調(diào)用findViewById()然后再執(zhí)行類型轉(zhuǎn)換,這樣無意義的膠水代碼讓Activity或者Fragment顯得臃腫無比,例如:
// JAVA public class MainJavaActivity extends Activity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView label = (TextView) findViewById(R.id.label); Button btn = (Button) findViewById(R.id.btn); label.setText("hello"); label.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("Glen","onClick TextView"); } }); btn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { Log.d("Glen","onClick Button"); } }); } }
我們考慮利用擴展函數(shù)結(jié)合泛型,避免頻繁的類型轉(zhuǎn)換,擴展函數(shù)定義如下:
//kotlin fun <T : View> Activity.find(@IdRes id: Int): T { return findViewById(id) as T }
調(diào)用的時候,如下:
// Kotlin ... TextView label = find(R.id.label); Button btn = find(R.id.btn); ...
只是我們還是需要獲取到label,btn,這樣無意義的中間變量,如果在Int類上擴展,可以直接對R.id.*操作,這樣更直接,再結(jié)合高階函數(shù),函數(shù)定義如下:
//Kotlin fun Int.setText(str:String){ val label = find<TextView>(this).apply { text = str } } fun Int.onClick(click: ()->Unit){ val tmp = find<View>(this).apply { setOnClickListener{ click() } } }
我們就可以這樣調(diào)用:
//Kotlin R.id.label.setText("hello") R.id.label.onClick { Log.d("Glen","onClick TextView") } R.id.btn.onClick { Log.d("Glen","onClick Button") }
通常這些擴展函數(shù)可以放到基類中,根據(jù)擴展函數(shù)的作用域知識,我們可以在所有子類中都調(diào)用到這些方法,所以kotlin的Activity可以寫成:
// Kotlin class MainKotlinActivity:KotlinBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) R.id.label.setText("hello") R.id.label.onClick { Log.d("Glen","onClick TextView") } R.id.btn.onClick { Log.d("Glen","onClick Button") } } }
關(guān)于Kotlin中擴展函數(shù)的實現(xiàn)機制是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。