huangxiushuo

道阻且长,行则将至

0%

语法糖、脱糖、D8

语法糖

  • 语法糖(Syntactic),也称语法糖衣,指在计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但更方便程序员使用。简而言之,语法糖然程序更加简洁,有更高的可读性。
  • Java中最常用的语法糖主要有泛型变长参数条件编译自动拆装箱内部类等。

解语法糖(脱糖)

语法糖的存在主要是方便开发人员使用,但JVM并不支持这些语法糖。因此这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。

Java匿名内部类和转写成Lambda,字节码上的区别

//Lambda写法
class LambdaTest {
    public static void main(String[] args) {
        Runnable r = () -> {
            System.out.println("hello, lambda");
        };
        r.run();
    }
}  


//匿名内部类写法    
class LambdaTest2 {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("hello lambda");
            }
        };
        r.run();
    }
}  
  • 匿名内部类写法在编译后会而外生成LambdaTest2$1.class类:
{
  lambda.LambdaTest2();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class lambda/LambdaTest2$1
         3: dup
         4: invokespecial #3                  // Method lambda/LambdaTest2$1."<init>":()V
         7: astore_1
         8: aload_1
         9: invokeinterface #4,  1            // InterfaceMethod java/lang/Runnable.run:()V
        14: return
      LineNumberTable:
        line 11: 0
        line 17: 8
        line 18: 14
}  
  • Lambda写法在编译后并没有生成额外的类:
{
  lambda.LambdaTest();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         5: astore_1
         6: aload_1
         7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
        12: return
      LineNumberTable:
        line 10: 0
        line 13: 6
        line 14: 12
}  


invokedynamic执行完之后,存储栈顶值,入栈。然后调用接口Runnablerun()方法。
这里的InvokeDynamic #0后面的#0对应的并不是常量池里的索引,而是一个叫BootstrapMethods的:

0: #20 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #21 ()V
      #22 invokestatic lambda/LambdaTest.lambda$main$0:()V
      #21 ()V

也就是0:后面的内容,可以看出它是通过invokedynamic来调用LambdaMetafactorymetafactory方法。断点可知:

    public static CallSite metafactory(MethodHandles.Lookup caller,     //caller: "lambda.LambdaTest"
                                       String invokedName,      //invokedName: "run"
                                       MethodType invokedType,      //invokedType: "()Runnable"      
                                       MethodType samMethodType,    //samMethodType: "()void"
                                       MethodHandle implMethod,     //implMethod: "MethodHandle()void"
                                       MethodType instantiatedMethodType)   //instantiatedMethodType: "()void"
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }

invokedynamic
JVM字节码指令集一直比较稳定,一直到JAVA7中才增加了一个invokedynamic指令,这是JAVA为了实现动态类型语言支持而做的一种改进。但是在JAVA7中并没有提供直接生成invokedynamic指令的方法,需要借助ASM这种底层字节码工具来产生invokedynamic指令。直到JAVA8的Lambda表达式的出现,invokedynamic指令的生成,在java中才有了直接的生成方式。

invokedynamic指令所指定的bootstrap方法,编译器置入,java7中要自己提供一个这种静态方法,由asm工具写入字节码中,java8中jdk提供了这样的一个启动方法。JVM在类加载解析时,如果是invokedynamic时,每次都会进行重新解析,解析的时候,会首先执行bootstrap方法,LambdaMetafactory.metafactory方法的前三个参数,会在运行时根据访问类和运行期常量池动态传入,而后三个参数,则为bootstrap静态参数列表传入:

  1. 泛型擦除后的方法签名及返回值;
  2. 对应的匿名方法;
  3. 泛型擦除之前的方法签名及返回值

2调用的方法:lambda$main$0:(),而LambdaTest中并没有这个方法,猜测是编译器帮我们生成的,通过javap -p命令:

class lambda.LambdaTest {
  lambda.LambdaTest();
  public static void main(java.lang.String[]);
  private static void lambda$main$0();
}  

可以看到LambdaTest确实多了一个lambda$main$0:()方法。反射调用这个方法:

class LambdaTest {
    public static void main(String[] args) throws Exception{
        Runnable r = () -> {
            System.out.println("hello, lambda");
        };
        r.run();
        LambdaTest.class.getDeclaredMethod("lambda$main$0").invoke(null);
    }
}  

