看《Java 8 in Action》,并将体会记录下来,方便后面查阅。
可能比较零散,都后面有时间和精力再分类整理,暂时先分条列出。
1. 新特性
lambda表达式
stream api
函数式编程
接口可以定义默认实现的方法
Optional
2. 方法引用
方法引用“::”,可作为方法的参数。
在java 8之前,如果需要获取隐藏的文件,可能需要这么写:
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
public boolean accept(File file) {
return file..isHidden();
}
});
而java 8,可以这么写:
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
同时还支持匿名函数,比如要获取非隐藏文件,可以这么写:
File[] notHiddenFiles= new File(".").listFiles((File f) -> !f.isHidden());
3. java 8的stream api使多核和并行处理更加自然
不使用并行:
import static java.util.stream.Collectors.toList;
List heavyApples = inventory.stream().filter((Apple a) -> a.getWeight() > 150).collect(toList());
使用并行:
import static java.util.stream.Collectors.toList;
List heavyApples = inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150).collect(toList());
4. 接口可以定义默认实现的方法
想想这么一个场景,一个对外使用的接口,某次升级需要增加一个方法,这个方法对大部分应用来说只需使用默认的实现即可,但是这个接口使用非常广泛(比如java.util.List),那么如果升级api,所有实现该接口的类都需要去实现下这个方法,可想而知有多崩溃。
java 8支持了给接口方法指定默认的实现,从而解决了这个问题,关键字是default,写法如下:
default void sort(Comparator super E> c) {
Collections.sort(this, c);
}
由于java支持实现多接口,那如果多接口都有同名的默认方法实现,可能会有一些问题,如何避免或解决的后面看到了再补充。
5. Behavior parameterization
行为参数化?
为了适应不断变化的需求。
6. lambda语法
(parameters) -> expression
或
(parameters) -> {statements;}
其中parameters可以为空。
expression不需要写return,隐式返回;而statements中需要(如果有返回值)。
7. 功能接口(functional interface)
只有一个抽象方法的接口称作功能接口。如Comparator、Runnable等。
即使包含多个默认实现的方法,只要保证只有一个抽象接口,还是可以叫做功能接口。
对于功能接口,可以使用lambda表达式简化书写,如:
Runnable r = new Runnable() {
public void run() {
System.out.println("run");
}
}
可以简写成:
Runnable r = () -> System.out.println("run");
常用的功能接口:
功能接口 | 功能描述符 | 基本类型专用 |
---|---|---|
Predicate |
T -> boolean | IntPredicate, LongPredicate, DoublePredicate |
Consumer |
T -> void | IntConsumer, LongConsumer, DoubleConsumer |
Function |
T -> R | IntFunction LongFunction LongToIntFunction, DoubleFunction ToDoubleFunction |
Supplier |
() -> T | BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier |
UnaryOperator |
T -> T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator |
(T, T) -> T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate |
(L, R) -> boolean | |
BiConsumer |
(T, U) -> void | ObjIntConsumer ObjDoubleConsumer |
BiFunction |
(T, U) -> R | ToIntBiFunction ToDoubleFunction |
可以看到每种功能接口都基本提供了一些支持基本类型的专用接口。
由于泛型的特性,不支持基本类型,java提供了自动装箱机制,可以自动转换成对应的包装类型(如int与Integer),但是这种转换会消耗性能(装箱的对象会放到堆中)。
于是java提供了泛型对应的专用接口,使输入或输出直接对应基本类型,避免了自动装箱。
8. @FunctionalInterface
如果要编写的一个接口是功能接口,可以加上这个注解。
当实现并非是功能接口时,编译的时候会报错。
9. lambda表达式的类型省略
由于参数的类型可以从泛型定义中获取,所以在书写lambda表达式的时候,参数类型可以省略(当只有一个参数的时候,括号也可以省略),如:
List greenApples = filter(inventory, a -> "green".equals(a.getColor()));
Comparator c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
有时候带上类型定义更易读,而有时候省略更易读。
什么时候带上什么时候省略,根据情况而定,或者团队进行约定。
10. lambda使用本地变量
lambda可以访问本地变量,但是不能修改本地变量。
需要将本地变量声明为final或事实上的final(即没有声明final,但是表现出了final的特点)。
实例变量不受此限制。
实例变量是放在堆上的,而本地变量是在栈上的,如果允许修改本地变量,当lambda放到线程中时,可能存在已被回收的资源的情况。
11. 方法引用
方法引用可以复用已有的方法,就像lambda表达式一样作为方法的参数。
可以将方法引用看成是lambda表达式的语法糖, 功能相同但是书写更简短,在某些场景下可读性提高。
三种写法:
1)静态方法引用,如Integer::parseInt;
2)实例方法引用,任意类型,如String::length;
3)实例方法引用,已有对象,如存在一个对象xxx,有一个getValue方法,则可以xxx::getValue。
2)与3)的区别可以理解为,2)是lambda函数定义的参数,3)是上下文中已有的对象。
12. 构造方法引用
构造方法使用关键字new。
无参构造方法Apple(),Apple::new;
1个参数的构造方法Apple(Integer weight),适用于Function
2个参数的构造方法Apple(Integer weight, String color),适用于BiFunction
13. lambda表达式的组合
功能接口和一些常用的工具类中一般都有默认方法,方便对lambda表达式进行组合。
Comparator反序:
inventory.sort(comparing(Apple::getWeight).reversed());
Comparator连接:
inventory.sort(comparing(Apple::getWeight).thenComparing(Apple::getColor));
Predicate:
negate、and、or。
优先级从左到右,如a.or(b).and(c)等同于(a || b) && c。
Function:
andThen(f.andThen(g) == g(f(x)))
compose(f.compose(g) == f(g(x)))