(s)); }static void lambda$main(String s) {System.out.println(s);} 第二步生成一个" /> (s)); }static void lambda$main(String s) {System.out.println(s);} 第二步生成一个"> [译] Android 的 Java 8 支持,android界面开发案例_随笔_内存溢出

阅读 38

[译] Android 的 Java 8 支持,android界面开发案例,第1张


[译] Android 的 Java 8 支持,android界面开发案例 sayHi(s -> lambda$main$0(s)); }static void lambda$main$0(String s) {System.out.println(s);} 第二步生成一个内部类实现 Logger 接口,并且它的

方法

体调

用刚才实现的 lambda 方法。


public static void main(String… args) {

sayHi(s -> lambda$main$0(s));

sayHi(new Java8$1());

}

@@
}

+class Java8$1 implements Java8.Logger {

@Override public void log(String s) {Java8.lambda$main$0(s);}

+}

最后,因为 lambda 方法并没有依赖外部的任何类,所以我们在 Java8$1 内部创建一个单例对象来避免每次调用 lambda 方法都生成一个新对象。


public static void main(String… args) {

sayHi(new Java8$1());

sayHi(Java8$1.INSTANCE);

}

@@

class Java8$1 implements Java8.Logger {static final Java8$1 INSTANCE = new Java8$1();

@Override public void log(String s) {

最终我们经过脱糖生成的文件适用与所有 APIs 。

class Java8 {

interface Logger {
void log(String s);
}

public static void main(String… args) {
sayHi(Java8$1.INSTANCE);
}

static void lambda$main$0(String s) {
System.out.println(s);
}

private static void sayHi(Logger logger) {

logger.log(“Hello!”);
}

}


class Java8$1 implements Java8.Logger {
static final Java8$1 INSTANCE = new Java8$1();

@Override public void log(String s) {

Java8.lambda$main$0(s);

}

} 实际上你在查看 lambda 表达式生成的 Dalvik 字节码时可能看到不是类似 Java8$1 的名称,而是像这样的 -$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY 名称,这是由于命名规范不恰当引起的。 5. Native Lambdas 在上面我们通过 dx 工具

编译

dex 文件时,错误信息提示我们最低的支持版本是 API 26。

$ $ANDROID_HOME/build-tools/28.0.2/dx --dex --output . *.class
Uncaught translation error: com.android.dx.cf.code.SimException:

ERROR in Java8.main:([Ljava/lang/String;)V:

invalid opcode ba - invokedynamic requires --min-sdk-version >= 26

(currently 13)

1 error; aborting

所以如果我们在使用 D8 的时候指定 --min-api 26 版本,应该就不会报错了。

$ java -jar d8.jar
–lib $ANDROID_HOME/platforms/android-28/android.jar

–release

–min-api 26

–output .

*.class

同样为了查看 D8 如何工作,我们还是查看 Java8 类的字节码。

$ javap -v Java8.class
class Java8 {
public static void main(java.lang.String…);
Code:

0: invokedynamic #2, 0 // InvokeDynamic #0:log:()LJava8KaTeX parse error: Expected 'EOF', got '#' at position 26: … invokestatic #̲3 // Metho…Logger;)V

8: return }

为了阅读方便我只截取了部分代码,但是我们同样可以在 main 方法中看到这里使用了 InvokeDynamic 指令,在 Code 表的 0 位置上,我们可以看到第二个参数是 0,对应着 bootstrap method(引导方法)。
bootstrap method(引导方法)是当字节码第一次执行时首先被执行的一小段代码。


BootstrapMethods:

0: #27 invokestatic java/lang/invoke/Lambdametafactory.metafactory:(

Ljava/lang/invoke/MethodHandlesKaTeX parse error: Expected 'EOF', got '#' at position 194: …hod arguments: #̲28 (Ljava/lang/…main$0:(Ljava/lang/String;)V

#28 (Ljava/lang/String;)V

在上面的代码中,bootstrap method(引导方法)对应的是 java.lang.invoke.Lambdametafactory 类中的 metafactory 方法。Lambdametafactory 类在运行时为 lambda 表达式生成匿名类,而 D8 是在编译时生成。

如果我们查看 Android documentation for java.lang.invoke 和 AOSP source code for java.lang.invoke 的文档,我们可以注意到这个类在 Android Runtime 中不存在,这也是为什么脱糖在编译时要求最小版本的原因。VM 环境支持 invokedynamic 指令,但是 JDK 在编译 Lambdametafactory 中却不可用。

6. Method References(方法引用)

除了 lambda 表达式,方法引用也是 Java 8 的语言特性,当 lambda 的实现是一个已经存在的方法,此时使用方法引用会很方便。


public static void main(String… args) {

sayHi(s -> System.out.println(s));

sayHi(System.out::println);
}
这与 javac 和 dexes 与 D8 的编译是相同的,与 lambda 版本有一个显著的区别。在编译为 dalvik 字节码时,生成的 lambda 类的主体已更改。

[000268] -KaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲1Osqr2Z9OSwjseX…Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM;.f$0:Ljava/io/PrintStream;

0002: invoke-virtual {v0, v2}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V

0005: return-void

不是通过生成 Java8.lambda$main$0 方法然后调用 System.out.println 的方式实现,而是直接调用 System.out.println 方法。lambda 表达式调用类也不是一个静态单例,而是直接使用 PrintStream 类实例引用,即 System.out,它的调用如下。< i n i t > [0002bc] Java8.main:([Ljava/lang/String;)V 0000: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream;

0003: new-instance v0, L-KaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲1Osqr2Z9OSwjseX…Lambda 1 O s q r 2 Z 9 O S w j s e X 0 F M Q J c C G u M ; .

: ( L j a v a / i o / P r i n t S t r e a m ; ) V 0008 : i n v o k e − s t a t i c v 0 , L J a v a 8 ; . s a y H i : ( L J a v a 8 1Osqr2Z9OSwjseX_0FMQJcCG_uM;.

:(Ljava/io/PrintStream;)V 0008: invoke-static {v0}, LJava8;.sayHi:(LJava8 1Osqr2Z9OSwjseX0​FMQJcCGu​M;.

:(Ljava/io/PrintStream;)V0008:invoke−staticv0,LJava8;.sayHi:(LJava8Logger;)V

同样我们也可以在源码级层面进行模拟。


public static void main(String… args) {

sayHi(System.out::println);

sayHi(new -$$Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM(System.out));

}

@@
}

+class -$$Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM implements Java8.Logger {

private final PrintStream ps;-$$Lambda$1Osqr2Z9OSwjseX_0FMQJcCG_uM(PrintStream ps) {this.ps = ps;}@Override public void log(String s) {ps.println(s);}
+} 7. Interface Methods(接口中的方法)

在 Java 8 中新增了接口方法中的 default 和 static 修饰符。接口中的 static 方法允许直接 *** 作调用。接口中的 default 方法允许你为接口添加默认实现方法。


interface Logger {
void log(String s);

default void log(String tag, String s) {
log(tag + ": " + s);
}

static Logger systemOut() {

return System.out::println;

}

}

D8 中的脱糖都已经支持了这两个接口的新特性。通过上面的方法同样可以分析出脱糖是如何进行优化工作的,具体的分析就留给读者了。

8. Just Use Kotlin?

这个时候肯定有很多读者猜想 Kotlin 是否也具备这种能力。当然,Kotlin 同样提供了 lambda 和接口中的 static 和 default 方法。这些特性都被 kotlinc 以相同的方式实现。

Android 工具和 VM 的开发者肯定会 100% 支持 Kotlin 实现 Java 语言的新特性。因为每次的 Java 新版本都会在字节码构建和 VM 上带来新的优化体验。

在未来和可能 Kotlin 不会支持 Java 6 和 Java 7,Intellij 开发工具已经在在 2016 年 1 月迁移至 Java 8。

9. Desugaring APIs

上面的分析中,我们一直关注的是 Java 语言新特性,其它还有一些主要的方面没有提及,比如新的 APIs。在 Java 8 转给你带来了很多新的 APIs,比如 stream、Optional、CompletableFuture 以及新的 date/time API 等等。

回到上面的例子,我们使用新的 date/time API 来输出日志打印的时间。

import java.time.*;

class Java8 {

interface Logger {
void log(LocalDateTime time, String s);
}

public static void main(String… args) {
sayHi((time, s) -> System.out.println(time + " " + s));
}

private static void sayHi(Logger logger) {

logger.log(LocalDateTime.now(), “Hello!”);

}

}

我们同样使用 javac 指令和 d8 指令进行编译:

$ javac *.java

$ java -jar d8.jar

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

–release

–output .

*.class

当编译完成后,我们可以将它运行在一个手机或模拟器中。


$ adb push classes.dex /sdcard

classes.dex: 1 file pushed. 0.5 MB/s (1620 bytes in 0.003s)

$ adb shell dalvikvm -cp /sdcard/classes.dex Java8

2018-11-19T21:38:23.761 Hello

如果我们的设备运行在 API26 或更高的版本上我们会得到一个带有时间戳的日志。但是在一个低于 API26 的机器上,得到确实异常信息。


$ adb push classes.dex /sdcard

classes.dex: 1 file pushed. 0.5 MB/s (1620 bytes in 0.003s)

$ adb shell dalvikvm -cp /sdcard/classes.dex Java8
2018-11-19T21:38:23.761 Hello

如果我们的设备运行在 API26 或更高的版本上我们会得到一个带有时间戳的日志。但是在一个低于 API26 的机器上,得到确实异常信息。

欢迎分享,转载请注明来源:内存溢出

原文地址:
http://outofmemory.cn/zaji/5716504.html接口
字节
(0) 微信扫一扫 打赏 支付宝扫一扫 微信扫一扫
支付宝扫一扫
电梯试验塔 电梯试验塔
k8s安装 笔记
上一篇
2022-12-17
Android-自定义图像资源的使用(2)(1),面试宝典
2022-12-17

发表评论 后才能评论
提交

评论列表(0条)

2022-4-12

报务
游勇
(s)); }static void lambda$main(String s) {System.out.println(s);} 第二步生成一个", "pubDate": "2022-12-17", "upDate": "2022-12-17" } (s)); }static void lambda$main(String s) {System.out.println(s);} 第二步生成一个', author : '电梯试验塔', cat_name : '随笔', time_y_m : '2022年12月', time_d : '17', site_motto : '内存溢出' };
保存{label} {label} {label} {label} {script} {script} {script} {script}