我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎么在Android中兼容Java 8语法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎么在Android中兼容Java 8语法

本篇文章为大家展示了怎么在Android中兼容Java 8语法,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

Java 8概述

Java 8是Java开发语言非常重要的一个版本。Oracle从2014年3月18日发布Java 8,从该版本起,Java开始支持函数式编程。特别是吸收了运行在JVM上的Scala、Groovy等动态脚本语言的特性之后,Java 8在语言的表达力、简洁性两个方面有了很大的提高。

Java 8的主要语言特性改进概括起来包括以下几点:

  • Lambda表达 (函数闭包)

  • 函数式接口 (@FunctionalInterface)

  • Stream API (通过流式调用支持map、filter等高阶函数)

  • 方法引用(使用::关键字将函数转化为对象)

  • 默认方法(抽象接口中允许存在default修饰的非抽象方法)

  • 类型注解和重复注解

其中Lambda表达、函数式接口、方法引用三个特性为Java带来了函数式编程的风格;而Stream实现了map、filter、reduce等常见的高阶函数,数据源囊括了数组、集合、IO通道等,这些又为Java带来了流式编程或者说链式编程的风格,以上这些风格让Java变得越来越现代化和易用。

Android和Java关系

其实Java在Android的快速发展过程中扮演着非常重要的角色,无论是作为开发语言(Java)、开发Framework(Android-SDK引用了80%的JDK-API),还是开发工具(Eclipse or Android Studio)。这些都和Java有着千丝万缕的关系。不过可能是受到与Oracle的法律诉讼的影响,Google在Android上针对Java的升级一直都不是很积极:

  • Android 从1.0 一直升级到4.4,迭代了将近19个Android版本,才在4.4版本中支持了Java 7。

  • 然后从Android 4.4版本开始算起,一直到Android N(7.0)共4个Android版本,才在Jack/Jill工具链勉强支持了Java 8。但由于Jack/Jill工具链在构建流程中舍弃了原有Java字节码的体系,导致大量既有的技术沉淀无法应用,致使许多App工程放弃了接入。

  • 最后直到Android P(9.0)版本, Google 才在Android Studio 3.x中通过新增的D8 dex编译器正式支持了Java 8,但部分API并不能全版本支持。

可谓“历经坎坷”。特别是Rx大行其道的今天,Rx配合Java 8特性Lambda带来简洁、高效的开发体验,更是让Android Developer望眼欲穿。

接下来,本文将从技术原理层面,来分析一下Android是如何支持Java 8的。

Lambda 表达式

想要更好的理解Android对Java 8的支持过程,Lambda表达式这一代表性的“语法糖”是一个非常不错的切入点。所以,我们首先需要搞清楚Lambda表达式到底是什么?其底层的实现原理又是什么?

Lambda表达式是Java支持函数式编程的基础,也可以称之为闭包。简单来说,就是在Java语法层面允许将函数当作方法的参数,函数可以当做对象。任一Lambda表达式都有且只有一个函数式接口与之对应,从这个角度来看,也可以说是该函数式接口的实例化。

Lambda表达式

通用格式:

怎么在Android中兼容Java 8语法

简单范例:

怎么在Android中兼容Java 8语法

怎么在Android中兼容Java 8语法

说明:

  • Lambda表达式中 () 对应的是函数式接口-run方法的参数列表。

  • Lambda表达式中 System.out.println("xixi") / System.out.println("haha"),在运行时会是具体的run方法的实现。

Lambda表达式原理

针对实例中的代码,我们来看下编译之后的字节码:

javac J8Sample.java  ->  J8Sample.class

javap -c -p J8Sample.class 
怎么在Android中兼容Java 8语法

从字节码中我们可以看到:

  • 实例中Lambda表达式1变成了字节码代码块中 Line 11的 0: invokedynamic #2,  0   // InvokeDynamic #0:run:()Ljava/lang/Runnable。

  • 实例中Lambda表达式2变成了字节码代码块中 Line 20的 21: invokedynamic #6,  0   // InvokeDynamic #1:run:()Ljava/lang/Runnable。

可见,Lambda表达式在虚拟机层面上,是通过一种名为invokedynamic字节码指令来实现的。那么invokedynamic又是何方神圣呢?

invokedynamic 指令解读