“hello lambda”多打印了一次,查看其字节码:

 private static synthetic lambda$main$0()V
   L0
    LINENUMBER 11 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello, lambda"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 12 L1
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0  

对比LambdaTest2run()方法的字节码:

  public run()V
   L0
    LINENUMBER 14 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello lambda"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 15 L1
    RETURN
   L2
    LOCALVARIABLE this Llambda/LambdaTest2$1; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

完全是一样的,获取静态变量System.out"hello lambda"入栈,然后调用PrintStream.println方法。
那么这个lambda$main$0()是在哪里被调用的?两种方式:

  • 抛异常打印堆栈信息
  • 借鉴Class.forName的做法——通过Reflection的getCallerClass来获取到调用者的Class信息:
public class LambdaTest {
    public static void main(String[] args) throws Exception {
        Runnable r = () -> {
            try {
                Method getCallerClass = Class.forName("sun.reflect.Reflection").getDeclaredMethod("getCallerClass", int.class);
                int index = 0;
                Class<?> callerClass;
                while ((callerClass = (Class<?>) getCallerClass.invoke(null, index)) != null) {
                    System.out.println(callerClass);
                    index++;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
        r.run();
    }
}  

运行后打印可知调用lambda$main$0()方法的是一个叫lambda.LambdaTest$$Lambda$1/1531448569的类
但我们在编译后并没有生成这个类,那可以推测它是JVM在运行时动态生成和加载的Runnable的实现类。打印Runnable实例的类名:

System.out.println(String.format("Runnable Class: %s", r.getClass().getSimpleName()));

打印结果:

hello, lambda
class sun.reflect.Reflection
class lambda.LambdaTest
class lambda.LambdaTest$$Lambda$1/1531448569
class lambda.LambdaTest
Runnable Class: LambdaTest$$Lambda$1/1531448569
  • 结论:Lambda表达式在编译后,其函数体都会放到CallerClass里一个额外生成的方法内(所谓的”匿名”函数),由JVM动态生成的实现类去调用。最终它也是通过内部类的方式实现的,只不过不像直接使用内部类那样在编译后生成额外的类,它把生成实现类这一步放到了运行时,由JVM完成。

Java中的Lambda与Android中的Lambda有什么不同?

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Runnable r = () -> {
            Log.d("MainActivity", "hello lambda");
        };
        r.run();
    }
}

编译后打开dex:

MainActivity
    <linit>()
    void lambda$onCreate$0()
    void onCreate(android.os.Bundle)
    void setContentView(int)
-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA
    <clinit>()
    <init>()
    void run()
    com.hxs.androiddesugardemo.-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA INSTANCE 

发现此时这个类已经存在dex里面了,同时MainActivity也多了一个lambda$onCreate$0方法。查看这个方法的字节码:

.method static synthetic lambda$onCreate$0()V
    .registers 2

    .line 15
    const-string v0, "MainActivity"

    const-string v1, "hello lambda"

    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    .line 16
    new-instance v0, Ljava/lang/RuntimeException;

    invoke-direct {v0}, Ljava/lang/RuntimeException;-><init>()V

    throw v0
.end method
  1. 先注册了2个寄存器;
  2. 把字符串常量 “Application” 赋值给寄存器v0;
  3. 把字符串常量 “hello, lambda” 赋值给寄存器v1;
  4. 调用android.util.Log的静态方法d,把寄存器v0和v1的值传了进去(Log.d(“Application”, “hello, lambda”););
  5. 创建java.lang.RuntimeException对象实例,并赋值到寄存器v0上;
  6. 调用java.lang.RuntimeException的()方法(即无参构造函数),目标对象是寄存器v0储存的值;
  7. 抛出异常,目标对象是寄存器v0储存的值(throw new RuntimeException(););

这里和Java的处理方式是一样的,将Lambda主体移到了一个额外生成的方法里。
查看onCreate()方法:

    method protected onCreate(Landroid/os/Bundle;)V
        .registers 3
        .param p1, "savedInstanceState"    # Landroid/os/Bundle;
    
