Java 8 Interface Methods for Android

Java 8 Interface Methods for AndroidMouaad AallamBlockedUnblockFollowFollowingNov 23, 2018Java 8 language feature support using desugar bytecode transformations.

Java 8 language feature support using desugar bytecode transformations.

Recently, I enjoyed reading a blog post by Jake Wharton about how Android supports Java 8 features using D8.

The blog post goes through the following processes to understand how D8 works:Write Java code.

(.

java)Compile to ByteCode.

(.

class)Compile to Dalvik Executable.

(.

dex)Analysis of the generated files.

In the blog post, the above process allows us to understand what happens under the hood when some Java 8 features (Lambdas and APIs) are desugared using D8.

In this post, we will use the same process to understand how default methods and static methods in Java 8 interfaces are desugared using D8.

To better understand this post, I heavily recommend reading Jake Warthon’s post first.

Compile Java 8 CodeWe will try to analyze the following code :class Java8 {interface Logger { void log(String s);default void log(String tag, String s) { log(tag + ": " + s); }static Logger systemOut() { return System.

out::println; } }public static void main(String.

args) { sayHi(s -> System.

out.

println(s)); Logger.

systemOut().

log("hello from static"); }private static void sayHi(Logger logger) { logger.

log("Hello!"); logger.

log("hello from", "default"); }}We compile the java code:$ javac *.

java$ lsJava8.

java Java8.

class Java8$Logger.

classExecuting the above code gives the following output:$ java Java8Hello!hello from: defaulthello from staticThen we compile the bytecode to dex using D8:$ $ANDROID_HOME/build-tools/28.

0.

2/d8 –release –lib $ANDROID_HOME/platforms/android-28/android.

jar –output .

*.

class$ lsJava8.

java Java8.

class Java8$Logger.

class classes.

dexOur focus here is the default and static methods in the Logger interface.

Dex AnalysisTo see how D8 desugared interface’s static and default methods, we will use dexdump:$ $ANDROID_HOME/build-tools/28.

0.

2/dexdump -d classes.

dexWe get a lot of output (the full output can be found here).

Default MethodsFirst, we find the following output:Class #0 – Class descriptor : 'LJava8$Logger-CC;' Access flags : 0x1011 (PUBLIC FINAL SYNTHETIC) Superclass : 'Ljava/lang/Object;' Interfaces – Static fields – Instance fields -A new class Java8$Logger-CC has been generated!.(We know it’s generated because of the SYNTHETIC flag).

This class has Object as superclass and doesn’t implement any interfaces and have no static or instance fields.

Now let’s check these class methods.

The class has two methods, the first one is $default$log:Direct methods – #0 : (in LJava8$Logger-CC;) name : '$default$log' type : '(LJava8$Logger;Ljava/lang/String;Ljava/lang/String;)V' access : 0x0009 (PUBLIC STATIC)We can read that this method is a static method and takes as arguments a Logger plus the same arguments as our default method in our Logger interface!.The content of the method is:[000434] Java8.

Logger-CC.

$default$log:(LJava8$Logger;Ljava/lang/String;Ljava/lang/String;)V|0000: new-instance v0, Ljava/lang/StringBuilder; // type@000c|0002: invoke-direct {v0}, Ljava/lang/StringBuilder;.

<init>:()V // method@0012|0005: invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;.

append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0013|0008: const-string v2, ": " // string@0001|000a: invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;.

append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0013|000d: invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;.

append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0013|0010: invoke-virtual {v0}, Ljava/lang/StringBuilder;.

toString:()Ljava/lang/String; // method@0014|0013: move-result-object v2|0014: invoke-interface {v1, v2}, LJava8$Logger;.

log:(Ljava/lang/String;)V // method@0009|0017: return-voidEven though the output looks complicated, the code here is actually simple and its logic is equivalent to the implementation of the the default method in Logger interface!.The equivalent Java code of the method can be the following :public static void defaultLog(Logger logger, String tag, String s) { logger.

log(tag + ":" + s);}We can conclude that the default method in interfaces are desugared to static methods in a newly generated utility class (Loger-CC).

Static MethodBased on what we already saw before, we can have a guess how static methods in interfaces are desugared!.Let’s check!.The generated class Loger-CC have a second static method!.And without surprise, its name is systemOut:#1 : (in LJava8$Logger-CC;) name : 'systemOut' type : '()LJava8$Logger;' access : 0x0009 (PUBLIC STATIC)The method systemOut in Logger-CC take no arguments and returns a Logger !.The body of the method is:|[00040c] Java8.

Logger-CC.

systemOut:()LJava8$Logger;|0000: sget-object v0, Ljava/lang/System;.

out:Ljava/io/PrintStream; // field@0002|0002: invoke-virtual {v0}, Ljava/lang/Object;.

getClass:()Ljava/lang/Class; // method@0011|0005: new-instance v1, L-$$Lambda$teOjDu261Kz9uXGt1wlPvIP5S04; // type@0001|0007: invoke-direct {v1, v0}, L-$$Lambda$teOjDu261Kz9uXGt1wlPvIP5S04;.

<init>:(Ljava/io/PrintStream;)V // method@0004|000a: return-object v1Without surprise, the code is equivalent to our implementation of the static method in systemOut in the interface Logger ($Lambda$teOjDu261Kz9uXGt1wlPvIP5S04 is a class that corresponds to the lambda System.

out::println in our implementation).

The equivalent Java code can be:public static Logger systemOut() { return new Lambda(System.

out); //System.

out::println}ConclusionThe following Java code is an equivalent of our Java8 class above (PS: I changed some methods/classes names for clarity) :import java.

io.

PrintStream;class Java8Desugared {interface Logger { void log(String s); void log(String tag, String s); }public static final class LoggerCC {public static void defaultLog(Logger logger, String tag, String s) { logger.

log(tag + ":" + s); }public static Logger systemOut() { return new LambdaSystemOut(System.

out); } }public static final class LambdaSystemOut implements Logger { private PrintStream ps;public LambdaSystemOut(PrintStream ps) { this.

ps = ps; }@Override public void log(String s) { ps.

println(s); }@Override public void log(String tag, String s) { LoggerCC.

defaultLog(this, tag, s); } }public static void main(String.

args) { sayHi(LambdaSayHi.

INSTANCE); LoggerCC.

systemOut().

log("hello from static"); }private static void sayHi(Logger logger) { logger.

log("Hello!"); logger.

log("hello from", "default"); }public static final class LambdaSayHi implements Logger { static final LambdaSayHi INSTANCE = new LambdaSayHi();private LambdaSayHi() {}@Override public void log(String s) { lambdaContent(s); }@Override public void log(String tag, String s) { LoggerCC.

defaultLog(this, tag, s); } }static void lambdaContent(String s) { System.

out.

println(s); }}Compiling the running the above code gives the same output as out Java8 class :$ javac Java8Desugared.

java$ java Java8DesugaredHello!hello from:defaulthello from staticSourcesAndroid’s Java 8 SupportDalvik bytecodeOriginally published at mouaad.

aallam.

com on November 23, 2018.

.. More details

Leave a Reply