掘金 后端 ( ) • 2024-04-08 14:24

什么是Lambda表达式

  • lambda表达式是JAVA8中提供的一种新的特性,它支持JAVA也能进行简单的“函数式编程”
  • 它是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。
  • Lambda 表达式是实现函数式接口的一种方式,可以看做匿名内部类的简写形式:它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表。
  • Lambda 是一个匿名函数,可以把 Lambda表达式 理解为是一段可以传递的代码 (将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升 ,JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。

什么是函数式接口

定义:接口中只有一个抽象方法的接口。

函数式接口一般使用 @FunctionalInterface 注解修饰,目的是检查接口是否符合函数式接口规范。

注意点:

  • 函数式接口中可以有 默认方法 和静态方法
  • 函数式接口重写父类的方法,并不会计入到自己的抽象方法中

函数式接口Runnable

java.lang.Runnable 就是一种函数式接口,在 Runnable 接口中只声明了一个方法 void run(),我们使用匿名内部类来实例化函数式接口的对象,有了 Lambda 表达式,这一方式可以得到简化。

每个 Lambda 表达式都能隐式地赋值给函数式接口,例如,我们可以通过 Lambda 表达式创建 Runnable 接口的引用。

Runnable r = () -> System.out.println("hello world");

当不指明函数式接口时,编译器会自动解释这种转化:

new Thread(

   () -> System.out.println("hello world")

).start();

因此,在上面的代码中,编译器会自动推断:根据线程类的构造函数签名 public Thread(Runnable r) { },将该 Lambda 表达式赋给 Runnable 接口。

4类常用函数式接口

  • Consumer 消费型接口:接受一个参数并进行逻辑操作,无返回值;
  • Supplier 供给型接口:不接受参数,操作后返回一个对象;
  • Function<T, R> 函数型接口:接收一个泛型T对象,操作后返回泛型R对象;
  • Predicate 断言型接口:接收一个参数,操作后返回一个 boolean 值;

Consumer

接口定义:

@FunctionalInterface

public interface Consumer<T> {



    /**

     * Performs this operation on the given argument.

     *

     * @param t the input argument

     */

    void accept(T t);

}

消费型接口:接收一个参数进行处理,不返回结果。

Supplier

接口定义:

@FunctionalInterface

public interface Supplier<T> {



    /**

     * Gets a result.

     *

     * @return a result

     */

    T get();

}

供给型接口:不接受参数,返回一个泛型类型的对象;

如何使用:使用时提供该接口的实现,并返回一个泛型类型的对象;

Function

接口定义:

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

}

函数型接口:提供一个 T 类型的参数,返回一个 R 类型的结果。

Predicate

接口定义:

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

}

断言型接口:输入一个 T 类型的参数,返回 boolean 类型的结果。

方法引用

方法引用是进一步简化 Lambda 表达式的写法。

方法引用的格式:类型或者对象 :: 引用的方法

方法引用的四种形式:

  1. 静态方法的引用;
  2. 实例方法的引用;
  3. 特定类型方法的引用;
  4. 构造器引用;

静态方法的引用

格式: 类名 :: 静态方法名

这里以 List Integer 转 String 为例:

List<Integer> list = new ArrayList<>();



//                  list.stream().map(s -> String.valueOf(s));

List<String> strs = list.stream().map(String::valueOf).collect(Collectors.toList());

注意点:前后参数一致的静态方法。

实例方法的引用

格式:实例对象 :: 方法名

这里以 System.out 对象的实例方法 println(String x) 为例:

List<String> list = new ArrayList<>();



// lists.forEach(s -> System.out.println(s));

list.forEach(System.out::println);

注意点:前后参数一致。

特定类型的方法引用

要点:参数列表中形参中的第一个参数作为了要调用方法的调用者,并且其余参数作为后面方法的实参,那么就可以用特定类型方法引用了。

String[] strs = new String[]{"James", "AA", "John", "Patricia", "Dlei", "Robert", "Boom",  "Cao", "black", "Michael", "Linda", "cao", "after", "sBBB"};



// 按照元素的首字符(忽略大小写)升序排序

Arrays.sort(strs, (s1,  s2) ->  s1.compareToIgnoreCase(s2));



// 特定类型的方法引用

Arrays.sort(strs,  String::compareToIgnoreCase);

当一个对象调用一个方法,方法的参数中包含一个函数式接口,该函数式接口的第一个参数类型这个对象的类,那么这个函数式接口可用方法引用代替,并且替换用的方法可以不包含函数式接口的第一个参数(调用对象的类)。

构造器引用

s -> new Student(s) => Student::new

创建对象时,前后参数一致。

总结

Lambda表达式是为了简化实现函数式接口的

如果没有Lambda表达式,需要使用匿名内部类实现

Consumer<String> s1= new Consumer<String>() {

    @Override

    public void accept(String s) {

        System.out.println(s);

    }

};

如果使用Lambda表达式

Consumer<String> s1= s2 -> System.out.println(s2);

既然Lambda表达式是一种实现方式,那函数式接口是啥呢,顾名思义接口用来定义行为,(多说一句,类是抽象属性和行为,即抽象类别的,比如人,飞机等不同类别,接口是抽象行为的,即飞机会飞,人要内卷,飞和内卷都是行为的一种),函数式接口即一个接口只有一个抽象方法,default与static的不算,然后加上注解@FunctionalInterface

函数式接口就是一类行为定义Lambda表达式就是简化实现这类行为的表达式。等于是有函数式接口才有Lambda表达式,单独成立都没有意义。Lambda表达式本质是一个语法糖,内部还是内部类,只是编译器给我们做了处理。

最后我们总结下什么是方法引用

方法引用其实是Lambda表达式的更简约写法,Lambda表达式需要实现函数式接口(即写方法实现),方法引用则是直接使用已经存在符合函数式接口定义方法

比如我们要实现一个int转String。

如果没有方法引用

Function<Integer,String> function=(number)->{return number+"";};

实际上String.valueOf()已经实现了我们要的转换方法,且符合Function的定义,即有一个参数输入,一个参数输出。则我们可以直接使用该方法。

Function<Integer,String> function=String::valueOf;

这样极大的简化了我们的代码实现。

再来一个简单例子。

Consumer<String> s1= s2 -> System.out.println(s2);

我们定义了一个传入打印,Lambda写法如上。我们可以使用更简约的写法

Consumer<String> s1= System.out::println;

只要符合我们的函数式接口的方法定义,即是否有返回和参数个数。

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

    }

}

如上,我们使用这个接口的时候,必须传入一个跟他定义一致的方法,都是没有返回,然后有一个参数传入。