共计 3281 个字符,预计需要花费 9 分钟才能阅读完成。
前情提要
- 深入探寻 JAVA8 part1:函数式编程与 Lambda 表达式
看此文前,不熟悉函数式编程和 Lambda 表达式的可以先看一下上文回忆一下。
本文将会简单介绍 Java8 中内置的一些函数式接口
回顾函数式接口
函数式接口就是只定义一个抽象方法的接口。在 JAVA8 以前,就有很多符合函数式接口定义的接口。
// 比较器
public interface Comparator<T> {int compare(T o1, T o2);
}
// 多线程接口
public interface Runnable{void run();
}
因为 JAVA8 中还引入了默认方法的概念,所以即使接口中有多个默认方法,只要接口之定义了一个抽象方法,就满足函数式接口的定义。
JAVA8 中对这些可以定义为函数式接口的接口加了一个 @FuncationalInterface
注解。如果一个接口中定义了多个抽象方法,又添加了这个注解,则会在编译时抛出错误提示。
Consumer
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {void accept(T var1);
default Consumer<T> andThen(Consumer<? super T> var1) {Objects.requireNonNull(var1);
return (var2) -> {this.accept(var2);
var1.accept(var2);
};
}
}
这是 JAVA8 中对 Consumer 的定义,该函数式接口可以接收一个 T 类型的数据,并对该数据进行操作。JDK 中一个典型的例子是 forEach 中对 Consumer 的使用,下面给出了 ArrayList 中的 forEach 源码。
@Override
public void forEach(Consumer<? super E> consumer) {checkNotNull(consumer);
for (E e : array) {consumer.accept(e);
}
}
forEach 的接口定义中传入了一个 Consumer 接口,并且调用 Consumer 的 accept 方法对数组中的每个元素进行处理。加入这是一个 String 数组,则可以使用如下的方式进行调用
list.forEach((String s) -> System.out::println);
在 Consumer 的接口定义中,还有一个 andThen 的默认方法,后面会再介绍一下这个默认方法。
因为 Consumer 这个接口使用了泛型,因此只能使用基础类型的封箱类型,如 Integer,Long 等。如果是对基础类型的元素进行处理,可能会出现大量的封箱拆箱的操作,造成性能损耗。为了解决这个问题,JAVA 也提供了基础类型的对应的 Consumer 接口,如 IntConsumer:
@FunctionalInterface
public interface IntConsumer {void accept(int value);
default IntConsumer andThen(IntConsumer after) {Objects.requireNonNull(after);
return (int t) -> {accept(t); after.accept(t); };
}
}
Predicate
@FunctionalInterface
public interface Predicate<T> {boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {return (t) -> !test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Predicate 函数式接口定义了 test 抽象方法,它会对 T 对象执行判断逻辑,并返回布尔类型的判断接口。
还是以 ArrayList 中的一个使用场景为例。ArrayList 中提供了一个 removeIf 方法,该方法传入了 Predicate 接口,并利用该接口判断是否要删除这个对象:
public boolean removeIf(Predicate<? super E> filter) {checkNotNull(filter);
E[] newArray = null;
int newIndex = 0;
for (int index = 0; index < array.length; ++index) {E e = array[index];
// 使用 Predicate 的 test 方法判断是否要删除该对象
if (filter.test(e)) {if (newArray == null) {newArray = ArrayHelper.clone(array, 0, index);
newIndex = index;
}
} else if (newArray != null) {newArray[newIndex++] = e;
}
}
if (newArray == null) {return false;}
array = newArray;
return true;
}
当然了,同 Consumer 一样,它也提供了很多可以传入基础类型的 Predicate 接口,如 IntPredicate,DoublePredicate 等
Function
@FunctionalInterface
public interface Function<T, R> {R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {return t -> t;}
}
Function 中定义了 apply 抽象方法,该抽象方法会接受一个 T 类型的对象,并转化为 R 类型的对象返回。使用方法和上面也没啥区别,这里就不具体赘述了。当然了,Function 也为基础类型做了很多扩展,比如 IntToDoubleFunction 就可以将 int 转化为 double 型,还有 ToDoubleFunction<T> 则支持将 T 对象转化为 double 基础型。
复合 Lambda 表达式
复合 Lambda 表达式是指将多个同类型的 Lambda 表达式按照一定语法进行组合,生成新的 Lambda 表达式。以比较基础的 Predicate 作为例子。Predicate 中有以下几个默认方法:and,negate,or
,分别对应与,否定和或。
举个例子,现在有两个 Predicate 分别是判断订单的状态是否为已支付以及订单的实付金额是否大于 100。两个 Predicate 如下:
Predicate<Order> p1 = (Order o) -> o.isPaid;
Predicate<Order> p2 = (Order o) -> o.actualFee > 100;
假如现在想要判断是已支付且实付金额大于 100 的订单,则新的 Predicate 可以通过上面两个 Predicate 组合生成,利用 and 默认方法:
Predicate<Order> p3 = p1.and(p2);