函数式接口一文看懂

260 阅读7分钟

由来

Lambda表达式是Java8的新特性之一,lambda表达式的官方定义如下:

lambda表达式也就是一个可传递的代码块(或者匿名函数),可以在以后执行一次或多次

然而Java是一种面向对象的语言,并不允许直接传递一段代码块,所以需要通过构造对象的传递lambda表达式的代码块,而这也正是函数式接口产生的原因。

定义

规定只有一个必须需要实现的方法的接口称为函数式接口

上面提到函数式接口是为了传递lambda表达式的代码,因为可以把lambda表达式的代码块作为函数式接口的唯一的抽象方法的实现从而通过函数式接口的实现类对象达到传递代码块的目的,这也是为什么函数式接口只能有一个需要实现的方法

@FunctionalInterface
public interface Runnable {
   
    public abstract void run();
}

比如Runnable就是一个函数式接口,他只有具有一个抽象的run方法,下面以他演示函数式接口在lambda表达中的应用

// lambda表达式
Runnable r = ()-> System.out.println("hello,lambda");
//上述lambda表达等价于如下实现
Runnable r = new Runnable() {
    @Override
    public void run() {
      System.out.println("hello,lambda")
    }
};

在上述例子中lambda表达式的代码块就可以通过函数式接口的实现类对象r来传递了

注意:

  1. 函数式接口规定只能有一个需要被实现的方法(只包含一个抽象方法),不是规定接口中只能有一个方法
  1. Java 8 中有另一个新特性:default, 被 default 修饰的抽象方法会有默认实现不是必须要实现的方法,不作为判断函数式接口的依据
  2. java.lang.Object是所有类的父类,所以如果接口显示声明覆盖了 Object 中方法,那么也不算抽象方法
  3. 静态方法也不是抽象方法
  4. Java自带的函数式接口通常带有@FunctionalInterface注解,但该注解并不影响lambda表达式的使用,只要符合函数式接口的定义都可以用作传递lambda表达的容器

四大核心函数式接口

