这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战
函数式接口是什么
函数式接口是指接口中有且仅有一个抽象方法,这个抽象方法的意义在于表达某种行为。
比如我们熟知的 Supplier
,仅仅只有一个抽象的 get 方法。
public interface Supplier<T> {
T get();
}
并且一般函数式接口会用 @FunctionalInterface
注解标注出来,如果不满足会标红。
定义里面的限定只是针对的抽象方法,所以下面的情况也满足函数式接口的定义。
- 接口中可以有多个默认实现,但是默认实现需要用 default 关键字显示标注。
@FunctionalInterface
public interface MyInterface {
String getName();
//这是默认实现的方法,并不违反函数接口的规则。
default int getAge(){
return 1;
}
}
- 接口可以包含多个静态方法
@FunctionalInterface
public interface MyInterface {
String getName();
//这是静态的方法,并不违反函数接口的规则。
static int getAge() {
return 1;
}
}
- 接口可以包含和
Object
的public
一样方法签名的方法,但是此方法需要是抽象的
public interface MyInterface {
String getName();
//该方法的方法签名与Object的相同
//并且是抽象的,因为实现此接口的实现类,必然会提供方法的实现。
int hashCode();
}
函数式接口的创建
函数式接口的实例有三种方式创建:Lambda 表达式、方法引用和构造方法引用
因为函数式接口只有一个抽象方法,所以 Lambda 表达式
实际代表的行为就是接口中的抽象方法,也从侧面说明了 Lambda 表达式不能脱离函数式接口的语境
Lambda 表达式
Lambda 表达式和函数式接口有着天然的联系,Lambda 表达式的基本结构如下:
(Type1 arg1 , Type2 arg2, ...) -> { body }
上面的基本结构就说明了 Lambda 表达式 的类型,和我们熟知的类型系统一样,Lambda 表达式也有类型,入参和出参就标定的表达式的类型。
比如:
(int a,int b)-> a + b 的类型为(int,int)-> int
(int a,int b)-> println(a + b) 的类型为(int,int)-> Unit
为了书写方便,Lambda 表达式 也有下面的简化:
- 如果参数可以通过类型推导出来,那么参数类型可以不写
(arg1 , arg2, ...) -> { body }
- 如果参数只有一个,可以将括号省略
arg1 -> { body }
- 如果 body 只有一行代码,可以省略花括号
arg1 -> println(arg1)
JDK 提供的函数式接口有哪些
JDK 中提供了一些基本的函数式接口,并用这些接口对以前的代码进行了重构。
接受一个参数,不返回结果的 Consumer
/**
* 这个函数式接口代表一种操作行为:无返回结果的单一参数操作
* 接口中的抽象方法,就是操作行为
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* 对给定的参数进行操作
*/
void accept(T t);
/**
* 返回一个组合的 Consumer。对t参数执行完给定的行为后,再去执行after行为。
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
我们以集合遍历为例,看一下使用:
@Override
public void forEach(Consumer<? super E> action) {
final int expectedModCount = modCount;
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);//第一处
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
forEach 是 Java 8 中新增的集合遍历方法,就是在 for 循环中对每一个元素调用 accept。因为遍历只有操作行为,没有输出行为,并且遍历的入参是唯一的,类型是 (T t)-> Unit
具体的使用,以集合遍历为例
public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
list.forEach(item -> System.out.println());
}
}
接受一个参数,返回一个结果的 Function
/**
* 接受一个 T 类型的参数并返回一个 R 类型的结果,T 与 R 可以相同类型也可以不同类型
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* t 参数去执行给定的行为
*/
R apply(T t);
/**
* 返回一个符合函数,在本实例行为执行前,先去执行 before 的行为,将 before 的结果作为本实例行为的输入
* 因此,before 的结果类型一定要是T类型的子类或本身。
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
//先执行before,再执行本实例的行为
return (V v) -> apply(before.apply(v));
}
/**
* 返回一个符合函数,在本实例行为执行后,紧接着去执行 after 的行为,将本实例的结果作为 after 行为的输入
* 因此,after 的输入一定是本实例结果的父类型或本身
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
//先执行本实例,再执行 after
return (T t) -> after.apply(apply(t));
}
/**
* 返回一个参数无操作的行为
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
这个行为我们看起来就是一个 map 的行为,将一个东西转成另一个东西。
map 是 Java 8 中新增的集合转换方法,语义就是将数组中的元素进行转换。
所以它的参数类型就是下面的 Function
:
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));//第一处
}
};
}
};
}
第一处的代码就是直接调用了 mapper 的 apply。它的使用场景就是需要对元素进行转换操作的时候,比如 RxJava 的 map 操作符等等。
接受两个参数,返回一个结果的 BiFunction
/**
* 是Function思想的特殊化,接受两个参数,返回一个R类型结果
*/
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* 对 t 和 u 参数执行给定的行为
*/
R apply(T t, U u);
/**
* 返回一个复合的函数,在执行完本实例行为后,将本实例的结果作为 after 行为的输入,执行after行为。
*/
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
//先执行本实例行为,在执行after行为
return (T t, U u) -> after.apply(apply(t, u));
}
}
我们的反射代理机制,就是这个操作的体现,入参是 类加载器和接口,出参是 代理类。
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// 代码省略 ...
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
上面就是根据 loader
和 interfaces
定义了代理类。
判断参数是否满足断言的 Predicate
除了正常的断言之外,Predicate 还提供了一些常用的操作,比如 与、非、或等等。
/**
* 判断参数是否满足 断言(也就是布尔值)
*/
@FunctionalInterface
public interface Predicate<T> {
/**
* 对给定的参数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);
}
/**
*测试两个实例是否相同(equals相等)
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Java8 中的集合中新增了数组条件移出的操作,这个操作的入参就是 Predicate。
public boolean removeIf(Predicate<? super E> filter) {
//... 省略代码
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) {//第一处
removeSet.set(i);
removeCount++;
}
}
//... 省略代码
return anyToRemove;
}
第一处就是对数组的元素进行断言,是否满足条件,入参是 数组元素
,出参就是 bool值 。
无入参,返回一个结果的 Supplier
/**
*返回一个结果
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*/
T get();
}
这个类型就是一个取值的操作,比如我们的缓存。
private static final class LookupValue<V> implements Value<V> {
private final V value;
LookupValue(V value) {
this.value = value;
}
@Override
public V get() {
return value;
}
//... 省略代码
}
除了这几个,JDK 8 还提供了其他的接口,但是那些接口都是在这几个基础上的变体。
总结
上面介绍了函数式接口是什么,怎么创建函数式接口以及 JDK8 内置的基础接口,有了这些概念,下面就可以源码遨游了。