前言
本文是学习java8新特性过程产生的一些笔记,参考的资料有:Java8实战,Java 8的新特性—终极版。
1. Lambda表达式
1.1 何为Lamdba表达式
Lambda表达式理解为简洁地表示可传递的一种表达式:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,例如:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
1.2 在哪里使用Lambda表达式
Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
@FunctionalInterface
public interface Functional {
void method();
}
不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
1.3 正确使用Lambda表达式
1.3.1 使用函数式接口传递行为
@FunctionalInterface
public interface Test<T> {
boolean test(T t);
}
1.3.2 执行一个行为
public static <T> List<T> filterApples(List<T> input, Test<T> p) {
List<T> result = new ArrayList<>();
for (T e : input) {
if (p.test(e)) {
result.add(e);
}
}
return result;
}
1.3.3 传递一个Lamdba表达式
List<Apple> result = filterApples(input, (Apple apple) -> apple.getColor().equals("green"));
1.4 函数式接口
表达式之前已经使用过自定义的函数式接口了,它只定义了一个抽象方法。Java API 中已经有了几个函数式接口了,这几个我们可以直接使用,比如Comparable,Runnalble和Callable等都是我们熟悉的接口。在java8的java.util.function包中引入了几个新的函数式接口,如Predicate,Consumer,Function等是比较常用的接口。
1.4.1 Predicate
Predicate接口中定义了一个抽象方法test(),它接受泛型对象,并返回boolean。我们需要一个涉及泛型T的布尔表达式的时候就可以使用这个接口,如:
public static <T> List<T> filterApples(List<T> input,
Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T e : input) {
if (p.test(e)) {
result.add(e);
}
}
return result;
}
List<Apple> result = filterApples(input, (Apple apple) ->apple.getColor().equals("green") &&apple.getWeight() > 2 && apple.getWeight() < 45);
1.4.2 Consumer
Comsumer接口中定义了一个名叫accpet()的抽象方法,接受泛型对象并且返回void,正如接口名字那样,我们可以用它来消费类型为T 的对象,但返回void。比如遍历集合中的元素并打印它。
public static<T> void forEach(List<T> list, Consumer<T>
consumer) {
for (T t : list) {
consumer.accept(t);
}
}
List<Integer> list = Arrays.asList(1,2,5,8,6);
forEach(list, (i) -> System.out.print(i + "\t"));
1.4.3 Function
Function<T,R>接口中定义了一个名叫apply()的抽象方法,接受泛型T的对象并且返回泛型R的对象,如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口。
public static <T, R> List<R> map(List<T> list,
Function<T, R> f) {
List<R> res = new ArrayList<>();
for (T t : list) {
res.add(f.apply(t));
}
return res;
}
List<String> str = Arrays.asList("red", "green", "blue");
forEach(map(str, (s) -> s.length()), (i) -> System.out.print(i + "\t"));
1.4.4 自定义函数式接口
可以使用@FunctionalImterface注解自定义函数式接口
@FunctionalInterface
public interface Test<T> {
boolean test(T t);
}
1.5 方法引用
1.5.1 什么是方法引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。
1.5.2方法引用的形式
方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号)有以下四种形式的方法引用:
类型 | 示例 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用某个对象的实例方法 | containingObject::instanceMethodName |
引用某个类型的任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
2. 函数式数据处理
2.1 Streams流
流是“从支持数据处理操作的原生成的一系列元素“;
流利用内部迭代:迭代通过filter,map,sorted等操作被抽橡掉了。
新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。
Stream API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:
public class Streams {
private enum Status {
OPEN, CLOSED
};
private static final class Task {
private final Status status;
private final Integer points;
Task( final Status status, final Integer points ) {
this.status = status;
this.points = points;
}
public Integer getPoints() {
return points