        .line 12
        invoke-super {p0, p1}, Landroidx/appcompat/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
    
        .line 13
        const v0, 0x7f0b001c
    
        invoke-virtual {p0, v0}, Lcom/hxs/androiddesugardemo/MainActivity;->setContentView(I)V
    
        .line 14
        sget-object v0, Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA;->INSTANCE:Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA;
    
        .line 18
        .local v0, "r":Ljava/lang/Runnable;
        invoke-interface {v0}, Ljava/lang/Runnable;->run()V
    
        .line 19
        return-void
    .end method


第14行,获取那个自动生成的实现类的静态变量==INSTANCE==,它的类型是这个类本身,也就是说INSTANCE是自动生成的实现类的实例,并赋值到寄存器v0上;
第18行,声明局部变量r,类型是java/lang/Runnable,初始值是寄存器**v0的值,然后调用java/lang/Runnable的借口方法run(),目标对象是寄存器v0存储的值。

这里把原来的【创建对象实例】改成了【获取静态字段】。其实这只是一个优化,因为lambda函数体没有依赖CallerClass的变量或方法,完全可以作为一个静态变量存在,这样能避免反复创建对象

查看这个实现类的字节码:

    .class public final synthetic Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA;
    .super Ljava/lang/Object;
    .source "lambda"
    
    # interfaces
    .implements Ljava/lang/Runnable;


# static fields
.field public static final synthetic INSTANCE:Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA;


# direct methods
.method static synthetic constructor ()V
.registers 1

        new-instance v0, Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA;
    
        invoke-direct {v0}, Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA;-><init>()V
    
        sput-object v0, Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA;->INSTANCE:Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$NKcUHI3kbmdlSYn5oJ5mHUINomA;
    
        return-void
    .end method
    
    .method private synthetic constructor <init>()V
        .registers 1
    
        invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    
        return-void
    .end method
    
    # virtual methods
    .method public final run()V
        .registers 1
    
        invoke-static {}, Lcom/hxs/androiddesugardemo/MainActivity;->lambda$onCreate$0()V
    
        return-void
    .end method

在类被加载的时候,就创建了他自己的对象实例并赋值给静态变量INSTANCE。实现的run()方法,里面也是直接调用MainActivity的静态方法lambda$onCreate$0

如果Lambda表达式调用了CallerClass的变量呢?
将代码稍微改造下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String tip = "hello lambda";
        Runnable r = () -> {
            Log.d("MainActivity", tip);
            throw new RuntimeException();
        };
        r.run();
    }
}

查看**onCreate()**方法的字节码:

.method protected onCreate(Landroid/os/Bundle;)V
    .registers 4
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;

    .line 12
    invoke-super {p0, p1}, Landroidx/appcompat/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V

    .line 13
    const v0, 0x7f0b001c

    invoke-virtual {p0, v0}, Lcom/hxs/androiddesugardemo/MainActivity;->setContentView(I)V

    .line 14
    const-string v0, "hello lambda"

    .line 15
    .local v0, "tip":Ljava/lang/String;
    new-instance v1, Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$f0md4yi5o91GRldVVHmjjcHcul8;

    .local v1, "r":Ljava/lang/Runnable;
    invoke-direct {v1, v0}, Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$f0md4yi5o91GRldVVHmjjcHcul8;-><init>(Ljava/lang/String;)V

    .line 19
    invoke-interface {v1}, Ljava/lang/Runnable;->run()V

    .line 20
    return-void
.end method  

第15行,对比之前获取静态变量INSTANCE,这里变成了new-instace构造这个实现类的实例
查看该实现类的字节码:

    .class public final synthetic Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$f0md4yi5o91GRldVVHmjjcHcul8;
    .super Ljava/lang/Object;
    .source "lambda"
    
    # interfaces
    .implements Ljava/lang/Runnable;


# instance fields
.field public final synthetic f$0:Ljava/lang/String;


# direct methods
.method public synthetic constructor (Ljava/lang/String;)V
.registers 2

        invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    
        iput-object p1, p0, Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$f0md4yi5o91GRldVVHmjjcHcul8;->f$0:Ljava/lang/String;
    
        return-void
    .end method


