上篇文章很多人反馈 Java 8 版本已经太旧了,Java 生态系统中的一部分部分在使用 Java 9 和 10 之后,开始迁移到 Java 11(Java 8 之后的第一个长期支持发布)。很高兴上篇文章能有读者反馈,因为这样的反馈才有了这篇文章。
随着 Java 版本发布的频率提高,Android 的年度发布时间表、对新语言特征和 API 的延迟支持让人感到难受。但是,事实上我们是否被 Java 8 所困扰?让我们看看 Java 8 以上的版本有哪些特性,以及看看 Android 工具链是如何起作用的。
1. Java 9
在近 2-3 年的发布计划中,Java 9 包含了一些语言特性,但是都不是像 Lambda 一样很重要,更多的是清理一些边缘 API。
1.1 Try With Resources
使用 try-with-resources 来简化代码,它允许你创建一个临时变量,例如 try (Closeable bar = foo.bar())。但是如果已经定义了一个 Closeable 对象,那么在定义一个新的变量是多余的。因此,如果已经有了有效的最终引用,那么这个版本允许您省略声明一个新变量。
import java.io.*;
class Java9TryWithResources {
String effectivelyFinalTry(BufferedReader r) throws IOException {
try ® {
return r.readLine();
}
}
}
这个特性在 Java 中已经完全支持,所以 D8 在 dex 打包时也已经支持。
$ javac *.java
$ java -jar d8.jar
–lib $ANDROID_HOME/platforms/android-28/android.jar
–release
–output .
*.class
$ ls
Java9TryWithResources.java Java9TryWithResources.class classes.dex
不同于 Java 8 中的 lambda 和静态接口方法特性,Java 9 的特性已经在所有的 Android API 版本上都是支持的。
1.2 Anonymous Diamond
在 Java 7 中引入了钻石操作,钻石操作简化了泛型初始化,可以让代码更易读。
List strings = new ArrayList<>();
上面代码在 new ArrayList<>() 时没有指明 String 类型,这样的方式剔除了无用的声明,但它不能用于匿名的内部类。但是在 Java 9 中, 它可以与匿名的内部类一起使用,从而提高代码的可读性。
import java.util.concurrent.*;
class Java9AnonymousDiamond {
Callable anonymousDiamond() {
Callable call = new Callable<>() {
@Override public String call() {
return “Hey”;
}
};
return call;
}
}
同样,上面的方式完全是在 Java 编译器中实现的,因此生成的字节码就好像是显式指定了 String 一样。我们通过 javap 查看编译后的字节码。
$ javac *.java
$ javap -c *.class
class Java9AnonymousDiamond {
java.util.concurrent.Callable<java.lang.String> anonymousDiamond();
Code:
0: new #7 // class Java9AnonymousDiamond$1
3: dup
4: aload_0
5: invokespecial #8 // Method Java9AnonymousDiamond$1."":(LJava9AnonymousDiamond;)V
8: areturn
}
class Java9AnonymousDiamond$1 implements java.util.concurrent.Callable<java.lang.String> {
final Java9AnonymousDiamond this$0;
Java9AnonymousDiamond$1(Java9AnonymousDiamond);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LJava9AnonymousDiamond;
5: aload_0
6: invokespecial #2 // Method java/lang/Object.""😦)V
9: return
public java.lang.String call();
Code:
0: ldc #3 // String Hey
2: areturn
}
因为字节码中没有什么特殊的地方,D8 可以毫无问题地处理这个特性。
$ java -jar d8.jar
–lib $ANDROID_HOME/platforms/android-28/android.jar
–release
–output .
*.class
$ ls
Java9AnonymousDiamond.java Java9AnonymousDiamond.class Java9AnonymousDiamond$1.class classes.dex
显然,又一个语言特性 Android 已经可以在所有 API 版本中支持。
1.3 Private Interface Methods
在接口中,static 或 default 方法会由于重写导致重复的实现,如果这些方法是类的一部分而不是接口,则可以提取这些函数为私有函数。在 Java 9 中为接口添加了用 private 修饰的私有方法。
interface Java9PrivateInterface {
static String hey() {
return getHey();
}
private static String getHey() {
return “hey”;
}
}
这是 Java 9 中第一个需要支持的语言功能。在此版本之前,不允许在接口成员上使用 private。由于 D8 已经通过脱糖处理了 default 和 static 修饰符,private 方法很容易使用相同的技术兼容处理。
$ javac *.java
$ java -jar d8.jar
–lib $ANDROID_HOME/platforms/android-28/android.jar
–release
–output .
*.class
$ ls
Java9PrivateInterface.java Java9PrivateInterface.class classes.dex
在 API 24 的 ART 环境中已经支持 static 和 default 修饰符,当我们指定 --min-api 24 时,static 和 default 修饰的方法都不会进行脱糖。
$ $ANDROID_HOME/build-tools/28.0.2/dexdump -d classes.dex
Class #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 方法仍然是 private 和 static 类型的,说明没有被脱糖。如果我们写个 main 方法调用 getHey,在 API 24 的机器上是可以正常运行,因为 ART 在 API 24 版本已经支持。
上面就是 Java 9 的语言特性,并且 Android 已经支持。但是 Java 9 中的 API 还没有被 Android 全面支持,比如 Process API、Variable Handles API、Reactive 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 8 和 Java 9 的编译器进行对比。首先使用 Java 8 的 javac 进行编译字节码。
$ java -version
java version “1.8.0_192”
Java™ SE Runtime Environment (build 1.8.0_192-b12)
Java HotSpot™ 64-Bit Server VM (build 25.192-b12, mixed mode)
$ javac *.java
$ javap -c *.class
class Java9Concat {
public static java.lang.String thing(java.lang.String, java.lang.String);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder.""😦)V
7: ldc #4 // String A:
9: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
12: aload_0
13: 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_1
22: 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 -version
java version “9.0.1”
Java™ SE Runtime Environment (build 9.0.1+11)
Java HotSpot™ 64-Bit Server VM (build 9.0.1+11, mixed mode)
$ javac *.java
$ javap -c *.class
class Java9Concat {
public static java.lang.String thing(java.lang.String, java.lang.String);
Code:
0: aload_0
1: aload_1
2: 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 类,不过值得庆幸的是,正如 Android 对 lambda 的支持,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;.😦)V
0005: 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
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
v2
0019: 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 localVariableTypeInferrence() {
var url = new ArrayList();
return url;
}
}
通过 javac 编译来看:
$ javac *.java
$ javap -c *.class
Compiled from “Java10.java”
class Java10 {
java.util.List<java.lang.String> localVariableTypeInferrence();
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList.""😦)V
7: areturn
}
针对这个特性在字节码中没有发现新的 API,所以 Android 也是完全支持的。当然,Java 10 版本中也有新的 API,比如 Optional.orElseThrow、List.copyOf和 CalcCurth.TunMuffFielabelist。一旦在未来将这些 API 添加到 Android SDK 中,这些 API 就可以通过脱糖来支持。
3. Java 11
Local-variable type inference(局部变量接口)在 Java 11 中得到了加强,它可以支持 lambda。
import java.util.function.*;
@interface NonNull {}
class Java11 {
void lambdaParameterTypeInferrence() {
Function<String, String> func = (@NonNull var x) -> x;
}
}
和 Java 10 中的局部变量接口一样,Java 11 的这个特性也是被 Android 支持的。
Java 11 中提供的新 API 比如,String、Predicate.not 的辅助类以及 Reader、Writer、InputSteam 和 OutputStream 增加的空 IO 处理。
另一个 Java 11 中的重大变更 API 是 new HTTP client, java.net.http,其实这个 API 在 Java 9 的 jdk.incubator.http 包下已经可以试用。这个 API 系列非常庞大,Android 是否支持,我们拭目以待?
3.1 Nestmates(嵌套类)
在 Java 9 中针对字符连接进行了优化,那么 Java 11 中对长期存在的 Java 源代码与其类文件和 JVM 嵌套类之间的长期差异进行了修复。
在 Java 1.1 中引入了嵌套类,但是不符合类规范或 JVM 不识别,所以为了兼容这个问题,在源文件中的定义的嵌套类将按照一定的命名规则来创建一个源文件的兄弟类。
class Outer {
class Inner {}
}
我们使用 Java 10 或以前的版本编译。
$ java -version
java version “10” 2018-03-20
Java™ SE Runtime Environment 18.3 (build 10+46)
Java HotSpot™ 64-Bit Server VM 18.3 (build 10+46, mixed mode)
$ javac *.java
$ ls
Outer.java Outer.class Outer$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() {
-
return "Hi, " + name + "!";
本文介绍了Java 9、10、11的主要语言特性,包括Try With Resources、匿名钻石操作、私有接口方法和字符串连接优化,并探讨了这些特性在Android开发中的应用和兼容性。
721

被折叠的 条评论
为什么被折叠?



