Kotlin扩展和对应的java代码解析

【Kotlin扩展和对应的java代码解析】Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

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) { val student = Student() student.showName("my name is moon") student.showOtherName("my name is sun")//使用扩展函数}

我们使用kotlin编写的扩展函数例子代码如上面所示,下面我们看一下对应的java代码是什么?
如果我们的开发工具配置好了kotlin的支持,在android studio中我们可以使用菜单栏中的tools-kotlin--show kotlin bytecode的选项执行;
执行完成展示的是对应的字节码文件
// ================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 ()V L0 LINENUMBER 6 L0 ALOAD 0 INVOKESPECIAL java/lang/Object. ()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. ()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. ()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字节码指令收集大全
字节码不容易查看,我们可以选择上面的Decompile进行反编译;
在反编译的代码中我们可以很容易的看到扩展函数其实就是一个静态方法,只是第一个函数参数,是我们对应的扩展的类对象;
我们通过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"); } }

@Metadata 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文件的大小,毕竟这个注解出现的地方是非常多的;但是日常的开发中,如果我们自己定义的注解尽量使用能够见名之意的完整英文单词;
  • k 对应kind 表示当前metadata注解编码种类,取值一下集中
    • 1: class,表示这个kotlin文件是一个类或者接口
    • 2: file,表示这个kotin文件是一个.kt结尾的文件
    • 3: Synthetic class,表示这个kotlin文件是一个合成类
    • 4(Multi-file class facade)
    • 5(Multi-file class part)
  • mv metadata version是metadata版本号
  • bv 字节码版本号
  • d1 data1 记录kotlin的语法信息,数组类型
  • d2 data2 记录kotlin的语法信息,数组类型
data2 相对于data1作为一些补充信息的标记,使用的是未加密的文本字符串来展示,所以看到d1都是一些看不懂的内容,d2我们比较好辨别,主要为了让这些标记内容在常量池中被重用,
kotlin扩展空对象
fun Any?.toString(): String { if (this == null) return "null" // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString() // 解析为 Any 类的成员函数 return toString() } fun main(arg:Array){ var t = null

kotlin扩展属性
val List.lastIndex: Int get() = size - 1

kotlin扩展伴生对象
class MyClass { companion object { }// 将被称为 "Companion" }fun MyClass.Companion.foo() { println("伴随对象的扩展函数") } //扩展伴生对象属性 val MyClass.Companion.no: Int get() = 10fun main(args: Array) { println("no:${MyClass.no}") MyClass.foo() }

另外kotlin的扩展还有一些其他的内容,有兴趣的可以查阅官方文档;

    推荐阅读