java8函数式接口@FunctionalInterface讲解

日期:2018-09-26       浏览:1345

一 定义

我们首先来看一下 FunctionalInterface 的定义。
/**
 * An informative annotation type used to indicate that an interface
 * type declaration is intended to be a <i>functional interface</i> as
 * defined by the Java Language Specification.
 *
 * Conceptually, a functional interface has exactly one abstract
 * method.  Since {@linkplain java.lang.reflect.Method#isDefault()
 * default methods} have an implementation, they are not abstract.  If
 * an interface declares an abstract method overriding one of the
 * public methods of {@code java.lang.Object}, that also does
 * <em>not</em> count toward the interface's abstract method count
 * since any implementation of the interface will have an
 * implementation from {@code java.lang.Object} or elsewhere.
 *
 * <p>Note that instances of functional interfaces can be created with
 * lambda expressions, method references, or constructor references.
 *
 * <p>If a type is annotated with this annotation type, compilers are
 * required to generate an error message unless:
 *
 * <ul>
 * <li> The type is an interface type and not an annotation type, enum, or class.
 * <li> The annotated type satisfies the requirements of a functional interface.
 * </ul>
 *
 * <p>However, the compiler will treat any interface meeting the
 * definition of a functional interface as a functional interface
 * regardless of whether or not a {@code FunctionalInterface}
 * annotation is present on the interface declaration.
 *
 * @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 {}
通过定义可以看出:
  • 该注解是信息类注解,用于表示使用的接口是一个 Java 语言规范定义的功能性接口(如果一个接口中包含不止一个抽象方法,添加该注解后编译会报错);
  • 使用该注解的接口只能有一个抽象方法,默认方法、静态方法都不算抽象方法;
  • 接口中重写 Object 的方法也不算抽象方法;
  • 无论接口中是否存在 @FunctionalInterface 注解,编译器都会将满足功能接口定义的任何接口视为功能接口。

二 在 JDK 中的使用

我们经常用到的 Runnable、Comparator、Callback、Callable 等接口都添加了该注解;
// ----------------------------------
// ------------Runnable--------------
// ----------------------------------
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
// ----------------------------------
// ------------Callback--------------
// ----------------------------------
@FunctionalInterface
public interface Callback<P,R> {
    /**
     * The <code>call</code> method is called when required, and is given a
     * single argument of type P, with a requirement that an object of type R
     * is returned.
     *
     * @param param The single argument upon which the returned value should be
     *      determined.
     * @return An object of type R that may be determined based on the provided
     *      parameter value.
     */
    public R call(P param);
}
Lambda 表达式的前身就是匿名函数,没有 Lambda 的时候,我们是这样定义一个匿名函数的:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("匿名函数");
    }
}).start();
使用 Lambda 表达式后就变成了这样子:
new Thread(() -> {
    System.out.println("Lambda");
}).start();
是不是看着更干净清晰了。

三 自定义及其使用

下面我们来自定义一个函数接口:
@FunctionalInterface
public interface FunctionalTest {

    int calculate(int i, int j);

    default String sayHello(String name) {
        return "Hello " + name;
    }

    static void print(String name) {
        System.out.println("Hello " + name);
    }
}
以上我们自定义了一个函数接口,其 calculate 方法用于两个入参的计算,并返回计算结果,具体的计算方法在使用方定义。
接下来我们看一下怎么使用我们自定义的函数接口:
public static void main(String[] args) {
    // 将两个入参做加法操作,并返回相加后结果
    test(6, 5, (i, j) -> i + j);
}

private static void test(int i, int j, FunctionalTest functionalTest) {
    if (functionalTest.calculate(i, j) > 10) {
        System.out.println("计算结果大于 10");
        return;
    }

    System.out.println("计算结果小于 10");
}
以上例子我们是将核心功能片段给抽离出去了,然后通过匿名函数的方式,在具体使用的地方去自定义这段逻辑。
很多时候项目内会有部分相同或相近的逻辑,这个时候我们就可以通过自定义一个接口函数,把这部分相同或相近的逻辑抽离成一个方法,把各自不同的逻辑写在匿名函数内,这样可以简化很多重复的代码,降低业务变更代码修改不干净带来的问题。
如果我们需要用到一个函数接口用于抽离代码逻辑,也许 java.util.function 包下已经存在了我们需要用到的一切。

四 java.util.function 包下的函数接口

接口名 抽象方法 抽象方法的作用
XxxConsumer accept 消费掉入参,无返回值
XxxFunction apply 操作入参,返回其他类型
XxxOperator applyAsXxx 操作入参,返回入参类型
XxxPredicate test 操作入参,返回 boolean类型
XxxSupplier getAsXxx 空入参,返回指定范型类型
扫码关注有惊喜

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

暂无评论

Copyright 2016 qbian. All Rights Reserved.

文章目录