Java Lambda 表达式、函数式接口、方法引用与构造器引用

842 阅读4分钟

一、Lambda 表达式

  • Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)
  • Lambda表达式,实际上还是一个接口的一个匿名实现类,但该接口只能有一个抽象方法
  • 从匿名类到Lambda
    @FunctionalInterface
    public interface MathCalculate<T> {
      int method(T a, T b);
    }
    
    @Test
    public void test2() {
        // 匿名内部类的方法
        MathCalculate<Integer> mathCalculate = new MathCalculate<>() {
          @Override
          public int method(Integer a, Integer b) {
            return a + b;
          }
        };
        int res = mathCalculate.method(1, 2);
        System.out.println(res); // 3
      }
    
      @Test
      public void test() {
      	// Lambda表达式的方法
        MathCalculate<Integer> mathCalculate = (x, y) -> x + y;
        int res = mathCalculate.method(1, 2);
        System.out.println(res);
      }
    
  • Lambda 表达式语法:
    • 左侧:指定了Lambda 表达式需要的所有参数(接口形参有什么,就写什么,不需要带类型)
    • 右侧:指定了Lambda体,即Lambda表达式要执行的功能(其实就是接口实现的内容)
  • 语法格式一:无参,无返回值,Lambda体只需要一条语句
      @Test
      public void runnableTest() {
        Runnable runnable = () -> System.out.println("你好世界");
        runnable.run();
      }
    
  • 语法格式二:Lambda需要一个参数,无返回值,参数的括号可以省略
      @Test
      public void consumerTest() {
        Consumer<String> talk = (str) -> System.out.println(str);
        //Consumer<String> talk = str -> System.out.println(str);
        talk.accept("你好世界");
      }
    
  • 语法格式三:Lambda 需要两个参数,并且有返回值
      @Test
      public void test() {
        // Lambda表达式的方法
        MathCalculate<Integer> mathCalculate = (x, y) -> x + y;
        int res = mathCalculate.method(1, 2);
        System.out.println(res);
      }
    
  • 语法格式四:Lambda表达式有多条语句,需要用大括号括住
      @Test
      public void runnableTest() {
        Runnable runnable = () ->
        {
          System.out.println("你好世界");
          System.out.println("第二个你好世界");
        };
        runnable.run();
      }
    

二、函数式接口

  • 只包含一个抽象方法的接口,称为函数式接口

  • 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

    @FunctionalInterface
    public interface MathCalculate<T> {
      int method(T a, T b);
    }
    
  • 作为参数传递Lambda表达式(其实就是接口作为形参,然后接收表达式)

    public class RunnableUser {
    // 接受Runnable的接口实现类或者Lambda表达式作为参数注入
      public void useRunnable(Runnable runnable) {
        runnable.run();
      }
    }
    
      @Test
      public void runnableTest() {
        Runnable runnable = () ->
        {
          System.out.println("你好世界");
          System.out.println("第二个你好世界");
        };
        RunnableUser runnableUser = new RunnableUser();
        runnableUser.useRunnable(runnable);
      }
    
      @Test
      public void runnableTest() {
        RunnableUser runnableUser = new RunnableUser();
        runnableUser.useRunnable(() -> System.out.println("你好世界"));
      }
    
  • Java已经内置了一些核心函数式接口,因此一般来说都不需要自己创建,除非有特殊要求。

    函数式接口参数类型返回类型用途
    Consumer<T> 消费者接口Tvoid用于有一个输入参数,没有返回值;包含方法accept(T t)
    Supplier<T> 供给型接口T用于无输入参数,有一个返回值;包含方法T get()
    Function<T, R>函数型接口TR用于有一个输入参数,一个返回值;包含方法R apply(T t)
    Predicate<T>断定型接口Tboolean用于判断某个值是否满足约束,输入一个参数,返回boolean类型;包含方法boolean test(T t)
    还有更多接口,可以查看以下链接:Java 8 函数式接口
  • 例子:

      @Test
      public void consumerTest() {
        Consumer<String> talk = (str) -> System.out.println(str);
        //Consumer<String> talk = str -> System.out.println(str);
        talk.accept("你好世界");
      }
    
      @Test
      public void supplierTest() {
        Supplier<String> getSomething = () -> "你好世界";
        String res = getSomething.get();
        System.out.println(res);
      }
    
      @Test
      public void functionTest() {
        Function<String, Integer> Parse = (str) -> Integer.parseInt(str);
        Integer integer = Parse.apply("10");
        System.out.println(integer);
      }
    
      @Test
      public void predicateTest() {
        Predicate<Integer> filter = (num) -> num >= 100;
        System.out.println(filter.test(100));
        System.out.println(filter.test(77));
        System.out.println(filter.test(110));
      }
    

三、方法引用与构造器引用

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
  • 实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致(包括输入参数、返回值)
  • 使用操作符 ::将方法名和对象或类的名字分隔开来
  • 有三种使用情况:
    • 对象::实例方法类::静态方法:这两种方法很类似
        @Test
        public void functionTest() {
          Function<String, Integer> Parse = Integer::parseInt;  // Integer的静态方法
          Integer integer = Parse.apply("10");
          System.out.println(integer);
        }
      
    • 类::实例方法:这种方法比较特别,相当于接口需要调用者传入一个对象及其参数。
        @Test
        public void compareTest() {
          BiPredicate<String, String> comparator = String::equals;
          // BiPredicate<String, String> comparator = (x, y) -> x.equals(y);  // 等价于
          boolean res = comparator.test("hello", "hello");
          System.out.println(res);
        }
      
  • 构造器引用 实际上构造器就是一个静态方法。因此,可以这样写:
      @Test
      public void newTest() {
        Supplier<User> supplier = User::new;  // 无参构造
        User user = supplier.get();
        System.out.println(user);
    
        Function<Integer,User> function = User::new;  // 有参构造
        user = function.apply(1);
        System.out.println(user);
      }
    

    使用哪个构造器,要看使用哪个函数式接口。

  • 数组引用
      @Test
      public void newArrayTest() {
        Function<Integer,Integer[]> function = Integer[]::new;
        Integer[] apply = function.apply(2);  // 需要传进去一个数组长度
        System.out.println(apply.length);  // 2
        System.out.println(Arrays.toString(apply));  // [null, null]
      }