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)容破托,有興趣的可以查閱官方文檔;