前面几个章节我们已经介绍了如何定义函数式接口,以及函数式接口的实例的实现方式。其实 JAVA8 为我们定义好了许多函数式接口,方便我们使用。这些函数式接口都在 java.util.function.Function 包中。还有一些在 JAVA8 之前引入的接口在 JAVA8 引入后也被标记成函数式接口了,比如 Runnalbe 和 Callable。
Consumer
顾名思义,消费者,消费者就是接收一个东西然后消费掉,没有返回。会接受一个输入,并且不返回任何结果。
这个接口中定义了两个方法,一个是抽象方法 accept,接收一个入参,不返回任何结果。
另一个是默认方法 andThen,接收一个 Consumer 接口。先对参数应用当前 Consumer 接口的 accept 方法,然后再对当前参数应用 after Consumer 的 accept 方法。
//源码
@FunctionalInterface
public interface Consumer {
void accept(T t);
default Consumer andThen(Consumer super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
例子:
因为这个函数式接口定义没有返回值,我们就传入一个字符串然后打印出来。
public static void main(String[] args) {
Consumer consumer = (str)-> System.out.println(str);
//也可以写成 Consumer consumer = System.out::println;
consumer.accept("hello java8");
}
hello java8
我们再测试一下 andThen 方法,andThen 方法返回一个新的 Consumer。
public static void main(String[] args) {
Consumer consumer = (str)-> System.out.println(str);
Consumer newConsumer = consumer.andThen((s)-> System.out.println("after:" +s));
newConsumer.accept("hello java8");
}
hello java8
after:hello java8
最终结果,先使用当前的 Consumer 处理输入字符串,然后在使用 andThen 函数中的入参的 Consumer 处理同一个字符串。
Function
接收一个参数,并且生成一个结果。这个接口除了一个抽象方法之外,还有两个 default 方法,还有一个 static 修饰的静态方法。
抽象方法 apply:接收一个参数,返回一个结果,参数和结果都是泛型,可以自己定义。
默认方法 compose,接收一个 Function 接口,返回一个 Function 接口。先对参数应用 before function,得到结果后应用到当前的 function,形成了两个 function 的串联。当然通过多次 compose 可以形成多个 function 的串联。
默认方法 andThen,与 compose 类似,先对参数应用当前 function,得到结果后应用到 after function。与 compose 类型,也可以多次形成串联。
静态方法 identity,返回一个 输入和输出相同的 function。
//源码
@FunctionalInterface
public interface Function {
R apply(T t);
default Function compose(Function super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static Function identity() {
return t -> t;
}
}
例子:
输入一个字符串,将其全部变为大写之后打印出来。
Function function = String::toUpperCase;
//使用 Lambda 表达式的写法 Function function = str->str.toUpperCase();
System.out.println(f.apply("hello java8"));
我们使用 Function 进行组合,将字符串进行分割和转为大写功能进行组合。andThen 是将 function 的执行结果交给 after function。compose 是先执行 before function,然后将执行结果交给 function。
public static void main(String[] args) {
Function f = String::toUpperCase;
Function after = f.andThen(str->str.substring(0,6));
System.out.println(after.apply("hello java8"));
}
结果:
//先转为大写,然后在分割字符串
HELLO J
Predicate
Predicate:接收一个输入,根据这个输入进行计算或者评估,返回一个 Boolean 类型的输出。
抽象方法 test:需要我们自己实现的方法,用于根据输入参数进行判断。
默认方法 and:与 && 的效果相同,逻辑与,根据两个 Predicate 的 test 结果进行逻辑与运算。都为 ture 的时候才返回 true。
默认方法 negate:与 !效果相同,否定。对执行 test 之后的结果取反。
默认方法 or:与 || 效果相同,逻辑或,根据两个 Predicate 的 test 结果进行逻辑或运算,其中一个为 true 的时候就返回 true。
默认方法 isEqual:返回一个能判断两个参数是否 equal 的函数式接口。根据 Objects 的 equal 的方法。这个方法的意义在于我们可以根据传入的 Object 类型来进行 equals 判断,传入的是 String 就用 String 的 equals,传入对象就用对象的 equals。
//源码
@FunctionalInterface
public interface Predicate {
boolean test(T t);
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate negate() {
return (t) -> !test(t);
}
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static Predicate isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
例子:
判断一个字符串的长度是否大于 5。
public static void main(String[] args) {
Predicate predicate = p->p.length()>5;
System.out.println(predicate.test("hello"));
}
//hello 的长度为 5,结果返回 false
false
打印出集合中的偶数。
public class PredicateTest {
public static void main(String[] args) {
List list = Arrays.asList(1,2,3,4,5,6,7,8);
PredicateTest test = new PredicateTest();
test.conditionFilter(list,item->item%2==0);
}
public void conditionFilter(List list, Predicate predicate){
for(Integer i : list){
if(predicate.test(i)){
System.out.println(i);
}
}
}
}
判断两个字符串是否相等:
//静态方法直接使用接口调用即可
//结果返回 true
System.out.println(Predicate.isEqual("test").test("test"));
//结果返回 false
Predicate p = Predicate.isEqual("hello");
System.out.println(p.test("hello1"));
Supplier
不接受输入,返回一个输出。以前写的没有输入参数的工厂的情况就可以用 Supplier 替代。
抽象方法 get:直接获取输出。
//源码
@FunctionalInterface
public interface Supplier {
T get();
}
例子:
Supplier> supplier = ()->new ArrayList<>();
List l = supplier.get();
当然也可以使用方法引用的方式:
Supplier> s = ArrayList::new;
List l = s.get();
获取到返回值之后我们就可以使用了。
其他
除了这些函数式接口之外,JAVA8 还为我们提供了很多函数式接口来满足我们不同的需求。
比如我们需要两个输入参数和一个返回值该怎么办那?Function 接口目前只支持一个参数一个返回值啊,不用担心,JDK 的设计者们早就想好了,它们定义了 BiFunction
这个函数式接口来满足两个输入参数一个返回值的情况。
BinaryOperator
接收两个操作数,是 BiFunction 在操作数和结果相同时的特例。Consumer 接口也有接收两个操作数的函数式接口BiConsumer
。
除此之外,上面介绍的四大种类的函数式接口也有更具体的实现,比如 Function 有 DoubleFunction(参数是 double),IntFunction(参数是 int),LongFunction(参数是 long),ToDoubleFunction(返回值是 double),ToIntFunction返回值是 int),ToLongFunction返回值是 long)。Consumer,Predicate 和 Supplier 也是同理,就不再赘述了。我们可以根据实际情况选择更为具体的函数式接口。
默认方法与静态方法
通过上面的源码我们会发现接口中有了非抽象方法,默认方法和静态方法,分别用 default 和 static 修饰。
其实这样设计是为了在增加新功能的同时考虑到向后兼容性。这样就能够在顶层接口中定义一些共同的非抽象方法操作。比如 forEach, stream 等等。
默认方法对应实例方法,静态方法对应类方法。
//Collection 接口
default Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
//Iterable 接口
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}