Java 中的函数式接口

131 阅读6分钟

这是我参与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;
  }
}
  • 接口可以包含和 Objectpublic 一样方法签名的方法,但是此方法需要是抽象的
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());
    }
}

上面就是根据 loaderinterfaces 定义了代理类。

判断参数是否满足断言的 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 内置的基础接口,有了这些概念,下面就可以源码遨游了。