阅读 154

Java 8 函数式接口

1. 概述

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

提到函数式接口肯定少不了 lambda 表达式,函数式接口可以隐式的转换为 lambda 表达式。

我们可以选择向各种各样的方法和构造函数传递 lambda 表达式,包括在 Java 8 之前创建的一些方法和构造函数。因为 lambda 表达式在 Java 中表示为函数接口。

2. 熟悉的代码

Thread thread = new Thread(new Runnable() {
  public void run() {
    System.out.println("In another thread");
  }
});
 
thread.start();
 
System.out.println("In main");
复制代码

这段代码应该对它很熟悉了,将 Runnable 的匿名实例传递给构造函数,仔细看这段代码,我们的目的无非是想要将一段代码作为参数传入另一个方法进行使用,但 Java 不像 C++ 或 python 一样,能传入代码段。但 Java 如何实现的呢?

函数式接口

简单地说,就是 Java 通过实现接口来间接性的将代码段传入使用。

JDK 1.8 之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

JDK 1.8 新增加的函数接口:

  • java.util.function

我们主要了解一下 Java 8 专门新增的函数式接口:function

3. function

重要法则

函数接口有 3 条重要法则:

  1. 一个函数接口只有一个抽象方法。
  2. Object 类中属于公共方法的抽象方法不会被视为单一抽象方法。
  3. 函数接口可以有默认方法和静态方法。

任何满足单一抽象方法法则的接口,都会被自动视为函数接口。这包括 RunnableCallable 等传统接口,以及自己构建的自定义接口。

java.util.function 包中最常用的接口包括 Function<T>Predicate<T>Consumer<T>

对于 java.util.function 包下的接口具体描述可以参考:Java 8 函数式接口

如何使用

来看一个 demo:

public class MainDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        evalNum(list, n -> n % 2 == 0);
    }

    public static void evalNum(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer i : list) {
            if (predicate.test(i)) {
                System.out.println(i + " ");
            }
        }
    }
}
复制代码

我们一点一点的来看:

  • evalNum() 方法的作用很明显,就是将满足 predicate.test()条件即打印出来。
  • predicate.test() 方法具体实现:n % 2 == 0.

这里用到了 lambda 表达式,n -> n % 2 == 0 意思是,参数为 n,实现为 n % 2 == 0

听起来是不是一脸懵逼。没事,我们来看 Predicate<T> 接口就明白了。

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 *
 * @param <T> the type of the input to the predicate
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * AND of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code false}, then the {@code other}
     * predicate is not evaluated.
     *
     * <p>Any exceptions thrown during evaluation of either predicate are relayed
     * to the caller; if evaluation of this predicate throws an exception, the
     * {@code other} predicate will not be evaluated.
     *
     * @param other a predicate that will be logically-ANDed with this
     *              predicate
     * @return a composed predicate that represents the short-circuiting logical
     * AND of this predicate and the {@code other} predicate
     * @throws NullPointerException if other is null
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     * Returns a predicate that represents the logical negation of this
     * predicate.
     *
     * @return a predicate that represents the logical negation of this
     * predicate
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * OR of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code true}, then the {@code other}
     * predicate is not evaluated.
     *
     * <p>Any exceptions thrown during evaluation of either predicate are relayed
     * to the caller; if evaluation of this predicate throws an exception, the
     * {@code other} predicate will not be evaluated.
     *
     * @param other a predicate that will be logically-ORed with this
     *              predicate
     * @return a composed predicate that represents the short-circuiting logical
     * OR of this predicate and the {@code other} predicate
     * @throws NullPointerException if other is null
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * Returns a predicate that tests if two arguments are equal according
     * to {@link Objects#equals(Object, Object)}.
     *
     * @param <T> the type of arguments to the predicate
     * @param targetRef the object reference with which to compare for equality,
     *               which may be {@code null}
     * @return a predicate that tests if two arguments are equal according
     * to {@link Objects#equals(Object, Object)}
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
复制代码

源码很多注释,不理解也没关系,我们只关注有且仅有一个抽象方法:boolean test(T t); 刚刚 demo 中 n % 2 == 0 就是实现了这个方法。

相当于:

public class MainDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        //evalNum(list, n -> n % 2 == 0);
        evalNum(list, new Predicate<Integer>() {
            @Override
            public boolean test(Integer n) {
                return n % 2 == 0;
            }
        });
    }

    public static void evalNum(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer i : list) {
            if (predicate.test(i)) {
                System.out.println(i + " ");
            }
        }
    }
}
复制代码

也就是说,我们根据自己的需求,需要返回一个 Boolean 判断类型的话,实现 Predicate<T> 接口使用即可,假如我们需要输入一个参数并无返回的话,使用Consumer<T> 即可,还有很多接口,需要那种自己实现就行了。当然使用 lambda 表达式会很方便。

4. 自定义接口

如果哪一天,你觉得它的接口已经无法满足你的需求时,那么,你就能自定义接口了,hhh,Java 8 很友好,知道你想要,满足你的个性化定制。

我们只需要做两件事:

  1. 使用 @FunctionalInterface 注释该接口,这是 Java 8 对自定义函数接口的约定。
  2. 确保该接口只有一个抽象方法。

仔细观察 java.util.function 的接口,也一样,都做了两件事,自定义就是那么简单。

使用 @FunctionalInterface 注释可以确保,如果在未来更改该接口时意外违反抽象方法数量规则,您会获得错误消息。

实列就不写了,因为不是很难理解,我们参考参考 Function<T,R>

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}
复制代码

5. 小结&参考资料

小结

官方一点的话来小结一下。

函数式接口代表的一种契约, 一种对某个特定函数类型的契约。 在它出现的地方,实际期望一个符合契约要求的函数。 Lambda 表达式不能脱离上下文而存在,它必须要有一个明确的目标类型,而这个目标类型就是某个函数式接口

我们到底该选择内置函数接口呢,还是自定义函数接口。

其实很好理解,我们可以清楚的看见,内置函数接口的使用并不影响代码的可读性,反而增强了复用性,那么我们何必自己再去造一个呢,有现成的为什么不用呢?

参考资料