Kotlin 能夠對一個類的屬性和方法進行擴展,且不須要繼承或使用 Decorator 模式。html
擴展是一種靜態行爲,對被擴展的類代碼自己不會形成任何影響。java
class Student {
fun showName(name: String) {
System.out.println(name)
}
}
//擴展student的類
fun Student.showOtherName(name: String) {
System.out.println("我是student的擴展函數$name")
}
fun main(args: Array<String>) {
val student = Student()
student.showName("my name is moon")
student.showOtherName("my name is sun")//使用擴展函數
}
複製代碼
咱們使用kotlin編寫的擴展函數例子代碼如上面所示,下面咱們看一下對應的java代碼是什麼?android
若是咱們的開發工具配置好了kotlin的支持,在android studio中咱們可使用菜單欄中的tools-kotlin--show kotlin bytecode的選項執行;數組
執行完成展現的是對應的字節碼文件bash
// ================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"//常量池中的常量值(int, float, string reference, object reference)入棧。
//調用靜態方法,包含兩個操做數
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;//獲取靜態字段的值
ALOAD 1//從局部變量1中裝載引用類型值入棧
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V //運行時方法綁定調用方法
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 //編譯時方法綁定調用方法
RETURN //void函數返回。另外還有六種操做碼主要對應不一樣類型的基本數據類型,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 //建立新的對象實例。
DUP //複製棧頂一個字長的數據,將複製後的數據壓棧。
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
複製代碼
字節碼的操做碼很是多,這裏給出一個連接,感興趣的同窗能夠前去查看; java字節碼指令收集大全app
字節碼不容易查看,咱們能夠選擇上面的Decompile進行反編譯; 在反編譯的代碼中咱們能夠很容易的看到擴展函數其實就是一個靜態方法,只是第一個函數參數,是咱們對應的擴展的類對象;jvm
咱們經過student.showOtherName()方法調用的時候實際上就是調用showOtherName的靜態方法,將咱們stuent的對象做爲第一個參數傳遞給這個靜態方法,進而實現看似擴展了類的方法的效果;函數
咱們發現反編譯的文件有不少特殊的標記,下面咱們簡單的介紹一下這些標記都是什麼做用和含義;工具
// 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的擴展函數" + 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");
}
}
複製代碼
kotlin中的@Metadata註解是一個很特殊的註解,它記錄了Kotlin代碼中的一些信息,好比 class 的可見性,function 的返回值,參數類型,property 的 lateinit,nullable 的屬性,typealias類型別名聲明等。咱們都知道Kotlin代碼最終都要轉化成Java的字節碼的,而後運行JVM上。可是Kotlin代碼和Java代碼差異仍是很大的,一些Kotlin特殊語言特性是獨有的(好比lateinit, nullable, typealias),因此須要記錄一些信息來標識Kotlin中的一些特殊語法信息。最終這些信息都是有kotlinc編譯器生成,並以註解的形式存在於字節碼文件中。開發工具
@Metadata(
mv = {1, 1, 15},//metadata的版本號1,1,15
bv = {1, 0, 3},//字節碼的版本號
k = 1,//對應一個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字節碼中,也就是這個註解是一個運行時的註解,在RunTime的時候會一直保留,那麼能夠經過反射能夠拿到,而且這個@Metadata註解是Kotlin獨有的,也就是Java是不會生成這樣的註解存在於.class文件中,也就是從另外一方面能夠經過反射能夠得知這個類是否是Kotlin的class.
咱們也能夠在android studio中直接查看此註解的源碼,英文好的同窗能夠直接理解對應的符號對應的含義;
@Metadata註解中的mv,bv,K,d1,d2..都是簡寫,主要是爲了減少class文件的大小,畢竟這個註解出現的地方是很是多的;可是平常的開發中,若是咱們本身定義的註解儘可能使用可以見名之意的完整英文單詞;
data2 相對於data1做爲一些補充信息的標記,使用的是未加密的文本字符串來展現,因此看到d1都是一些看不懂的內容,d2咱們比較好辨別,主要爲了讓這些標記內容在常量池中被重用,
fun Any?.toString(): String {
if (this == null) return "null"
// 空檢測以後,「this」會自動轉換爲非空類型,因此下面的 toString()
// 解析爲 Any 類的成員函數
return toString()
}
fun main(arg:Array<String>){
var t = null
複製代碼
val <T> List<T>.lastIndex: Int
get() = size - 1
複製代碼
class MyClass {
companion object { } // 將被稱爲 "Companion"
}
fun MyClass.Companion.foo() {
println("伴隨對象的擴展函數")
}
//擴展伴生對象屬性
val MyClass.Companion.no: Int
get() = 10
fun main(args: Array<String>) {
println("no:${MyClass.no}")
MyClass.foo()
}
複製代碼
另外kotlin的擴展還有一些其餘的內容,有興趣的能夠查閱官方文檔;