Kotlin擴展和對應(yīng)的java代碼解析

Kotlin 可以對一個類的屬性和方法進行擴展碰辅,且不需要繼承或使用 Decorator 模式森逮。

擴展是一種靜態(tài)行為,對被擴展的類代碼本身不會造成任何影響顺少。

class Student {

    fun showName(name: String) {
        System.out.println(name)
    }
}

//擴展student的類
fun Student.showOtherName(name: String) {
    System.out.println("我是student的擴展函數(shù)$name")
}

fun main(args: Array<String>) {
    val student = Student()
    student.showName("my name is moon")
    student.showOtherName("my name is sun")//使用擴展函數(shù)

}

我們使用kotlin編寫的擴展函數(shù)例子代碼如上面所示潭兽,下面我們看一下對應(yīng)的java代碼是什么琳骡?

如果我們的開發(fā)工具配置好了kotlin的支持,在android studio中我們可以使用菜單欄中的tools-kotlin--show kotlin bytecode的選項執(zhí)行讼溺;

執(zhí)行完成展示的是對應(yīng)的字節(jié)碼文件

// ================com/example/kotlin/Student.class =================
// class version 50.0 (50)
// access flags 0x31
public final class com/example/kotlin/Student {


  // access flags 0x11
  public final showName(Ljava/lang/String;)V
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1//從局部變量1中裝載引用類型值入棧。
    LDC "name"http://常量池中的常量值(int, float, string reference, object reference)入棧最易。

    //調(diào)用靜態(tài)方法,包含兩個操作數(shù)
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 9 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;//獲取靜態(tài)字段的值
    ALOAD 1//從局部變量1中裝載引用類型值入棧
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V  //運行時方法綁定調(diào)用方法
   L2
    LINENUMBER 10 L2
    RETURN
   L3
    LOCALVARIABLE this Lcom/example/kotlin/Student; L0 L3 0
    LOCALVARIABLE name Ljava/lang/String; L0 L3 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 6 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V  //編譯時方法綁定調(diào)用方法
    RETURN //void函數(shù)返回怒坯。另外還有六種操作碼主要對應(yīng)不同類型的基本數(shù)據(jù)類型,ireturn  返回int類型的值藻懒;
   L1
    LOCALVARIABLE this Lcom/example/kotlin/Student; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  @Lkotlin/Metadata;(mv={1, 1, 15}, bv={1, 0, 3}, k=1, d1={"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002J\u000e\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u0006\u00a8\u0006\u0007"}, d2={"Lcom/example/kotlin/Student;", "", "()V", "showName", "", "name", "", "app_debug"})
  // compiled from: Student.kt
}


// ================com/example/kotlin/StudentKt.class =================
// class version 50.0 (50)
// access flags 0x31
public final class com/example/kotlin/StudentKt {


  // access flags 0x19
  public final static showOtherName(Lcom/example/kotlin/Student;Ljava/lang/String;)V
    // annotable parameter count: 2 (visible)
    // annotable parameter count: 2 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1
   L0
    ALOAD 0
    LDC "$this$showOtherName"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
    ALOAD 1
    LDC "name"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 15 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder  //創(chuàng)建新的對象實例剔猿。
    DUP //復制棧頂一個字長的數(shù)據(jù),將復制后的數(shù)據(jù)壓棧嬉荆。
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "\u6211\u662fstudent\u7684\u6269\u5c55\u51fd\u6570"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 16 L2
    RETURN
   L3
    LOCALVARIABLE $this$showOtherName Lcom/example/kotlin/Student; L0 L3 0
    LOCALVARIABLE name Ljava/lang/String; L0 L3 1
    MAXSTACK = 3
    MAXLOCALS = 2

