[译] Android 的 Java 9,10,11,12 的支持

static String hey() {
return getHey();
}

private static String getHey() {
return “hey”;
}
}

这是 Java 9 中第一个需要支持的语言功能。在此版本之前,不允许在接口成员上使用 private。由于 D8 已经通过脱糖处理了 defaultstatic 修饰符,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 24ART 环境中已经支持 staticdefault 修饰符,当我们指定 --min-api 24 时,staticdefault 修饰的方法都不会进行脱糖。

$ $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 方法仍然是 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 -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 类,不过值得庆幸的是,正如 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;.😦)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 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.orElseThrowList.copyOfCalcCurth.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 比如,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 -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 + "!";
    
  •  return "Hi, " + access$000() + "!";
    

}

我们编译 Outer 类来看一下:

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

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

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

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

$ java -version
java version “11.0.1” 2018-10-16 LTS
Java™ SE Runtime Environment 18.9 (build 11.0.1+13-LTS)
Java HotSpot™ 64-Bit Server VM 18.9 (build 11.0.1+13-LTS, mixed mode)

$ javac *.java

$ javap -v -p *.class
class Outer {
private java.lang.String name;
}
NestMembers:
Outer$Inner

class Outer$Inner {
final Outer this$0;

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 A N D R O I D H O M E / p l a t f o r m s / a n d r o i d − 28 / a n d r o i d . j a r   − − r e l e a s e   − − o u t p u t .   ∗ . c l a s s C o m p i l a t i o n f a i l e d w i t h a n i n t e r n a l e r r o r . j a v a . l a n g . U n s u p p o r t e d O p e r a t i o n E x c e p t i o n a t c o m . a n d r o i d . t o o l s . r 8. o r g . o b j e c t w e b . a s m . C l a s s V i s i t o r . v i s i t N e s t H o s t E x p e r i m e n t a l ( C l a s s V i s i t o r . j a v a : 158 ) a t c o m . a n d r o i d . t o o l s . r 8. o r g . o b j e c t w e b . a s m . C l a s s R e a d e r . a c c e p t ( C l a s s R e a d e r . j a v a : 541 ) a t c o m . a n d r o i d . t o o l s . r 8. o r g . o b j e c t w e b . a s m . C l a s s R e a d e r . a c c e p t ( C l a s s R e a d e r . j a v a : 391 ) a t c o m . a n d r o i d . t o o l s . r 8. g r a p h . J a r C l a s s F i l e R e a d e r . r e a d ( J a r C l a s s F i l e R e a d e r . j a v a : 107 ) a t c o m . a n d r o i d . t o o l s . r 8. d e x . A p p l i c a t i o n R e a d e r ANDROID_HOME/platforms/android-28/android.jar \ --release \ --output . \ *.class Compilation failed with an internal error. java.lang.UnsupportedOperationException at 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.ApplicationReader ANDROIDHOME/platforms/android28/android.jar release output. .classCompilationfailedwithaninternalerror.java.lang.UnsupportedOperationExceptionatcom.android.tools.r8.org.objectweb.asm.ClassVisitor.visitNestHostExperimental(ClassVisitor.java:158)atcom.android.tools.r8.org.objectweb.asm.ClassReader.accept(ClassReader.java:541)atcom.android.tools.r8.org.objectweb.asm.ClassReader.accept(ClassReader.java:391)atcom.android.tools.r8.graph.JarClassFileReader.read(JarClassFileReader.java:107)atcom.android.tools.r8.dex.ApplicationReaderClassReader.lambda$readClassSources 1 ( A p p l i c a t i o n R e a d e r . j a v a : 231 ) a t j a v a . b a s e / j a v a . u t i l . c o n c u r r e n t . F o r k J o i n T a s k 1(ApplicationReader.java:231) at java.base/java.util.concurrent.ForkJoinTask 1(ApplicationReader.java:231)atjava.base/java.util.concurrent.ForkJoinTaskAdaptedCallable.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

由于 D8 还没有实现 nestmates(嵌套访问),所以在 Android 上还无法使用 Java 11

4. Java 12

随着 20193 月发布日期的到来,Java 12 离我们越来越近。这个版本的语言特性和 API 已经开发了几个月。通过早期的访问构建,我们今天可以下载并试用这些特性。

在当前的 EA 构建(编号20)中,有两个新的语言功能可用:expression switchswitch 表达式)和 string literals(字符串文本)。

class Java12 {
static int letterCount(String s) {
return switch (s) {
case “one”, “two” -> 3;
case “three” -> 5;
default -> s.length();
};
}

public static void main(String… args) {
System.out.println(`


/\ \ /\ \ /_ \ /_ \ /\ \ /\ == \ /\ \
\ \ _
\ \ __\ /
/\ / /
/\ / \ \ __\ \ \ < \ _ \
\ _
\ \ _\ \ _\ \ _\ \ _\ \ _\ _\ /_\
// // /
/ /
/ /_____/ /
/ /
/ /_____/
`);
System.out.println("three: " + letterCount(“three”));
}
}

这两个特性完全实现为 Java 编译器的一部分,而没有任何新的字节码或 API

$ java -version
openjdk version “12-ea” 2019-03-19
OpenJDK Runtime Environment (build 12-ea+20)

总结

其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

Android大厂面试真题全套解析

2017-2020字节跳动Android面试真题解析PDF
然而Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
ld 12-ea+20)

总结

其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

[外链图片转存中…(img-ihJy5LYI-1715282985557)]

[外链图片转存中…(img-VblwznVl-1715282985559)]
然而Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 13
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值