之前已经介绍过了Java8函数式变成及Lambda表达式,感兴趣可以看看,地址:Java8函数式编程(Lambda表达式)_琅琊之榜PJ的博客-CSDN博客
本文主要介绍一下常用的接口及用法,先来看一个表格:
本文主要选取几个常用的:Supplier<T>、Consumer<T>、Function<T,R>、Predicate<T>
在此之前,再来了解一下@FunctionalInterface注解。
一、@FunctionalInterface注解
一种信息注释类型,用于指示接口类型声明是Java语言规范定义的函数接口。从概念上讲,函数接口只有一个抽象方法。由于默认方法有一个实现,所以它们不是抽象的。如果一个接口声明了一个覆盖java.lang.Object的一个公共方法的抽象方法,那么这也不计入该接口的抽象方法计数,因为该接口的任何实现都将具有来自java.lang.Oobject或其他地方的实现。
注意,函数接口的实例可以使用lambda表达式、方法引用或构造函数引用创建。
如果使用此注解类型对类进行注解,则编译器需要生成错误消息,除非类型是接口类型,而不是注解类型、枚举或类。
带注解的类型满足功能接口的要求。
但是,无论接口声明中是否存在FunctionalInterface注释,编译器都会将符合函数接口定义的任何接口视为函数接口。
源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
需要注意的是:函数式接口只有一个抽象方法,接口声明覆盖 java.lang.Object的抽象方法,不会进行抽象方法计数。这对后面介绍几个常用的接口很有用!!
二、Supplier<T>
先看一下源码:
/**
* 表示结果的供应者。
* 没有要求每次调用供应者时都返回新的或不同的结果。
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*/
T get();
}
Supplier<T> 接口源码中只有个get()方法。每次调用get()方法,就会调用构造方法,获取对象实例。
示例:每次调用get()方法都返回一个整数:
Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
return new Random().nextInt();
}
};
System.out.println(supplier.get());
结合Lambda表达式应用:
List<Integer> integers = makeList(10, () -> (int) (Math.random() * 10));
integers.forEach(System.out::println);
private List<Integer> makeList(int num, Supplier<Integer> supplier){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
list.add(supplier.get());
}
return list;
}
结合方法引用应用:
Supplier<Double> supplier1 = Math::random;
System.out.println(supplier1.get());
三、Consumer<T>
Java源码:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Consumer<T>表示接受单个输入参数而不返回结果的操作。java.util.function.Consumer 接口则正好与上面的Supplier<T> 接口相反,它不是生产一个数据,而是消费一个数据, 其数据类型由泛型决定。
该类有两个方法
- void accept(T t);【抽象方法】
该方法对给定的参数执行此操作。
- default Consumer<T> andThen(Consumer<? super T> after)【默认实现方法】
返回一个组合的Consumer,该Consumer依次执行此操作和after操作。如果执行任何一个操作都会引发异常,则会将异常中继到组合操作的调用方。如果执行此操作会引发异常,则不会执行after操作。
1、抽象方法accept的使用
该方法消费一个指定泛型的数据:
import java.util.function.Consumer;
public class Demo01Consumer {
public static void main(String[] args) {
consumerString(s -> System.out.println(s));
}
private static void consumerString(Consumer<String> function) {
function.accept("Hello");
}
}
运行程序,控制台输出:Hello
2、默认方法andThen的使用
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
提示:java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出 NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合的情况:
import java.util.function.Consumer;
public class Demo02Consumer {
public static void main(String[] args) {
consumerString(
// toUpperCase()方法,将字符串转换为大写
s -> System.out.println(s.toUpperCase()),
// toLowerCase()方法,将字符串转换为小写
s -> System.out.println(s.toLowerCase())
);
}
private static void consumerString(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
}
运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的组合。
四、Function<T,R>
源码如下:
@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<T,R>接口的作用的是用来表现一个参数类型为T、返回值类型为R的函数(方法)。
Function 接口是一个功能型接口,是一个转换数据的作用。接收一个T参数,返回一个R结果
Function 接口实现 apply 方法来做转换。
一个简单的示例:
Function<String, Boolean> function = p -> p.length() == 5;
Stream<Boolean> stream = stringList().stream().map(function);
默认方法1:<V> Function<V, R> compose(Function<? super V, ? extends T> before)
返回一个组合函数,该函数首先将before函数应用于其输入,然后将此函数应用于结果。如果对任意一个函数的求值引发异常,则会将异常中继到组合函数的调用方。
默认方法2:<V> Function<T, V> andThen(Function<? super R, ? extends V> after)
返回一个组合函数,该函数首先将此函数应用于其输入,然后将after函数应用于结果。如果对任意一个函数的求值引发异常,则会将异常中继到组合函数的调用方。
静态方法:static <T> Function<T, T> identity()
返回一个始终返回其输入参数的函数。
五、Predicate<T>
源码:
@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);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Predicate<T>常用的四个方法:
boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
default Predicate<T> negate():返回一个逻辑的否定,对应逻辑非
default Predicate<T> and(Predicate other):返回一个组合判断,对应短路与
default Predicate<T> or(Predicate other):返回一个组合判断,对应短路或
Predicate<T>接口常用于判断参数是否满足指定的条件
最常见的应用是在集合Stream流过滤条件中使用:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class LambdaStudy
{
public static void main(String[] args) {
//初始化list集合
List<String> list = new ArrayList<String>();
list.add("测试数据1");
list.add("测试数据2");
list.add("测试数据3");
list.add("测试数据12");
//使用λ表达式遍历集合
list.forEach(s -> System.out.println(s));
//结合Predicate使用和过滤条件筛选元素
Predicate<String> contain1 = n -> n.contains("1");
Predicate<String> contain2 = n -> n.contains("2");
//根据条件遍历集合
list.stream().filter(contain1).forEach(n -> System.out.println(n));
list.stream().filter(s -> contain1.test(s)).forEach(s -> System.out.println(s));
list.stream().filter(contain1.and(contain2)).forEach(n -> System.out.println(n));
list.stream().filter(contain1.or(contain2)).forEach(n -> System.out.println(n));
//将过滤后的元素重新放到一个集合中
List<String> newList = list.stream().filter(contain1.and(contain2)).collect(Collectors.toList());
newList.forEach(s -> System.out.println(s));
}
}