  // access flags 0x19
  public final static main([Ljava/lang/String;)V
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "args"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 19 L1
    NEW com/example/kotlin/Student
    DUP
    INVOKESPECIAL com/example/kotlin/Student.<init> ()V
    ASTORE 1
   L2
    LINENUMBER 20 L2
    ALOAD 1
    LDC "my name is moon"
    INVOKEVIRTUAL com/example/kotlin/Student.showName (Ljava/lang/String;)V
   L3
    LINENUMBER 21 L3
    ALOAD 1
    LDC "my name is sun"
    INVOKESTATIC com/example/kotlin/StudentKt.showOtherName (Lcom/example/kotlin/Student;Ljava/lang/String;)V
   L4
    LINENUMBER 23 L4
    RETURN
   L5
    LOCALVARIABLE student Lcom/example/kotlin/Student; L2 L5 1
    LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
    MAXSTACK = 2
    MAXLOCALS = 2

  @Lkotlin/Metadata;(mv={1, 1, 15}, bv={1, 0, 3}, k=2, d1={"\u0000\u001c\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\u0008\u0002\n\u0002\u0018\u0002\n\u0002\u0008\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\u000c\u0010\u0002\u001a\u0008\u0012\u0004\u0012\u00020\u00040\u0003\u00a2\u0006\u0002\u0010\u0005\u001a\u0012\u0010\u0006\u001a\u00020\u0001*\u00020\u00072\u0006\u0010\u0008\u001a\u00020\u0004\u00a8\u0006\u0009"}, d2={"main", "", "args", "", "", "([Ljava/lang/String;)V", "showOtherName", "Lcom/example/kotlin/Student;", "name", "app_debug"})
  // compiled from: Student.kt
}


// ================META-INF/app_debug.kotlin_module =================
????
?
?com.example.kotlin?    StudentKt

字節(jié)碼的操作碼非常多归敬,這里給出一個鏈接,感興趣的同學可以前去查看;
java字節(jié)碼指令收集大全

字節(jié)碼不容易查看汪茧,我們可以選擇上面的Decompile進行反編譯椅亚;
在反編譯的代碼中我們可以很容易的看到擴展函數(shù)其實就是一個靜態(tài)方法,只是第一個函數(shù)參數(shù)舱污,是我們對應(yīng)的擴展的類對象呀舔;

我們通過student.showOtherName()方法調(diào)用的時候?qū)嶋H上就是調(diào)用showOtherName的靜態(tài)方法,將我們stuent的對象作為第一個參數(shù)傳遞給這個靜態(tài)方法扩灯,進而實現(xiàn)看似擴展了類的方法的效果媚赖;

我們發(fā)現(xiàn)反編譯的文件有很多特殊的標記,下面我們簡單的介紹一下這些標記都是什么作用和含義珠插;

// Student.java
package com.example.kotlin;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u000e\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u0006¨\u0006\u0007"},
   d2 = {"Lcom/example/kotlin/Student;", "", "()V", "showName", "", "name", "", "app_debug"}
)
public final class Student {
//類的方法
   public final void showName(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      System.out.println(name);
   }
}
// StudentKt.java
package com.example.kotlin;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 2,
   d1 = {"\u0000\u001c\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a\u0012\u0010\u0006\u001a\u00020\u0001*\u00020\u00072\u0006\u0010\b\u001a\u00020\u0004¨\u0006\t"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "showOtherName", "Lcom/example/kotlin/Student;", "name", "app_debug"}
)
public final class StudentKt {
//類的擴展方法
   public static final void showOtherName(@NotNull Student $this$showOtherName, @NotNull String name) {
      Intrinsics.checkParameterIsNotNull($this$showOtherName, "$this$showOtherName");
      Intrinsics.checkParameterIsNotNull(name, "name");
      System.out.println("我是student的擴展函數(shù)" + name);
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      Student student = new Student();
      student.showName("my name is moon");
      showOtherName(student, "my name is sun");
   }
}

@Metadata

kotlin中的@Metadata注解是一個很特殊的注解惧磺,它記錄了Kotlin代碼中的一些信息,比如 class 的可見性捻撑,function 的返回值磨隘,參數(shù)類型,property 的 lateinit布讹,nullable 的屬性琳拭,typealias類型別名聲明等。我們都知道Kotlin代碼最終都要轉(zhuǎn)化成Java的字節(jié)碼的描验,然后運行JVM上白嘁。但是Kotlin代碼和Java代碼差別還是很大的,一些Kotlin特殊語言特性是獨有的(比如lateinit, nullable, typealias)膘流,所以需要記錄一些信息來標識Kotlin中的一些特殊語法信息絮缅。最終這些信息都是有kotlinc編譯器生成,并以注解的形式存在于字節(jié)碼文件中呼股。

