背景
看了 让你迷惑的 Kotlin 代码 有感, 自己前些天也遇到了一些令人困惑的 Kotlin 代码/语法糖实现, 在这里分享一下
上代码
下面反编译的代码都有修改, 优化了可读性
代码是一个简单的 类委托 + interface default method
object App {
    @JvmStatic
    fun main(args: Array<String>) {
        val origin = object : Service {
            override fun getName(): String = "origin"
        }
        val proxy = object : Service by origin {
            override fun getName(): String = "proxy"
        }
        proxy.printName()
    }
}
interface Service {
    fun getName(): String
    fun printName() {
        println(getName())
    }
}
复制代码
大家可以先想一下这段代码会输出什么内容
答案是 “origin”, 这里可能会和预期有些不符
但是如果我们给printName() 添加一个 @JvmDefault注释, 答案就会是 “proxy”
interface Service {
    fun getName(): String
    @JvmDefault
    fun printName() {
        println(getName())
    }
}
复制代码
原理
这里涉及到 以下知识点:
- kotlin interface default method 默认实现 和 加了@JvmDefault下的实现
 - 类委托的原理
 
interface default method 的原理
默认情况下
通过反编译得到的Java代码, 我们可以看到生成了一个 Service.DefaultImpls 类, 里面有我们定义的 Kotlin 中的默认方法 printName(Service), 该方法需要传入一个 Service 对象(关键点1).
Service 的子类对默认方法 printName() 的调用, 其实是调用了 Service.DefaultImpls 中的方法, 默认传入了 this(关键点2)
new Service() {
   @NotNull
   public String getName() {
      return "origin";
   }
   public void printName() {
      Service.DefaultImpls.printName(this);
   }
};
public interface Service {
   @NotNull
   String getName();
   void printName();
   public static final class DefaultImpls {
      public static void printName(@NotNull Service $this) {
         String var1 = $this.getName();
         boolean var2 = false;
         System.out.println(var1);
      }
   }
}
复制代码
添加 @JvmDefault 注解后
需要添加 gradle 配置
compileKotlin {
    kotlinOptions {
        // or freeCompilerArgs = ['-Xjvm-default=compatibility']
        freeCompilerArgs = ['-Xjvm-default=enable']
        jvmTarget = '1.8'
    }
}
复制代码
这里利用了 java 8 引入的 default 关键字, 使用了默认方法, 所以不会再生成其他对象了
new Service() {
   @NotNull
   public String getName() {
      return "origin";
   }
}
public interface Service {
   @NotNull
   String getName();
   @JvmDefault
   default void printName() {
      String var1 = this.getName();
      boolean var2 = false;
      System.out.println(var1);
   }
 }
复制代码
类委托的原理
我们来看反编译之后的代码
      Service proxy = new Service() {
         // $FF: synthetic field
         private final Service delegate = origin;
         @NotNull
         public String getName() {
            return "proxy";
         }
         public void printName() {
            this.delegate.printName();
         }
      };
复制代码
可以看到, 未重写的 printName() 方法, 内部调用了  delegate.printName()(关键点3)
到现在结论就很明显了:
- 
在未使用注解时:
proxy.printName()->origin.printName()->Service.DefaultImpls.printName(origin)->origin.getName()-> 输出 “origin” - 
在使用
@JvmDefault注解时:proxy.printName()->proxy.getName()-> 输出 “proxy” 
如何避免
- 
添加
@JvmDefault注解, 这就不用说了 - 
传入指定对象; 既然
Service.printName()在调用Service.DefaultImpls.printName()时会自动给我们传入当前对象, 我们只要给Service.printName()添加一个Service参数, 传入指定的对象就行了, 代码如下:interface Service { fun getName(): String fun printName(service: Service = this) { println(service.getName()) } } 复制代码不过这种方式的 Java 实现可能会和你想象中的有所不同, 如果有兴趣的话, 可以自己去反编译看看
 


















![[02/27][官改] Simplicity@MIX2 ROM更新-一一网](https://www.proyy.com/wp-content/uploads/2020/02/3168457341.jpg)


![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)