1. 定义

Java中有且仅有一个抽象方法的接口称为函数式接口,它主要应用于函数式编程的场景中。Java中函数式编程具体的体现 就是Lambda表达式的使用,所以函数式接口可以适用于Lambda使用的接口。

  • 只有确保接口中有且仅有一个抽象方法,接口才能被称为函数式接口
  • 当然接口中可以有除抽象方法外其他的方法,如默认方法、静态方法和私有方法等
  • 通常为保证函数式接口的正确性,通常用@FunctionalInterface注解来检测所编写的接口是否符合函数式接口的要求:
    • 符合,编译成功
    • 不符合,则编译失败,说明此时接口中没有抽象方法或者抽象方法的个数多于1

函数式接口格式:

  1. 修饰符 interface 接口名{
  2. public abstract 返回值类型 方法名(参数列表){};
  3. }

如一个简单的函数式接口可定义为:

  1. @FunctionalInterface
  2. public interface FuncInterface {
  3. public abstract void method();
  4. }

2. 使用

函数式接口一般作为方法的参数返回值类型。例如在之前的浅析Java和Python中Lambda表达式

中其实已经使用过函数式编程,在文中的例子中函数式接口作为方法的参数使用,下面我们再单独回顾下它作为方法参数的使用:函数式接口本质上仍然是接口,因此函数式接口的使用有三种方式:

  • 定义接口的实现类并重写接口中的抽象方法,通过新建实现类的对象使用

    1. public class FuncInterfaceImpl implements FuncInterface{
    2. @Override
    3. public void method() {
    4. System.out.println("Functional interface...");
    5. }
    6. }
  • 不显式的创建接口的实现类,而是通过匿名内部类的方式直接新建对象使用

  • 通过函数式接口的的独有方法-Lambda表达式使用
  1. public class FuncMain {
  2. public static void main(String[] args) {
  3. // 调用接口实现类的重写方法
  4. show(new FuncInterfaceImpl());
  5. // 使用匿名内部类重写方法
  6. show(new FuncInterface() {
  7. @Override
  8. public void method() {
  9. System.out.println("Functional interface...");
  10. }
  11. });
  12. // 使用Lambda表达式重写方法
  13. show(() -> {System.out.println("Functional interface...");});
  14. show(() -> System.out.println("Functional interface..."));
  15. }
  16. public static void show(FuncInterface fi){
  17. fi.method();
  18. }
  19. }

下面看一个使用函数式接口作为返回值类型的例子。ArrayList中的sort()方法需要接收一个比较器,即Comparator,那么我们就将创建的Comparator作为返回值传入sort(),然后再对ArrayList中的元素进行排序。

  1. import java.util.ArrayList;
  2. import java.util.Comparator;
  3. public class RunnableDemo {
  4. public static void main(String[] args) {
  5. ArrayList<String> list = new ArrayList<>();
  6. list.add("Forlogen");
  7. list.add("ada");
  8. list.add("kobe");
  9. System.out.println(list.toString());
  10. list.sort(getComparator());
  11. System.out.println(list.toString());
  12. }
  13. public static Comparator<String> getComparator(){
  14. return (o1, o2) -> o2.length() - o1.length();
  15. }
  16. }

2. 特点

Lambda表达式除了可以简化代码的书写外,它还有一个重要的特点:延迟执行。怎么理解延迟执行呢?假设现在有一个日志输出的demo,只有日志等级等于1时才输出传入的日志信息,如果不使用Lambda表达式,代码编写如下:

  1. public class LoggerDemo {
  2. public static void main(String[] args) {
  3. String m1 = "hello";
  4. String m2 = " world";
  5. String m3 = " ...";
  6. show(1, m1 + m2 + m3);
  7. }
  8. public static void show(int level, String message){
  9. if (level == 1){
  10. System.out.println(message);
  11. }
  12. }
  13. }

