Google在去年的IO大会上宣布使用Jack编译器支持Java 8, 支持的很有限,还问题多多.在后来Google开始"弃坑"了,弃坑了当然会有新的替代方案来支持,Android Studio 2.4 Preview预览版本已经更新了对Java 8的支持
Android 文档资料: https://developer.android.google.cn/studio/preview/features/java8-support.html
先来讨论个问题:编程语言会往哪些方法演进?
这里指已经还在的编程语言
- 性能会越来越强,多核,并发...
- 代码语法表达式会越来越精简,简单
- API的调用会越来越简单,易懂,
- 语言的也开始适应一些人为因素而改动(项目需求的更改,项目的开发\版本的迭代速度)
- ....
为什么要问这个问题?当你明白了这些,所有的编程语言的新特性,改进,都只是在这几个大的方向上努力的结果
Studio 2.4版本之前支持Java 8 的两种方式:
- Jack编译器: https://developer.android.com/guide/platform/j8-jack.html
- gradle-retrolambda: https://github.com/evant/gradle-retrolambda (仅支持Lambda表达式)
不过,都会成为过往人烟,因为2.4要来了,这两种方式不用管了,知道有那么回事。下面是官方文档中支持的一些主要特性:
Note:Android对Java 8是不完全支持,打个比方说:如果Java 8发布了100个新特性,可能Android现在支持的只有80个. 有些Java 8的新特性用不了也不要奇怪, 估计后面也会更新, Studio 2.4正式版本还未更新,后面的代码均在IntelliJ IDEA编写
Android支持Java 8的主要特性:
- 默认和静态接口方法
- FunctionalInterface注解
- Lambda表达式
- Function接口
- 方法引用
- Stream API
- 重复注解
- ......
默认和静态接口方法
在Java 8以前,接口不能编写方法的具体实现,而对于已经设计好的接口,一旦发布出去后,就不能轻易更改接口.如果要修改,所有的实现类均要修改. 在Java 8中,接口允许添加默认和静态方法,也可在方法内写实现代码,实现类不用重写该方法. 特性虽然友好,但不要滥用.比如:List接口新增的默认方法sort:
FunctionalInterface注解
FunctionalInterface是一个函数注解,用在接口上,如果这个接口是一个函数式接口,这是一个新名词,只有一个抽象方法的接口,就叫函数式接口. Java可能常用的函数式接口:
- java.lang.Runnable
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.util.function包下所有接口
- ......
@FunctionalInterface用法
这个注解有什么用? 用来限定你的接口只有一个抽象方法,使之成为函数式接口,如果你在添加第二个抽象方法,不好意思,会提示报错,
那什么时候用呢?当你要设计一个函数式接口的时候,然后,这个注解是非必需的. 不是说加了这个注解才是函数式接口,不加不是函数式接口,没有这种说法, 函数式接口可参考:java.util.function包下所有接口.
还有一个问题:上面一直在强调一定是抽象方法, 还有新增的默认方法,静态方法不算,如果非要钻牛角尖,Object的toString,hashCode,equals也不算:
Lambda表达式
什么时候用:当要使用到函数式接口(接口中只有一个抽象方法)的时候,就可以用Lambda表达式,Like this:
- 伪代码: 方法名(函数式接口)
- 比如File中的listFiles(FileFilter filter)
- 比如List中的sort(Comparator<? super E> c)
FileFilter和Comparator都是函数式接口
有什么好处?使代码变得更加紧凑,简洁
代码格式:(parameters参数) -> { statements代码块;}
代码由两部分组成:左侧的重写函数参数部分,右侧的具体实现代码块,用"->"符号连接,暂且称之为Lambda符号
你可能注意到这个重写的函数是有返回值的,上面的代码连return关键字都没有, 这里的逻辑是:如果代码的实现部分只有一句话,不用加return关键字语句,连语句结束符号";"也不用加,
上面有说Lambda代码的格式:(parameters参数) -> { statements代码块;},这断代码还能写成,都没有问题:
Lambda的演进方式更像是这样:干掉接口名,干掉函数以及函数修饰符,在参数和代码块之间插入Lambda符号"->"
Function接口
java.util.function包下定义了几组基础类型的函数式接口以及针对基本数据类型的子接口,个人对这些接口的理解就是:Java对一些常见的函数接口进行了归纳,总结,然后写了几个高度抽象的样板,供自己和开发者使用
- Predicate -- 判断型:传入一个参数,返回一个bool结果,方法为boolean test(T t)
- Consumer -- 消费型:传入一个参数,无返回值,纯消费,方法为void accept(T t)
- Function -- 功能型:传入一个参数,返回一个结果,方法为R apply(T t)
- Supplier -- 生产型:无参数传入,返回一个结果,方法为T get()
- UnaryOperator -- 一元操作符,继承Function,传入参数的类型和返回类型相同。
- BinaryOperator -- 二元操作符,传入的两个参数的类型和返回类型相同. 继承BiFunction
方法引用
首先,这个方法引用是为Lambda表达示服务的, 用来替换Lambda表达式, 使代码更加紧凑,并且具有可读性
**定义: **方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法,提供了一种引用而不执行方法的方式,方法引用的操作符是双冒号"::"
先来个匿名函数,Lambda表达式,方法引用对比的例子, 还是用上面的FileFilter接口:
官方文档上提到的几个种类:上面示例使用的引用静态方法
- 引用静态方法:ClassName::staticMethodName
- 引用实例上的实例方法:instanceReference::instanceMethodName
- 引用特定类型上的实例方法:ClassName::methodName
- 引用构造方法:Class::new
上面已经提到了方法引用是干什么的?现在来回答另外一个问题:到底什么时候用?或者,这个引用的方法该怎么写?关注两点:保证函数式接口中重写方法和需要引用方法中的参数和返回值一致,即可使用方法引用
1.静态方法引用 ClassName::staticMethodName(上图)
2.实例方法引用 instanceReference::instanceMethodName
this是当前实例对象的引用
静态方法引用和实例方法引用的区别就是:静态方法引用加了关键字static.你这不是废话吗?这不是重点:重点是来理解方法引用的操作符“::”,它和方法调用("类名.静态方法")的“.”操作符非常相似,只是换了一个符号,然后起了一个好听的名字--方法引用操作符
android.app.dialog中也有段方法引用的代码:
这里来说另一个问题:方法引用具有可读性
在Android的代码中Google不惜为定义一个方法名写上三四十个字母,其实是一句话,其中单词还不带有缩写的,目的只有一个,方便开发者在使用该API容易理解
关联到这里呢:方法引用的方法名是自定义的,当你看到dismissDialog时,已经知道这个线程是干嘛的了,还有上面说过:方法引用是为了替换Lambda表达式的,这个可读性也是对Lambda表达式而言
private final Runnable mDismissAction = this::dismissDialog;
private final Runnable mDismissAction = () -> {
//balabala
//balabala
//balabala
};
3.通过类型引用实例方法 ClassName::methodName
官网的示例代码:方法引用String::compareToIgnoreCase会把方法中的参数(String o1, String o2),然后这个方法引用会去调用o1.compareToIgnoreCase(o2); 没看出个所以然来,你们有什么特别的理解可以留言.
Barbara
James
John
Linda
Mary
Michael
Patricia
Robert
4.构造方法引用 Class::new
这里写了一个匿名表达式-->Lambda表达式-->到方法引用的演进,Person实体类,PersonFactory函数式接口,当函数接口中方法参数和构造方法参数一致时,这时候可以考虑使用方法引用中的,构造方法引用
如果你有天看到了这断代码,一开始会有点难懂
PersonFactory personFactory = Person::new;
这时候代码要倒过来看:方法引用-->Lmabda表达式-->匿名表达式,引用了Person的构造方法,并把函数式接口中方法参数传了过去
PersonFactory personFactory = Person::new;
personFactory = (firstName, lastName) -> new Person(firstName, lastName);
personFactory = new PersonFactory() {
@Override
public Person create(String firstName, String lastName) {
return new Person(firstName, lastName);
}
};
Stream API
首先,这个Stream API是针对数据容器(集合)的操作, 先来看个例子:扫描一个文件夹,获取视频文件,对大小进行限制,然后对取前面9个,在然后,通过文件名对文件进行排序,然后放到List集合中来:
三种写法对比:谁是你的菜?
- Java 8以前
- Stream + Lambda 表达式
-
Stream + Method References
怎么用?
- 对于Collection以及子类(Set,List)调用实例方法.stream()和parallelStream(),区别是前者是顺序,另一个是并发
- 对于数组使用Arrays.stream(Object[]); (上面示例代码中用法)
- Stream提供的静态方法Stream.of(Object[])/IntStream.range(int, int)/[Stream.iterate(Object, UnaryOperator)
- BufferedReader的实例方法lines():获取文件行的流对象
- Random的实例方法ints();获取随机数流对象
- ....
流操作:其实更像是工厂的流水线,把数据容器(集合/数组)中的子元素送上流水线,然后对子元素进行一系列的操作(过滤/排序/去重...),最后打包.
这里有两个概念:中间操作,最终操作
- 中间操作返回Stream流对象,一次处理可以执行多个中间操作
- 最终操作只能有一个
常用操作:
重复注解
对一个元素添加多次同一个类型注解,个人没什么用,一般写框架会用的比较多吧