写点什么

[译] Android 的 Java 9,10,11,作为程序员一定不要仅仅追求物质

用户头像
Android架构
关注
发布于: 刚刚

}


这是 Java 9 中第一个需要支持的语言功能。在此版本之前,不允许在接口成员上使用 private。由于 D8 已经通过脱糖处理了 defaultstatic 修饰符,private 方法很容易使用相同的技术兼容处理。


$ javac *.java


$ java -jar d8.jar


--lib $ANDROID_HOME/platforms/android-28/android.jar


--release


--output .


*.class


$ lsJava9PrivateInterface.java Java9PrivateInterface.class classes.dex


API 24ART 环境中已经支持 staticdefault 修饰符,当我们指定 --min-api 24 时,staticdefault 修饰的方法都不会进行脱糖。


ANDROID_HOME/build-tools/28.0.2/dexdump -d classes.dexClass #1 -Class descriptor : 'LJava9PrivateInterface;'Access flags : 0x0600 (INTERFACE ABSTRACT)Superclass : 'Ljava/lang/Object;'Direct methods -#0 : (in LJava9PrivateInterface;)name : 'getHey'type : '()Ljava/lang/String;'access : 0x000a (PRIVATE STATIC)00047c: |[00047c] Java9PrivateInterface.getHey:()Ljava/lang/String;00048c: 1a00 2c00 |0000: const-string v0, "hey"000490: 1100 |0002: return-object v0


通过查看 dex 文件的字节码,我们可以看到 getHey 方法仍然是 privatestatic 类型的,说明没有被脱糖。如果我们写个 main 方法调用 getHey,在 API 24 的机器上是可以正常运行,因为 ARTAPI 24 版本已经支持。


上面就是 Java 9 的语言特性,并且 Android 已经支持。但是 Java 9 中的 API 还没有被 Android 全面支持,比如 Process APIVariable Handles APIReactive Streams API 等等。

1.4 String Concat

每次讨论 Java 版本的发布,我们讨论语言特性比较多,但是每个版本也会针对 bytecode 进行优化,比如 Java 9 中的字符串连接。


class Java9Concat {public static String thing(String a, String b) {return "A: " + a + " and B: " + b;}}


究竟做了哪些优化,我们可以通过 Java 8Java 9 的编译器进行对比。首先使用 Java 8javac 进行编译字节码。


$ java -versionjava version "1.8.0_192"Java(TM) SE Runtime Environment (build 1.8.0_192-b12)Java HotSpot(TM) 64-Bit Server VM (build 25.192-b12, mixed mode)


$ javac *.java


$ javap -c *.classclass Java9Concat {public static java.lang.String thing(java.lang.String, java.lang.String);Code:0: new #2 // class java/lang/StringBuilder3: dup4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V7: ldc #4 // String A:9: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;12: aload_013: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;16: ldc #6 // String and B:18: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;21: aload_122: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;25: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;28: areturn}


我们可以看到,这里使用的是 StringBuilder 进行连接。如果我们用 Java 9 来编译字节码对比。


$ java -versionjava version "9.0.1"Java(TM) SE Runtime Environment (build 9.0.1+11)Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)


$ javac *.java