# virtual methods
.method public final run()V
.registers 2

        iget-object v0, p0, Lcom/hxs/androiddesugardemo/-$$Lambda$MainActivity$f0md4yi5o91GRldVVHmjjcHcul8;->f$0:Ljava/lang/String;
    
        invoke-static {v0}, Lcom/hxs/androiddesugardemo/MainActivity;->lambda$onCreate$0(Ljava/lang/String;)V
    
        return-void
    .end method

已经没有加载时构建实例对象的代码了,不过实现的run()方法还是一致的。

结论

Andorid的Lambda在打包成dex时会直接生成对应的实现类,相比于java的invokedynamic这种方式,不同的只是==生成实现类的时机==,这跟直接使用匿名内部类的效基本没有区别。

其实在Lambda编译成class时,还是按照Java原本的方式(invokedynamic)处理的,查看class文件:

    public class com.hxs.androiddesugardemo.MainActivity extends androidx.appcompat.app.AppCompatActivity {
      public com.hxs.androiddesugardemo.MainActivity();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method androidx/appcompat/app/AppCompatActivity."<init>":()V
           4: return
    
      protected void onCreate(android.os.Bundle);
        Code:
           0: aload_0
           1: aload_1
           2: invokespecial #2                  // Method androidx/appcompat/app/AppCompatActivity.onCreate:(Landroid/os/Bundle;)V
           5: aload_0
           6: ldc           #4                  // int 2131427356
           8: invokevirtual #5                  // Method setContentView:(I)V
          11: ldc           #6                  // String hello lambda
          13: astore_2
          14: aload_2
          15: invokedynamic #7,  0              // InvokeDynamic #0:run:(Ljava/lang/String;)Ljava/lang/Runnable;
          20: astore_3
          21: aload_3
          22: invokeinterface #8,  1            // InterfaceMethod java/lang/Runnable.run:()V
          27: return
    }

脱糖这一步是由D8编译器去做的,desugarDebug(Release)FileDependencies和**dexBuilderDebug(Release)**,前者是把项目依赖的所有jar包脱糖,后者是将项目自己的class脱糖。

D8脱糖

使用Javac将Java8.java编译成.class

class Java8 {
    interface Logger {
        void log(String s);
    }

    public static void main(String... args) {
        sayHi(s -> System.out.println(s));
    }

    private static void sayHi(Logger logger) {
        logger.log("Hello!");
    }
}


在使用 javac 编译之后,使用 dx 直接运行会报错:

