笨读JDK8源码(1)-Function和@FunctionalInterface

2019/09/16

@FunctionalInterface注解

源码翻译

/**
 * 一个用于标记Java语言规范中<i>函数式接口</i>的信息注解。
 * <p>
 * 从概念上讲,函数式接口是有且仅有一个抽象方法的接口。由于默认方法具
 * 有实现,所以它们不是抽象的。如果接口声明了覆写{@code java.lang.Object}
 * 的公有方法的方法,那么这些方法也不计作抽象方法,因为接口的任何实现
 * 都是{@code java.lang.Object}的子类。
 *
 * <p>函数式接口的实例可以用Lambda表达式、方法引用或构造器引用来创建。
 *
 * <p>对于使用了这个注解的类型,不满足以下条件时编译器会生成错误信息:
 *
 * <ul>
 * <li> 该类型是接口,而非注解、枚举、类。
 * <li> 该类型满足函数式接口的要求。
 * </ul>
 *
 * <p>编译器将自动把满足函数式接口定义的任何接口视作函数式接口,无论
 * 接口是否被{@code FunctionalInterface}注解标记。
 *
 * @jls 4.3.2. The Class Object
 * @jls 9.8 Functional Interfaces
 * @jls 9.4.3 Interface Method Body
 * @since 1.8
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {
}

详解

@FunctionalInterface 注解用于显式地标记函数式接口,它是一个作用于类型的运行时注解。即使不使用 @FunctionalInterface 注解标记接口,编译器也会把满足函数式接口要求的接口视作函数式接口;而使用了 @FunctionalInterface 注解后,如果接口不满足函数式接口的要求,编译器会给出错误信息。如果我们要设计一个函数式接口,最好加上这个注解以作种规约,同时也能让编译器帮我们检查接口中是否有多于一个的抽象方法。

所谓函数式接口,指的是有且仅有一个抽象方法的接口。比如:

// 有且只有一个抽象方法void hello(),所以是函数式接口
@FunctionalInterface
public interface Example {
    // 抽象方法,由实现类负责实现
    void hello();

    // 默认方法,具有默认实现
    default void hi() {
        System.out.println("你好");
    }
}

这个 Example 接口的 hello() 方法就是一个抽象方法,相对应的,hi() 方法就不是一个抽象方法。因此 Example 接口就是一个函数式接口。

如果在接口中声明覆写了 Object 类公有方法的方法,比如 equals(Object)toString(),这样的方法也不会被视作是抽象方法。比如上面的 Example 接口,它在添加了equals(Object)toString() 之后仍然是一个函数式接口:

// 有且只有一个抽象方法void hello(),所以是函数式接口
@FunctionalInterface
public interface Example {
    // 抽象方法,由实现类负责实现
    void hello();

    // 默认方法,具有默认实现
    default void hi() {
        System.out.println("你好");
    }

    // 覆写Object类的公有方法
    @Override
    String toString();

    // 覆写Object类的公有方法
    @Override
    boolean equals(Object o);
}

除了定义实现类和通过 new 匿名创建以外,函数式接口还可以用Lambda表达式、方法引用或构造器引用来创建。

比如像这样创建 Example 实例:

public class Main {
    public static void main(String[] args) {
        // 通过Lambda表达式创建,打印“你好”并换行
        Example example = () -> System.out.println("你好");
        example.hello();

        System.out.println("-------------");

        // 通过方法引用创建,打印换行
        example = System.out::println;
        example.hello();

        System.out.println("-------------");
    }
}

运行结果:

你好
-------------

-------------

Function接口

源码翻译

/**
 * 表示接收一个参数并返回一个结果的函数
 *
 * <p>这是一个<a href="package-summary.html">函数式接口</a>,
 * 其唯一抽象方法为{@link #apply(Object)}。
 *
 * @param <T> 函数输入的类型
 * @param <R> 函数结果的类型
 * @author jdk
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {

    /**
     * 对给定的参数指定函数
     *
     * @param t 函数的参数
     * @return 函数的结果
     */
    R apply(T t);

    /**
     * 返回一个一个先执行给定前置函数再执行本函数的组合函数,组合函数执
     * 行时将把输入作为前置函数的输入,把前置函数的输出作为本函数的输入,
     * 最后将本函数的执行结果作为组合函数的结果。如果任一函数抛出异常,
     * 则将异常抛给组合函数的调用者。
     *
     * @param <V>    前置函数的输入类型,即组合函数的输入类型
     * @param before 在组合函数中,先于本函数执行的前置函数
     * @return 一个先执行前置函数再执行本函数的组合函数
     * @throws NullPointerException 若前置函数为空则抛出
     * @see #andThen(Function)
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        // 前置函数判空
        Objects.requireNonNull(before);
        // 返回一个先执行前置函数,然后将其输出作为本函数输入并执行的组合函数
        return (V v) -> apply(before.apply(v));
    }

    /**
     * 返回一个把本函数作为前置函数并把给定函数作为后置函数的组合函数。
     * 如果任一函数抛出异常,则将异常抛给组合函数的调用者。
     *
     * @param <V>   后置函数的结果类型
     * @param after 在组合函数中后于本函数执行的后置函数
     * @return 一个先执行本函数再执行给定函数的组合函数
     * applies the {@code after} function
     * @throws NullPointerException 若后置函数为空则抛出
     * @see #compose(Function)
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        // 后置函数判空
        Objects.requireNonNull(after);
        // 返回一个先执行本函数,然后将输出作为后置函数并执行的组合函数
        return (T t) -> after.apply(apply(t));
    }

    /**
     * 返回一个总是将输入作为输出的函数
     *
     * @param <T> 函数输入的类型
     * @return 一个总是将输入作为输出的函数
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

详解

Function 接口是一个函数式接口,表示接收一个参数并返回一个结果的函数。Function 实例可以作为方法参数以实现行为传递,在 JDK8 的许多地方(比如 Collection)存在以 Function 作为参数的方法。

使用示例:

public class Main {

    /**
     * 遍历传入的列表并对每个元素执行指定操作,返回每个对元素的操作结果
     *
     * @param list     列表
     * @param function 操作函数
     * @param <T>      列表的元素类型
     * @param <R>      操作函数的输出类型
     * @return 操作结果列表
     */
    public static <T, R> List<R> example(List<T> list, Function<T, R> function) {
        // 用JDK7的Objects类进行判空
        Objects.requireNonNull(list);
        Objects.requireNonNull(function);
        // 创建结果列表
        List<R> result = new ArrayList<>(list.size());
        // 遍历操作
        for (T t : list) {
            result.add(function.apply(t));
        }
        return result;
    }

    public static void main(String[] args) {
        List<Integer> input = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7);
        // 创建操作函数,判断整数的奇偶性并以字符串形式返回结果
        Function<Integer, String> function = i -> i % 2 == 0 ? "偶数" : "奇数";
        List<String> result = example(input, function);
        // 遍历打印结果,这里传的是方法引用
        result.forEach(System.out::println);
    }
}

