[译] 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)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

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

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

[外链图片转存中…(img-6C47xFi5-1713420505054)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值