@Metadata(
   mv = {1, 1, 15},//metadata的版本號1耕魄,1,15
   bv = {1, 0, 3},//字節(jié)碼的版本號
   k = 1,//對應(yīng)一個class表示這個是kotlin的一個類或者接口
   d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u000e\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u0006¨\u0006\u0007"},
   d2 = {"Lcom/example/kotlin/Student;", "", "()V", "showName", "", "name", "", "app_debug"}
)

@Metadata注解會一直保存在class字節(jié)碼中彭谁,也就是這個注解是一個運行時的注解吸奴,在RunTime的時候會一直保留,那么可以通過反射可以拿到缠局,并且這個@Metadata注解是Kotlin獨有的则奥,也就是Java是不會生成這樣的注解存在于.class文件中,也就是從另一方面可以通過反射可以得知這個類是不是Kotlin的class.

我們也可以在android studio中直接查看此注解的源碼狭园,英文好的同學可以直接理解對應(yīng)的符號對應(yīng)的含義读处;

@Metadata注解中的mv,bv,K,d1,d2..都是簡寫,主要是為了減小class文件的大小唱矛,畢竟這個注解出現(xiàn)的地方是非常多的罚舱;但是日常的開發(fā)中井辜,如果我們自己定義的注解盡量使用能夠見名之意的完整英文單詞;

  • k 對應(yīng)kind 表示當前metadata注解編碼種類管闷,取值一下集中
    • 1: class,表示這個kotlin文件是一個類或者接口
    • 2: file,表示這個kotin文件是一個.kt結(jié)尾的文件
    • 3: Synthetic class,表示這個kotlin文件是一個合成類
    • 4(Multi-file class facade)
    • 5(Multi-file class part)
  • mv metadata version是metadata版本號
  • bv 字節(jié)碼版本號
  • d1 data1 記錄kotlin的語法信息粥脚,數(shù)組類型
  • d2 data2 記錄kotlin的語法信息,數(shù)組類型

data2 相對于data1作為一些補充信息的標記渐北,使用的是未加密的文本字符串來展示阿逃,所以看到d1都是一些看不懂的內(nèi)容,d2我們比較好辨別赃蛛,主要為了讓這些標記內(nèi)容在常量池中被重用恃锉,

kotlin擴展空對象

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空檢測之后,“this”會自動轉(zhuǎn)換為非空類型呕臂,所以下面的 toString()
    // 解析為 Any 類的成員函數(shù)
    return toString()
}
fun main(arg:Array<String>){
    var t = null
    

kotlin擴展屬性

val <T> List<T>.lastIndex: Int
    get() = size - 1

kotlin擴展伴生對象

class MyClass {
    companion object { }  // 將被稱為 "Companion"
}

fun MyClass.Companion.foo() {
    println("伴隨對象的擴展函數(shù)")
}
//擴展伴生對象屬性
val MyClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    MyClass.foo()
}

另外kotlin的擴展還有一些其他的內(nèi)容破托,有興趣的可以查閱官方文檔;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歧蒋,一起剝皮案震驚了整個濱河市土砂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谜洽,老刑警劉巖萝映,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阐虚,居然都是意外死亡序臂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門实束,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奥秆,“玉大人,你說我怎么就攤上這事咸灿」苟” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵避矢,是天一觀的道長悼瘾。 經(jīng)常有香客問我,道長审胸,這世上最難降的妖魔是什么分尸? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮歹嘹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘孔庭。我一直安慰自己尺上,他們只是感情好材蛛,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著怎抛,像睡著了一般卑吭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上马绝,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天豆赏,我揣著相機與錄音,去河邊找鬼富稻。 笑死掷邦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的椭赋。 我是一名探鬼主播抚岗,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哪怔!你這毒婦竟也來了宣蔚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤认境,失蹤者是張志新(化名)和其女友劉穎胚委,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叉信,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡亩冬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茉盏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鉴未。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鸠姨,靈堂內(nèi)的尸體忽然破棺而出铜秆,到底是詐尸還是另有隱情,我是刑警寧澤讶迁,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布连茧,位于F島的核電站糠聪,受9級特大地震影響冤吨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜英上,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一祟峦、第九天 我趴在偏房一處隱蔽的房頂上張望罚斗。 院中可真熱鬧,春花似錦针姿、人聲如沸袱吆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榕暇。三九已至蓬衡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間彤枢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工堂污, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盟猖。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓讨衣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親式镐。 傳聞我的和親對象是個殘疾皇子反镇,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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