image-20230715102835541

  1. 消费型接口 Consumer < T>

    他具有的唯一一个抽象方法就是accept,void accept(T t),很明显该方法只有入参但没有出参,所以他叫做消费型接口,将入参消费掉,具体的消费逻辑在lambda表达式中定义

    Consumer接口的源码如下:

    @FunctionalInterface
    public interface Consumer<T> {
    ​
        void accept(T t);
    ​
       
        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    }
    

    Consumer还可以通过default Consumer<T> andThen(Consumer<? super T> after)方法将多个consumer串起来,通过源码也很好理解,他通过lambda表达式创建了一个新的Consumer,依次调用前后两个consumer的accept方法

    如果还不习惯上面lambda表达式的写法,可以看一下如下不使用lambda表达式的等价写法

    public interface myConsumer<T> {
    ​
        void accept(T t);
      
        default myConsumer<T> andThen(myConsumer<? super T> after) {
            Objects.requireNonNull(after);
            // return (T t) -> { accept(t); after.accept(t); };
            myConsumer before=this;
            return new myConsumer<T>() {
                @Override
                public void accept(T t) {
                    before.accept(t);
                    after.accept(t);
                }
            };
        }
    }
    

    注意:

    为什么这里我需要引入一个before变量,调用 before.accept(t),而在lambda表达式中可以直接调用accept(t)?

    因为在lambda表达式this指代的是Lambda 的外部类,在匿名内部类中this指代的是匿名类本身

    通过debug可以证明:

    在lambda表达式中:before和this是同一个对象

    image-20230715111604674

    在匿名类中:before和this是不同对象

    image-20230715111424742

    ConsumerDemo

    class ConsumerTest{
      // 接收两个consumer参数,并通过andThen将他们串起来
        public static void stringInfo(Consumer<String> consumer1,Consumer<String> consumer2,String str){
           consumer1.andThen(consumer2).accept(str);
        }
        public static void main(String[] args) {
            String str = "hello";
           // 定义两个consumer
            Consumer<String> consumer1 = s -> System.out.println("strlen = "+s.length());
            Consumer<String> consumer2 = s -> System.out.println("str = "+s);
            stringInfo(consumer1,consumer2,str);
        }
    }
    
  2. 供给型接口 Supplier < T>

    Supplier唯一的抽象接口是T get();,不接受参数却有一个返回值,作用就是产生数据给调用环境,是数据产生的源头,但是具体数据的产生逻辑在lambda表达式中定义

    Supplier源码:

    @FunctionalInterface
    public interface Supplier<T> {
    ​
        /**
         * Gets a result.
         *
         * @return a result
         */
        T get();
    }
    

    这个接口很容易理解,我就直接上demo了

    class SupplierTest{
        /**
         * 产生一个number,但是产生number的规则交给调用方决定
         */
        public static Integer generateNumber(Supplier<Integer> supplier){
            return supplier.get();
        }
        public static void main(String[] args) {
            Supplier<Integer> integerSupplier=()-> new Random().nextInt(100);
            Integer number = generateNumber(integerSupplier);
            System.out.println(number);
        }
    }
    
  3. 函数型接口 Function<T,R>

    Function的抽象方法:R apply(T t);,接收T类型的参数返回值为R类型,主要用于数据类型转换

    基本使用

    public class FunctionTest {
        static Student getStudent(Function<String,Student> studentFunction, String name){
            return studentFunction.apply(name);
        }
        public static void main(String[] args) {
            Function<String,Student> function = Student::new;
            String name = "zhangsan";
            Student student = getStudent(function, name);
            System.out.println(student);
        }
    }
    class Student{
        private String name;
    ​
        public Student(String name) {
            this.name = name;
        }
    ​
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + ''' +
                    '}';
        }
    }
    

    注意:

    上面我的使用了Student::new的lambda表示的写法,这种写法是lambda的方法引用

    他使用在lambda表达式中只有一个方法调用的情况

    例如上面这个例子不使用方法引用的写法为:

    Function<String,Student> function = name -> new Student(name); //
    

    对于方法引用编译器会生成一个函数式接口实例,覆盖接口的抽象方法,方法体中就直接调用被引用的方法,比如上面的new Student()

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

    可以看到Function还有compose()andThen()identity()几个方法

    1. andThen():和Consumer的andThen方法的作用相同,允许我们链式调用多个Function

       // 接收一个String类型的数字,将数字转化为Integer,然后+1
      Function<String,Integer> AddOne1 = x-> {
        return Integer.parseInt(x)+1;
      };
      // 将+1后的数字转化回String
      Function<Integer,String> convertToString = String::valueOf;
      System.out.println(AddOne1.andThen(convertToString).apply("10")); // 11
      

      AddOne1.andThen(convertToString) 会先执行AddOne1,再执行convertToString

    1. compose: 和andThen的功能相同都是允许我们链式调用多个Function,但是执行的顺序相反

       // 接收一个String类型的数字,将数字转化为Integer,然后+1
      Function<String,Integer> AddOne1 = x-> {
        return Integer.parseInt(x)+1;
      };
      // 将+1后的数字转化回String
      Function<Integer,String> convertToString = String::valueOf;
      ​
      System.out.println(convertToString.compose(AddOne1).apply("10")); // 11
      

      convertToString.compose(AddOne1) 会先执行AddOne1,再执行convertToString

    2. identity是一个静态方法返回一个输入和输出相同的lambda表达式,只是一种替代lambda t -> t 的写法

      Function.identity() = t -> t 
      
  4. 断言型接口 Predicate < T>

    抽象方法boolean test(T t);泛型T是参数、返回结果类型为布尔类型

    主要用判断类型为T的对象是否满足约束条件,并返回判断结果

    基本使用:

    public class MyPredicateTest {
    ​
        // 定义一个对String长度做判断的函数,判断规则在Predicate中决定
        public static boolean checkStrLength(Predicate<String> stringPredicate, String str){
            return stringPredicate.test(str);
        }
        public static void main(String[] args) {
            Predicate<String> predicate = t -> t.length()<=5;
            String str = "hello";
            System.out.println(checkStrLength(predicate, str));
        }
    }
    

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

    从源码看到Predicate还有andnegateor几个default方法和isEqual这个静态方法

    1. and 功能:将两个Predicate约束条件做&&运算

      实现方式:在and中创建了一个新的Predicate,新的Predicate的test方法中分别调用之前两个Predicate的test方法做&&运算

      如果还不了解这个lambda表达式的写法可以看我下面的等价

       default Predicate<T> and(Predicate<? super T> other) {
              Objects.requireNonNull(other);
              Predicate<T> before = this;
              return new Predicate<T>() {
      ​
                  @Override
                  public boolean test(T t) {
                      return before.test(t)&&other.test(t);
                  }
              }
          }
      

      and的使用:

       public static void main(String[] args) {
              Predicate<String> predicate1 = t -> t.length()<=5;
              Predicate<String> predicate2 = t -> t.startsWith("he");
              String str = "hello";
              //predicate1和predicate2都是true
              System.out.println(predicate1.and(predicate2).test(str)); // true 
              String str = "hello,world";
              //predicate1是false,predicate2是true
              System.out.println(predicate1.and(predicate2).test(str)); // false 
       }
      
    2. negate功能:对原有约束条件取反

      negate的使用

       public static void main(String[] args) {
              Predicate<String> predicate1 = t -> t.length()<=5;
              String str = "hello";
              System.out.println(predicate1.negate().test(str)); //本来是true,经过negate后就是false
       }
      
    3. or功能:将两个约束条件进行||运算

      public static void main(String[] args) {
              Predicate<String> predicate1 = t -> t.length()<=5;
              Predicate<String> predicate2 = t -> t.startsWith("he");
              String str = "hello";
               //predicate1是false但predicate2是true
              System.out.println(predicate1.or(predicate2).test("hello,World"));  //true
      }
      
    4. isEqual,这个方法是一个静态方法,作用是帮我快速创建一个Predicate,该Predicate的功能是判断参数是否equals我们传入的参数

      具体用法如下:

      public static void main(String[] args) {
        String target = "Predicate isEqual()";
        // 快速创建一个判断 str是否equals我们的target的Predicate
        Predicate<String>  targetStrPredicate= Predicate.<String>isEqual(target);
        System.out.println(targetStrPredicate.test("hello")); // false
        System.out.println(targetStrPredicate.test("Predicate isEqual()")); // true
      }
      

    总结

    本文主要如何判断一个函数式接口,以及四种最主要的函数式接口的使用,还包括了一些lambda表达式的注意事项

    参考

    《Java 核心技术卷1》

    JAVA常见的函数式接口