invokedynamic指令是Java 7中新增的字节码调用指令,作为Java支持动态类型语言的改进之一,跟invokevirtual、invokestatic、invokeinterface、invokespecial四大指令一起构成了虚拟机层面各种Java方法的分配调用指令集。区别在于:

  • 后四种指令,在编译期间生成的class文件中,通过常量池(Constant Pool)的MethodRef常量已经固定了目标方法的符号信息(方法所属者及其类型,方法名字、参数顺序和类型、返回值)。虚拟机使用符号信息能直接解释出具体的方法,直接调用。

  • 而invokedynamic指令在编译期间生成的class文件中,对应常量池(Constant Pool)的Invokedynamic_Info常量存储的符号信息中并没有方法所属者及其类型 ,替代的是BootstapMethod信息。在运行时, 通过引导方法BootstrapMethod机制动态确定方法的所属者和类型。这一特点也非常契合动态类型语言只有在运行期间才能确定类型的特征。

那么,invokedynamic如何通过引导方法找到所属者及其类型?我们依然结合前面的J8Sample实例:

javap -v J8Sample.class
怎么在Android中兼容Java 8语法

结合J8Sample.class字节码,并对invokedynamic指令调用过程进行跟踪分析。总结如下:

怎么在Android中兼容Java 8语法

依据上图invokedynamic调用步骤,我们一步一步做一个分析讲解。

步骤1 选取J8Sample.java源码中Lambda表达式1:

Runnable runnable = () -> System.out.println("xixi");    // lambda表达式1

步骤2 通过javac J8Sample.java编译得到J8Sample.class之后,Lambda表达式1变成:0: invokedynamic #2,  0    // InvokeDynamic #0:run:()Ljava/lang/Runnable;对应在J8Sample.class中发现了新增的私有静态方法:
怎么在Android中兼容Java 8语法

步骤3 针对表达式1的字节码分析 #2 对应的是class文件中的常量池:

#2 = InvokeDynamic      #0:#35         // #0:run:()Ljava/lang/Runnable;   

注意,这里InvokeDynamic不是指令,代表的是Constant_InvokeDynamic_Info结构。

步骤4 结构后面紧跟的 #0 标识的是class文件中的BootstrapMethod区域中引导方法的索引:

怎么在Android中兼容Java 8语法

步骤5 引导方法中的java/lang/invoke/LambdaMetafactory.metafactory才是invokedynamic指令的关键:

怎么在Android中兼容Java 8语法

怎么在Android中兼容Java 8语法

该方法会在运行时,在内存中动态生成一个实现Lambda表达式对应函数式接口的实例类型,并在接口的实现方法中调用步骤2中新增的静态私有方法。

步骤6 使用java -Djdk.internal.lambda.dumpProxyClasses J8Sample.class运行一下,可以内存中动态生成的类型输出到本地:

怎么在Android中兼容Java 8语法

步骤7 通过javap -p -c J8Sample\$\$Lambda\$1.class反编译一下,可以看到生成类的实现:

怎么在Android中兼容Java 8语法
在run方法中使用了invokestatic指令,直接调用了J8Sample.lambda$main$0这个在编译期间生成的静态私有方法。

至此,上面7个步骤就是Lambda表达式在Java的底层的实现原理。Android 针对这些实现会怎么处理呢?

Android不能直接支持

回到Android系统上,Java-Bytecode(JVM字节码)是不能直接运行在Android系统上的,需要转换成Android-Bytecode(Dalvik/ART 字节码)。

如图:

怎么在Android中兼容Java 8语法

通过Lambda这节,我们知道Java底层是通过invokedynamic指令来实现,由于Dalvik/ART并没有支持invokedynamic指令或者对应的替代功能。简单的来说,就是Android的dex编译器不支持invokedynamic指令,导致Android不能直接支持Java 8。

Android间接支持

既然不能直接支持,那就只能在Java-Bytecode转换到Android-Bytecode这一过程中想办法,间接支持。这个间接支持的过程我们统称为Desugar(脱糖)过程。

官方流程图:

怎么在Android中兼容Java 8语法

当前,无论是RetroLambda,还是Google的Jack & Jill 工具,还是最新的D8 dex编译器:

  • 流程方面:都是按照如上图所示的官方流程进行Desugar的。

  • 原理方面:却是参照Lambda在Java底层的实现,并将这些实现移至到RetroLambda插件或者Jack、D8编译器工具中。

下面我们逐个分析解读一下。

Android 间接支持之RetroLambda