这样做有个问题:不管最后控制台是否会输出传入的日志信息,字符串的拼接工作都会执行,这样就造成了性能的浪费。而如果使用Lambda表达式作为参数传递,它仅仅是把参数传递到方法中,只有满足条件时才调用接口中的方法,然后进行字符串的拼接。如果条件不满足,接口中的方法就不会执行,那么自然不会进行字符串拼接,也就不会存在性能的浪费发生。

  1. @FunctionalInterface
  2. public interface MessageBuilder {
  3. public abstract String building();
  4. }
  5. public class LambdaLoggerDemo {
  6. public static void main(String[] args) {
  7. String m1 = "hello";
  8. String m2 = " world";
  9. String m3 = " ...";
  10. showLog(1, () ->{return m1 + m2 + m3;});
  11. // showLog(2, () ->{return m1 + m2 + m3;});
  12. }
  13. public static void showLog(int level, MessageBuilder mb){
  14. if (level == 1){
  15. System.out.println(mb.building());
  16. }
  17. }
  18. }

4. Java中的函数式接口

除了可以根据规则自定义和使用函数式接口外,Java的java.util.function包中提供了大量已编写好的函数式接口供用户使用。
image-20200506145823754.png

4.1 Supplier

  1. @FunctionalInterface
  2. public interface Supplier<T> {
  3. /**
  4. * Gets a result.
  5. *
  6. * @return a result
  7. */
  8. T get();
  9. }

java.util.function.Supplier 接口仅含有一个无参的方法T get(),它用来获取一个泛型参数指定类型的对象数据。因此,Supplier接口又被称为生产者接口,通过指定接口中泛型的具体类型,接口就会生产对应类型的数据。

  1. import java.util.function.Supplier;
  2. public class SomeFncInterfacesInJDK {
  3. public static void main(String[] args) {
  4. String s = getString(()-> "Forlogen");
  5. System.out.println(s); // Forlogen
  6. }
  7. public static String getString(Supplier<String> sup){
  8. return sup.get();
  9. }
  10. }

如上所示,Java程序使用Supplier接口获取一个String类型的数据。因为我们可以在Lambda表达式中编写自己的逻辑,只要最后是获取某一具体类型的数据,中间过程可以做任何的操作。例如,使用Supplier接口实现获取数组中最大值的操作:

  1. import java.util.function.Supplier;
  2. public class SomeFncInterfacesInJDK {
  3. public static void main(String[] args) {
  4. int[] array = {1, 3, 10, 5, 8};
  5. // 自动拆箱
  6. int max = getMax(() -> {
  7. int maxN = array[0];
  8. for (int i = 1; i < array.length; i++) {
  9. if (array[i] > maxN) {
  10. maxN = array[i];
  11. }
  12. }
  13. return maxN;
  14. });
  15. System.out.println(max); // 10
  16. }
  17. public static Integer getMax(Supplier<Integer> sup){
  18. return sup.get();
  19. }
  20. }

4.2 Consumer

  1. @FunctionalInterface
  2. public interface Consumer<T> {
  3. void accept(T t);
  4. default Consumer<T> andThen(Consumer<? super T> after) {
  5. Objects.requireNonNull(after);
  6. return (T t) -> { accept(t); after.accept(t); };
  7. }
  8. }

java.util.function.Consumer接口用于消费一个由泛型指定的具体类型数据,接口中的抽象方法为void accept(T t),用来消费一个指定类型的数据。虽然接口用来消费某一类型的数据,但具体怎么消费或者说如何使用数据的逻辑由用户自定义。例如,我们可以使用Consumer接口消费一个字符串,消费过程是将字符串中的字母全部转为大写形式。

import java.util.function.Consumer;

public class SomeFncInterfacesInJDK {
    public static void main(String[] args) {
        Consume("Forlogen", (name) -> System.out.println(name.toUpperCase()));  //FORLOGEN
    }

    public static void Consume(String s, Consumer<String> c){
        c.accept(s);
    }
}

如果我们的消费过程涉及到多步操作呢?即如果需要对每次消费的结果再次进行消费呢?按照一般的写法,我们要定义多个Consumer接口,然后分别调用accept()进行消费。除此之外,Consumer接口中提供了一个默认方法addThen()用于将多个接口组合在一起,然后再对数据进行消费。以String类型的数据为例,方法的使用格式为:

String str = " ...";
Consumer<String> c1, Consumer<String> c2, COnsumer<String> c3;  // 可以是多个
c1.addThen(c2).andThen(c3).accept(s)
  • 理论上可以使用addThen()组合无限多个Consumer接口使用,只需要不断地·andThen()即可,最后使用accept()消费数据
  • 使用中,谁写在前边谁就先消费,后面的接口消费前一个接口的结果

例如我们实现简单的将字符串先转为大写,然后再转为小写,就可以使用addThen()组合两个Consumer接口消费两次。

import java.util.function.Consumer

public class SomeFncInterfacesInJDK {
    public static void main(String[] args) {
        ConsumeAddThen("Forlogen", (name)-> System.out.println(name.toUpperCase()),
                (name)->System.out.println(name.toLowerCase()) // FORLOGEN  forlogen
        );
    }

    public static void ConsumeAddThen(String s, Consumer<String> c1, Consumer<String> c2){
        c1.andThen(c2).accept(s);
    }
}

4.3 Predicate

@FunctionalInterface
public interface Predicate<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);
    }

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

java.util.function.Predicate接口用于对某种数据类型的数据进行判断,接口中包含一个抽象方法:boolean test(T t)用来对指定的数据类型进行判断:

  • 如果符合条件,返回true
  • 如果不符合条件,返回false

至于条件如何设置由用户自定义。例如,使用Predicate接口判断字符串的长度是否大于5:

import java.util.function.Predicate;

public class SomeFncInterfacesInJDK {
    public static void main(String[] args) {
        boolean b = CheckString("Forlogen", (str) -> str.length() > 5); // true
        System.out.println(b);
    }

    public static boolean CheckString(String s, Predicate<String> p){
        return p.test(s);
    }
}

类似于逻辑判断中的&& 、||!,Predicate接口中提供了默认方法and()or()negate()来实现同样的逻辑。

import java.util.function.Predicate;

public class SomeFncInterfacesInJDK {
    public static void main(String[] args) {
        boolean b1 = CheckStringAnd("Forlgoen", (str)-> str.startsWith("a"), (str)->str.length() > 5);
        System.out.println(b1);    // false

        boolean b2 = CheckStringOr("Forlgoen", (str)-> str.startsWith("a"), (str)->str.length() > 5);
        System.out.println(b2);  // true

        boolean b3= CheckStringNegate("Forlgoen", (str)-> str.startsWith("a"));
        System.out.println(b3); // true
    }

    public static boolean CheckStringAnd(String s, Predicate<String> p1, Predicate<String> p2){
        return p1.and(p2).test(s);
        // 等价于 return p1.test(s) && p2.test(s);
    }

    public static boolean CheckStringOr(String s, Predicate<String> p1, Predicate<String> p2){
        return p1.or(p2).test(s);
        // 等价于 return p1.test(s) || p2.test(s)

    }

    public static boolean CheckStringNegate(String s, Predicate<String> p){
        return p.negate().test(s);
        // 等价于 return !p.test(s);
    }
}

4.4 Function

@FunctionalInterface
public interface Function<T, R> {
    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;
    }
}

java.util.function.Function接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。接口中的抽象方法R apply(T t)用于根据类型T的参数获取类型R的结果,将进行数据类型的转换,如将String类型数据转换为Integer类型:

import java.util.function.Function;

public class SomeFncInterfacesInJDK {
    public static void main(String[] args) {
        int x = convert((str)-> Integer.parseInt(str), "100");
        System.out.println(x);  // 100
    }

    public static Integer convert(Function<String, Integer> f, String s){
        return f.apply(s);
    }
}

Function接口中同样提供了addThen()来执行多步操作。

import java.util.function.Function;

public class SomeFncInterfacesInJDK {
    public static void main(String[] args) {
        String s1 = convertAndThen((str) -> Integer.parseInt(str) * 3, (num) -> num + " ", "1000");
        System.out.println(s1);  // 3000
    }

    public static String convertAndThen(Function<String, Integer> f1, Function<Integer, String> f2, String s){
        return f1.andThen(f2).apply(s);
    }
}