运行结果:

偶数
奇数
偶数
奇数
偶数
奇数
偶数
奇数

Function 接口中有两个默认方法和一个静态方法。

静态方法 identity() 可以创建一个直接返回输入参数的函数,在某些情况下可以提供一定的便利(比如使用流的时候)。由于是接口静态方法,它不会被继承,不能通过实例名来调用,只能通过 Function.identity() 调用。

两个默认方法用于创建组合函数,可以把 Function 实例按照期望的先后顺序组合起来,先执行一个后执行另一个。使用这两个方法可以方便地创建函数链。其中 Function<V, R> compose(Function<? super V, ? extends T> before) 可以组合一个前置函数,而 Function<T, V> andThen(Function<? super R, ? extends V> after) 可以组合一个后置函数。

默认方法使用示例:

public class Main {

    /**
     * 遍历传入的列表并对每个元素执行指定操作,返回每个对元素的操作结果
     *
     * @param list     列表
     * @param function 操作函数
     * @param <T>      列表的元素类型
     * @param <R>      操作函数的输出类型
     * @return 操作结果列表
     */
    public static <T, R> List<R> example(List<T> list, Function<T, R> function) {
        // 用JDK7的Objects类进行判空
        Objects.requireNonNull(list);
        Objects.requireNonNull(function);
        // 创建结果列表
        List<R> result = new ArrayList<>(list.size());
        // 遍历操作
        for (T t : list) {
            result.add(function.apply(t));
        }
        return result;
    }

    public static void main(String[] args) {
        List<Integer> input = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7);
        // 创建操作函数,把整数乘以3
        Function<Integer, Integer> function = i -> i * 3;
        // 组合一个后置函数,把整数加1
        function = function.andThen(i -> i + 1);
        // 遍历执行操作
        List<Integer> result = example(input, function);
        // 遍历打印结果,这里传的是方法引用
        result.forEach(System.out::println);
    }
}

运行结果:

1
4
7
10
13
16
19
22

总结

@FunctionalInterface 注解和 Function 接口的源码都非常简单,唯一需要重点关注的是函数式接口的定义和要求。而看懂了 Function 接口,BiFunction 接口就很容易理解了。


(转载本站文章请注明作者和出处)

Show Disqus Comments

Post Directory