Kotlin Extension — Declaring Extensions as Members 由于官网的例子很全面,因此这里直接套用Kotlin官网的例子 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 open class D { } class D1 : D() { } open class C { open fun D.foo() { println("D.foo in C") } open fun D1.foo() { println("D1.foo in C") } fun caller(d: D) { d.foo() // call the extension function } } class C1 : C() { override fun D.foo() { println("D.foo in C1") } override fun D1.foo() { println("D1.foo in C1") } } C().caller(D()) // prints "D.foo in C" C1().caller(D()) // prints "D.foo in C1" - dispatch receiver is resolved virtually C().caller(D1()) // prints "D.foo in C" - extension receiver is resolved statically
例子很简单,无非就是Extension的声明和如何调用Extension方法(注意:这里调用Members’ Extension 通过了实例方法的转接,可以跟Kotlin Extension - Method中提到的无法在.kt文件中从实例或者外界直接拿到Extension方法引用联系起来) 这里Kotlin定义了两个重要的概念:
dispatch receiver : 指的便是例子中执行caller() 方法的C或者C1实例对象extension receiver : 指的便是例子中caller() 方法定义中执行Extension方法foo() 的对象接下来看看三个例子给我们揭示的一些重要现象
1. C().caller(D1()) 很明显,我们这里往caller()方法里面传的是extension receiver 类型是D1 ,但是为何最后调用的是父类D 的Extension方法呢? 通过反编译生成的.class文件我们找到的原因所在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 //C.class ... public final void caller(com.maxtropy.viewtest.D); Code: 0: aload_1 1: ldc #39 // String d 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_0 7: aload_1 8: invokevirtual #41 // Method foo:(Lcom/maxtropy/viewtest/D;)V 11: return ... //.class where C().caller(D1()) was executed ... 32: invokespecial #20 // Method com/maxtropy/viewtest/C."<init>":()V 35: new #29 // class com/maxtropy/viewtest/D1 38: dup 39: invokespecial #30 // Method com/maxtropy/viewtest/D1."<init>":()V 42: checkcast #22 // class com/maxtropy/viewtest/D 45: invokevirtual #27 // Method com/maxtropy/viewtest/C.caller:(Lcom/maxtropy/viewtest/D;)V ...
在caller()的入参中,d的参数类型是D,因此在调用caller()时,入参实例的类型就被上转型成D了。
2. C1().caller(D()) 程序执行的结果很明显的告诉我们这是动态选择的结果,执行的是C1中的Extension方法,而不是父类C中的Extension方法。 哇!在 top-level Extension反编译的例子中很明显的看到Extension方法被编译成了静态方法,这里怎么又跟动态选择有关了呢??? 同样施以反编译的魔法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 //C.class Compiled from "C.kt" public class com.maxtropy.viewtest.C { public void foo(com.maxtropy.viewtest.D); Code: 0: aload_1 1: ldc #9 // String $receiver 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: ldc #17 // String D.foo in C 8: astore_2 9: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream; 12: aload_2 13: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 16: return public void foo(com.maxtropy.viewtest.D1); Code: 0: aload_1 1: ldc #9 // String $receiver 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: ldc #35 // String D1.foo in C 8: astore_2 9: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream; 12: aload_2 13: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 16: return public final void caller(com.maxtropy.viewtest.D); Code: 0: aload_1 1: ldc #39 // String d 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_0 7: aload_1 8: invokevirtual #41 // Method foo:(Lcom/maxtropy/viewtest/D;)V 11: return public com.maxtropy.viewtest.C(); Code: 0: aload_0 1: invokespecial #45 // Method java/lang/Object."<init>":()V 4: return } //C1.class Compiled from "C.kt" public final class com.maxtropy.viewtest.C1 extends com.maxtropy.viewtest.C { public void foo(com.maxtropy.viewtest.D); Code: 0: aload_1 1: ldc #9 // String $receiver 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: ldc #17 // String D.foo in C1 8: astore_2 9: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream; 12: aload_2 13: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 16: return public void foo(com.maxtropy.viewtest.D1); Code: 0: aload_1 1: ldc #9 // String $receiver 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: ldc #35 // String D1.foo in C1 8: astore_2 9: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream; 12: aload_2 13: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 16: return public com.maxtropy.viewtest.C1(); Code: 0: aload_0 1: invokespecial #40 // Method com/maxtropy/viewtest/C."<init>":()V 4: return }
原来声明在Member中的Extension方法全部被编译成了Member所在类型的实例方法! 这实例方法有什么用呢?(再次重复这些实例方法在写.kt时无法通过实例直接拿到引用,也就c.foo()是不能通过编译的) 玄妙便在call()方法的第6行, 第6行的aload_0指的便是this, 也即是caller()被执行时真正的实例对象dispatch receiver 。正是this,让Extension方法具备了动态选择的能力。