函数式接口

一、函数式接口

1.1 概念

函数式接口在java中是指:有且仅有一个抽象方法的接口。

函数式接口,即适用于函数式编程场景的接口。而java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,java中的Lambda才能顺利地进行推导。

备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,java中Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。

1.2 格式

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 {
	public abstract 返回值类型 方法名称(可选参数信息);
}

由于接口当中抽象方法的public abstract是可以省略的,所以定义一个函数式接口很简单:

public interface MyFunctionalInterface {
    void myMethod();
}

1.3 @FunctionalInterface注解

  1. 该注解只能标记在"有且仅有一个抽象方法"的接口上。
  2. 该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionalInterface,那么编译器会报错。

二、函数式编程

2.1 Lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。

使用 Lambda 表达式的主要原因是:将代码的执行延迟到一个合适的时间点,即调用的时候。
所有的 Lambda 表达式都是延迟执行的。
因为匿名内部类的方法都是要等到调用的时候才会执行。

性能浪费的日志案例

public class Demo01Logger {
    private static void log(int level, String msg) {
        if (level == 1) {
            System.out.println(msg);
        }
    }
 
    public static void main(String[] args) {
        String msgA = "Hello ";
        String msgB = "world ";
        String msgC = "Java ";
        log(1,msgA + msgB + msgC);
    }
}
//输出: Hello world Java

这种代码存在问题:无论级别是否满足要求,作为log方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。

使用Lambda

使用Lambda必然需要一个函数式接口

@FunctionalInterface
public interface MessageBuilder {
    String buildMessage();
}

然后对log方法进行改造

public class Demo03LoggerDelay {
    private static void log(int level, MessageBuilder builder) {
        if (level == 1) {
            System.out.println(builder.buildMessage());
        }
    }
    public static void main(String[] args) {
        String msgA = "Hello ";
        String msgB = "world ";
        String msgC = "Java ";
        log(2,() ->{
            System.out.println("Lambda执行!");//用于证明Lambda的延迟执行
             return msgA + msgB + msgC;
        });
    }
}
// 此时没有任何输出,证明lambda表示式没有执行

从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。

2.2 使用Lambda作为参数和返回值

三、常用函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。

3.1 Supplier接口(生产型或供给型接口)

java.util.function.Supplier<T>接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。 (Supplier接口被称为生产型接口,指定接口的泛型是什么类型,那个接口中的get()方法就会生产什么类型的数据)

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

3.2 Consumer接口(消费型接口)

java.util.function.Consumer<T>接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

抽象方法:accept

Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。

默认方法:andThen

如果一个方法的参数和返回值类型全都是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThen

public class Demo01ConsumerAndThen {

    public static void method(String str, Consumer<String> con1, Consumer<String> con2) {
        con1.andThen(con2).accept(str);//con1连接con2,先执行con1消费数据,再执行con2消费数据
    }

    public static void main(String[] args) {
        method("Hello", t -> System.out.println(t.toUpperCase()), (t) -> System.out.println(t.toLowerCase()));
    }
}

3.3 Predicate接口(断言型接口)

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>接口。

@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);

    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);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

抽象方法:test

Predicate接口中包含一个抽象方法:boolean test(T t)

默认方法:and

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate条件使用"与"逻辑连接起来实现"并且"的效果时,可以使用default方法and

默认方法:or

and的"与"类似,默认方法or实现逻辑关系中的""。

默认方法:negate

默认方法negate实现逻辑关系中的""(取反)。

3.4 Function接口(转化型接口T->R)

java.util.function.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);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

抽象方法:apply

Function接口最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。

默认方法:andThen

该方法同样用于“先做什么,再做什么”的场景,和Consumer中的andThen类似。

函数式接口

上一篇:【NLP学习其四】如何构建自己用于训练的数据集?什么是词性标注?


下一篇:YBTOJ:防具布置