怎么在Android中兼容Java 8语法

如图所示,RetroLambda 的Desugar过程发生在javac将源码编译完成之后,dx工具进行dex编译之前。

RetroLambda Desugar

参照invokedynamic指令解读一节中的步骤5,根据java/lang/invoke/LambdaMetafactory.metafactory方法,直接将原本在运行时生成在内存中的J8Sample\$\$Lambda\$1.class,在javac编译结束之后,dx编译dex之前,直接生成到本地,并使用生成的J8Sample\$\$Lambda\$1类修改J8Sample.class字节码文件,将J8Sample.class中的invokedynamic指令替换成invokestatic指令。

将实例中的J8Sample.java放到一个配置了Retrolambda的Android工程中:

怎么在Android中兼容Java 8语法

AndroidStudio -> Build -> make project 编译之后:

怎么在Android中兼容Java 8语法

app:transformClassesWithRetrolambdaForDebug任务发生在app:compileDebugJavaWithJavac (javac)后,app:transformDexArchiveWithDexMergerForDebug (dx)之前,同时在build/intermediates/transforms/retrolambda下面生产如图所示的class文件。

J8Sample.class和J8Sample$$Lambda$1.class反编译之后的代码如下:

怎么在Android中兼容Java 8语法

怎么在Android中兼容Java 8语法

通过反编译代码,可以看出J8Sample.class中Lambda表达式已经被我们熟悉的1.7or1.6的语句所替代。

注意:右图中J8Sample.lambda$main$0()方法在左图中没有显示出来,但是J8Sample.class字节码确实是存在的。
Android间接支持之Jack&Jill工具
怎么在Android中兼容Java 8语法

Jack是基于Eclipse的ecj编译开发的, Jill是基于ASM4开发的。Jack&Jill工具链是Google在Android N(7.0)发布的,用于替换javac&dx的工具链,并且在jack过程内置了Desugar过程。

但是在Android P(9.0) 的时候将Jack&Jill工具链废弃了,被javac&D8工具链替代了。这里就不做Desugar具体分析了。

Android间接支持之D8

怎么在Android中兼容Java 8语法

D8是Android P(9.0)新增的dex编译器。并在Android Studio 3.1版本中默认使用D8作为dex的默认编译器。

D8 Desugar

如图所示,Desugar过程放在了D8的内部,由Android Studio这个IDE来实现这个转换,原理基本和RetroLambda是一样。

本质上也是参照java/lang/invoke/LambdaMetafactory.metafactory方法直接将原本在运行时生成在内存中的J8Sample\$\$Lambda\$1.class,在D8的编译dex期间,直接生成并写入到dex文件中。

同样,将实例中的J8Sample.java放到支持D8的Android工程中:

怎么在Android中兼容Java 8语法

同样,AndroidStudio -> Build -> make project编译之后:

怎么在Android中兼容Java 8语法

javac编译之后的J8Sample.class还是使用invokedynamic指令,即这一步并没有Desugar:

怎么在Android中兼容Java 8语法

app:transformDexArchiveWithDexMergerForDebug(对应dx)任务之后,再对应build/intermediates/transforms/dexMerger目录找第0个classex.dex。

执行$ANDROID_HOME/build-tools/28.0.3/dexdump -d classes.dex >> dexInfo.txt拿到dex信息。

还是选取实例中Lambda表达式1 :Runnable runnable = () -> System.out.println("xixi");来进行分析。

这个dexIno.txt文件非常大,有1.4M,我们通过com.J8Smaple2.J8Sample找到我们J8Sample在dex中位置。
怎么在Android中兼容Java 8语法

新增方法:

怎么在Android中兼容Java 8语法

J8Sample.main方法:

怎么在Android中兼容Java 8语法

图中选中部分,对应就是Lambda表达式1 desugar之后的内容。

翻译成Java的话就变成了:new Lcom/j8sample2/-$$Lambda$J8Sample$jWmuYH0zEF070TKXrjBFgnnqOKc这个生成类的一个对象。类Lcom/j8sample2/-$$Lambda$J8Sample$jWmuYH0zEF070TKXrjBFgnnqOKc对应前面的生成的J8Sample$$Lambda$1类型,只不过数字1变成了Hash值。
怎么在Android中兼容Java 8语法

实现Interface Ljava/lang/Runnable。Lcom/j8sample2/-$$Lambda$J8Sample$jWmuYH0zEF070TKXrjBFgnnqOKc.run方法:

怎么在Android中兼容Java 8语法

到这里,是不是和前面RetroLambda就一样了。

总结

至此,Lambda及其invokedynamic指令、RetroLambda插件、D8编译器各自的原理分析都已经结束了。

相比较Lambda在Java8自己内部的实现:即运行时,在内存中动态生成关联的函数式接口的实例类型,通过BSM-引导方法找到该内存类(字节码层面的反射)。

在Android上的其他三种Desugar方式,原理都是一样的,区别在于时机不同:

  1. RetroLambda将函数式接口对应的实例类型的生产过程,放在javac编译之后,dx编译之前,并动态修改了表达式所属的字节码文件。

  2. Jack&Jill是直接将接口对应的实例类型,直接jack过程中生成,并编译进了dex文件。

  3. D8的过程是在dex编译过程中,直接在内存生成接口对应的实例类型,并将生成的类型直接写入生成的dex文件中。

上述内容就是怎么在Android中兼容Java 8语法,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

怎么在Android中兼容Java 8语法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么在Android中兼容Java 8语法

本篇文章为大家展示了怎么在Android中兼容Java 8语法,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java 8概述Java 8是Java开发语言非常重要的一个版本。Oracle从2014年
2023-06-03

如何解决在Android中使用setButtonDrawable()方法出现的兼容问题

这篇文章给大家介绍如何解决在Android中使用setButtonDrawable()方法出现的兼容问题,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Android setButtonDrawable()的兼容问题解
2023-05-31

怎么在java中扩容数组

这期内容当中小编将会给大家带来有关怎么在java中扩容数组,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. 客户端开
2023-06-14

Java中finally语法怎么用

这篇文章主要介绍了Java中finally语法怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、finally语句块1.注意点:(1)finally语句块可以直接和tr
2023-06-02

Vue2.0在IE11版本浏览器中的兼容性问题怎么解决

本篇内容主要讲解“Vue2.0在IE11版本浏览器中的兼容性问题怎么解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue2.0在IE11版本浏览器中的兼容性问题怎么解决”吧!让IE11支持v
2023-07-05

continue语句怎么在Java中使用

continue语句怎么在Java中使用?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。package com.yiibai;public class UseOfContinue
2023-05-31

在Java语言中什么是构造方法

这篇文章给大家分享的是有关在Java语言中什么是构造方法的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。什么是构造方法带参数的构造方法1.3.1 什么是构造方法在Java语言中,当创建一个对象时,需要对创建的对象做
2023-06-03

怎么在java中使用do-while语句

怎么在java中使用do-while语句?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。java基本数据类型有哪些Java的基本数据类型分为:1、整数类型,用来表示整数的数据
2023-06-14

怎么在Java中调用方法

这期内容当中小编将会给大家带来有关怎么在Java中调用方法,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一、方法调用方法调用的唯一目的:确定要调用哪一个方法方法调用分为解析调用和分派调用二、非虚方法与虚方
2023-06-15

怎么在vue3中使用jsx语法

今天小编给大家分享一下怎么在vue3中使用jsx语法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。背景vue3项目中 推进使
2023-07-05

怎么在Android中利用SpannableString对内容进行格式化

这篇文章将为大家详细讲解有关怎么在Android中利用SpannableString对内容进行格式化,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。要实现的效果:将话题进行变色并且可以点击提示
2023-05-31

Arrays.toString()方法怎么在java中使用

Arrays.toString()方法怎么在java中使用?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。在实际项目中,如果想要把数组中的内容打印出来,直接使用toStrin
2023-06-06

怎么在Java中使用 List方法

怎么在Java中使用 List方法 ?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Java中可变数组的原理就是不断的创建新的数组,将原数组加到新的数组中,下文对Java L
2023-05-31

valueOf方法怎么在java中使用

这篇文章将为大家详细讲解有关valueOf方法怎么在java中使用,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Java的特点有哪些Java的特点有哪些1.Java语言作为静态面向对象编程语
2023-06-14

instanceof方法怎么在java 中使用

这篇文章给大家介绍instanceof方法怎么在java 中使用,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是J
2023-05-30

Exchanger方法怎么在Java中使用

这期内容当中小编将会给大家带来有关Exchanger方法怎么在Java中使用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Exchanger的使用方法介绍exchange(V x):阻塞当前线程,直到另外
2023-06-14

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录