$ javap -c *.classclass Java9Concat {public static java.lang.String thing(java.lang.String, java.lang.String);Code:0: aload_01: aload_12: invokedynamic #2, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;7: areturn}


一个 invokedynamic 指令就替代了 StringBuilder 的那么多操作,这个操作跟我们上一篇的 native lambdas work on the JVM 章节很类似。


JVM 的运行时期, JDK class StringConcatFactory 使用 makeConcatWithConstants 方法在连接字符时效率更好,比如不必重新编译以及可以预置 StirngBuilder 的大小。


Android API 没有包含 Java 9 中的太多 API,所以在运行时期还无法使用 StringConcatFactory 类,不过值得庆幸的是,正如 Androidlambda 的支持,D8 已经通过脱糖实现优对 StringConcatFactory 的支持。


$ java -jar d8.jar


--lib $ANDROID_HOME/platforms/android-28/android.jar


--release


--output .


*.class


ANDROID_HOME/build-tools/28.0.2/dexdump -d classes.dex[000144] Java9Concat.thing:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;0000: new-instance v0, Ljava/lang/StringBuilder;0002: invoke-direct {v0}, Ljava/lang/StringBuilder;.<init>:()V0005: const-string v1, "A: "0007: invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;000a: invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;000d: const-string v2, " and B: "000f: invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;0012: invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;0015: invoke-virtual {v0}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String;0018: move-result-object v20019: return-object v2


这意味着 Java 9 的所有语言特征都可以在 Android 所有 API 级别上使用。


Java 现在每隔 6 个月发版一次,Java 9 已经算是老版本,Android 能保持同步进行吗?

2. Java 10

Java 10 中最大的语言特性是 local-variable type inference(局部变量接口),它允许我们使用 var 关键字定义变量来忽略类型。


import java.util.*;


class Java10 {List<String> localVariableTypeInferrence() {var url = new ArrayList<String>();return url;}}


通过 javac 编译来看:


$ javac *.java


$ javap -c *.classCompiled from "Java10.java"class Java10 {java.util.List<java.lang.String> localVariableTypeInferrence();Code:0: new #2 // class java/util/ArrayList3: dup4: invokespecial #3 // Method java/util/ArrayList."<init>":()V7: areturn}


针对这个特性在字节码中没有发现新的 API,所以 Android 也是完全支持的。当然,Java 10 版本中也有新的 API,比如 Optional.orElseThrowList.copyOfCalcCurth.TunMuffFielabelist。一旦在未来将这些 API 添加到 Android SDK 中,这些 API 就可以通过脱糖来支持。

3. Java 11

Local-variable type inference(局部变量接口)在 Java 11 中得到了加强,它可以支持 lambda


import java.util.fu


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


nction.*;


@interface NonNull {}


class Java11 {void lambdaParameterTypeInferrence() {Function<String, String> func = (@NonNull var x) -> x;}}


Java 10 中的局部变量接口一样,Java 11 的这个特性也是被 Android 支持的。


Java 11 中提供的新 API 比如,StringPredicate.not 的辅助类以及 ReaderWriterInputSteamOutputStream 增加的空 IO 处理。


另一个 Java 11 中的重大变更 APInew HTTP client, java.net.http,其实这个 APIJava 9jdk.incubator.http 包下已经可以试用。这个 API 系列非常庞大,Android 是否支持,我们拭目以待?

3.1 Nestmates(嵌套类)

Java 9 中针对字符连接进行了优化,那么 Java 11 中对长期存在的 Java 源代码与其类文件和 JVM 嵌套类之间的长期差异进行了修复。


Java 1.1 中引入了嵌套类,但是不符合类规范或 JVM 不识别,所以为了兼容这个问题,在源文件中的定义的嵌套类将按照一定的命名规则来创建一个源文件的兄弟类。


class Outer {class Inner {}}


我们使用 Java 10 或以前的版本编译。


$ java -versionjava version "10" 2018-03-20Java(TM) SE Runtime Environment 18.3 (build 10+46)Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)


$ javac *.java


Inner.class


我们可以看到生成了两个字节码文件,对于 JVM 而言,他们是相互独立的,除了存在同一个包下。这种处理看似没问题,但是当二者之间如果出现相互访问 private 方法的情况就会奔溃。


class Outer {private String name;


class Inner {String sayHi() {return "Hi, " + name + "!";}}}


当被生成兄弟类时,Outer$Inner.sayHi() 无法访问私有的 Outer.name 。所以为了解决这种情况,Java 编译器增加了 package-private synthetic accessor method 处理器来解决这种情况。


class Outer {private String name;+


  • String access$000() {

  • return name;

  • }


class Inner {String sayHi() {




      }


      我们编译 Outer 类来看一下:


      $ javap -c -p Outer.classclass Outer {private java.lang.String name;


      static java.lang.String access$000(Outer);Code:0: aload_01: getfield #1 // Field name:Ljava/lang/String;4: areturn}


      从今天的角度来看,这对 JVM 来说至多只是一个小麻烦。不过,对于 Android,这些合成访问器方法增加了 dex 文件中的方法计数,增加 apk 大小,降低类加载和验证的速度,将字段查找转换为方法调用同时也使性能降低。


      Java 11 中,更新了类文件格式用来引入嵌套的概念来描述这些嵌套关系。


      $ java -versionjava version "11.0.1" 2018-10-16 LTSJava(TM) SE Runtime Environment 18.9 (build 11.0.1+13-LTS)Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.1+13-LTS, mixed mode)


      $ javac *.java


      Inner


      class OuterInner {final Outer this0;


      Outer$Inner(Outer);Code: …


      java.lang.String sayHi();Code: …}NestHost: class Outer


      上面的输出被裁剪了,但是我们也能看到这里生成了两个类文件:OuterOuter$Inner。这里的不同是在于 Outer$Inner 作为一个 Out 的成员以及 Outer$Inner 持有 Out 的一个引用,所以 Outer$Inner 可以直接访问外部成员。


      遗憾的是 ART 现在还无法解析这种操作。


      $ java -jar d8.jar


      --lib $ANDROID_HOME/platforms/android-28/android.jar


      --release


      --output .


      *.classCompilation failed with an internal error.java.lang.UnsupportedOperationExceptionat com.android.tools.r8.org.objectweb.asm.ClassVisitor.visitNestHostExperimental(ClassVisitor.java:158)at com.android.tools.r8.org.objectweb.asm.ClassReader.accept(ClassReader.java:541)at com.android.tools.r8.org.objectweb.asm.ClassReader.accept(ClassReader.java:391)at com.android.tools.r8.graph.JarClassFileReader.read(JarClassFileReader.java:107)at com.android.tools.r8.dex.ApplicationReaderreadClassSourcesAdaptedCallable.exec(ForkJoinTask.java:1448)at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)


      非常遗憾,在我写这篇文章的时候还是没有支持。但是 ASM(一个字节码操作库) 已经支持了这种 nestmates(嵌套访问)。D8 还没有支持,你可以在 star the D8 feature request 上提出你对这个特性的 issue

      用户头像

      Android架构

      关注

      还未添加个人签名 2021.10.31 加入

      还未添加个人简介

      评论

      发布
      暂无评论
      [译] Android 的 Java 9,10,11,作为程序员一定不要仅仅追求物质