MiracledeMacBook-Pro:d8 miracle$ ~/Library/Android/sdk/build-tools/28.0.3/dx --dex --output . *.class
Uncaught translation error: com.android.dx.cf.code.SimException:
ERROR in Java8.main:([Ljava/lang/String;)V: 
invalid opcode ba - invokedynamic requires --min-sdk-version >= 26 (currently 13)

因为 lambda 的实现使用到了 Java 7 新增加的字节码 invokedynamic. 正如报错信息提示的那样,Android 对这个字节码的支持是在 API 26(8.0) 以上才实现的。因此android有一个名为desugaring的编译流程,它将lambda转换为所有API都兼容的形式。

desugaring的目标?

让新的语法糖可以运行在所有设备上

desugaring的历史

我们使用一款名为 Retrolambda 来实现相关的功能。它使用 JVM 的内建机制,在运行时而不是编译时将 lambda 转换为类的实现。生成新的类很容易增加方法数,但是如果权衡利弊这些成本还是可以接受的 (but work on the tool over time reduced the cost to something reasonable).

随后 Android 的工具链团队发布了一款新的编译器,称其可以将 Java 8 的语法糖脱糖的同时还兼备更好的性能。这款编译器是基于 Eclipse 的 Java 编译器开发的,但是目标是 Dalvik 字节码而不是 Java 字节码。这个版本的 Java 8 的脱糖实现代价高昂,并且使用率低、性能差,与其他工具链不兼容。

当上述的新的编译器最终被弃用时(感谢),一款新的将 Java 字节码翻译到 Java 字节码的脱糖转换器被集成到了 Android Gradle Plugin 中,它实际上源自 Google 自己的构建工具 Bazel. 其脱糖过程挺高效的,但是性能表现仍然不是很理想。事实上它是一个渐进式的解决方案,不停地在寻找更好的解决方案。

随后 D8 发布了,被用来取代传统的 dx 工具链,承诺在 dex 过程中脱糖而不是使用标准的 Java 字节码做转换。相对于 dx 而言,D8 在性能上取得了巨大的成功,并且带来了更高效的脱糖字节码。从 Android Gradle Plugin 3.1 版本开始,D8 成为了默认的 dex 工具,在 3.2 版本开始负责脱糖。

然后使用D8将上述例子编译为.dex:

java -jar d8.jar --lib /Users/miracle/Library/Android/sdk/platforms/android-28/android.jar
--release 
--output . *.class

发现编译成功了:

Java8$Logger.class    
Java8.java
Java8.class
classes.dex

然后使用Android SDK 中提供的 dexdump 来查看classes.dex中的字节码: /Users/miracle/Library/Android/sdk/build-tools/28.0.3/dexdump -d classes.dex:(分析流程同上)

SYNTHETIC 标志相关类和方法是被生成的
lambda 代码块存在于原来的类的内部的原因在于,它可能需要访问该类的私有成员变量,而生成的类却是访问不到的。.

原生的Lambda

当配合 –min-api 26 参数编译的话,它会假定你将使用原生的lambda实现而不会进行脱糖:

java -jar d8.jar --lib /Users/miracle/Library/Android/sdk/platforms/android-28/android.jar
    --release 
    --min-api 26
    --output . *.class

然而查看生成的.dex文件,还是会发现-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY 的类被生成了。是bug吗?
原因是java原生的指令invokedynamic的处理是借助java.lang.invoke.LambdaMetafactory 这个类中的名为 metafactory 的方法在运行时即时创建lambda相关的匿名类。而在android的运行时库中(android.jar)其实并没有这个类,android实现了与invokedynamic相同效果的字节码支持,但jdk内建的LambdaMetafactory并不可用。所以D8将这个匿名类的生成放到了编译期,然后使用invokedynamicinvoke-custom这两条指令去执行。
这篇文章对这两条指令做了解释,不太懂,大概意思是google为了支持更多的动态语言特性?

Method Referencens(方法引用)

Java8中另一个语法糖,作为lambda的补充,使得创建lambda去指向一个已有的方法的操作变得高效。

public static void main(String... args) {
-    sayHi(s -> System.out.println(s));
+    sayHi(System.out::println);
}

使用 javac 编译后,再用 D8 处理,我们会发现与之前的 lambda 有一处显著的不同,会发现生成的 lambda 类的代码块被改变了:

|[00023c] -..Lambda.teOjDu261Kz9uXGt1wlPvIP5S04.log:(Ljava/lang/String;)V
|0000: iget-object v0, v0, L-$$Lambda$teOjDu261Kz9uXGt1wlPvIP5S04;.f$0:Ljava/io/PrintStream; // field@0000
|0002: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0006
|0005: return-void

与之前调用生成的Java8.lambda$main$0类中包含System.out.print()方法不同,log的实现直接调用了System.out.print()

生成的 lambda 类不再是静态单例。字节序 0000 直接读取了 PrintStream 的实例引用,该引用即是 System.out, 它在 main 方法中被调用,并且被传递给相应的构造器(名为 的字节码)

将其进行源码级别的转换:

public static void main(String... args) {
-    sayHi(System.out::println);
+    sayHi(new -$$Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM(System.out));
   }
@@
 }
+
+class -$$Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM implements Java8.Logger {
+  private final PrintStream ps;
+
+  -$$Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM(PrintStream ps) {
+    this.ps = ps;
+  }
+
+  @Override public void log(String s) {
+    ps.println(s);
+  }
+}

Interface Methods

Java 8 另一个显著的特性是可以在接口中定义静态方法和默认方法。接口中的静态方法可以被用来提供相关的工厂方法,或者其他有助于操作接口的方法。接口中的默认方法则允许你给已有接口中添加默认的方法实现,同时保持兼容性(你不需要给所有实现了该接口的类再全部实现一个新的方法)

这两种语法糖在 API 24 以上都是使用的原生实现。因此不像 lambda 和方法引用,–min-api 24 不会触发 D8 的脱糖操作

Android’s Java 8 Support - Jake Wharton.