前端视角 Java Web 入门手册 2.6:Java Core ——lambda 表达式

360 阅读5分钟

Lambda 表达式既匿名函数,指的是在需要函数的地方直接定义函数,而不必定义一个函数并给它赋予一个名称

lambda 是希腊字母 λ 的读音,取名借鉴于数学领域的 λ 演算

JavaScript labmda 表达式

JavaScript 中函数可以作为和普通变量一样被传递,同时支持函数表达式定义函数,ES6 支持了箭头函数定义函数,lambda 表达式使用起来非常的自然

[3, 6, 1, 9, 4].sort((x,y) => x - y);
[3, 6, 1, 9, 4].forEach(console.log);

Java lambda 表达式

Java 8 之前没有 lambda 表达式的支持,实现具有单一抽象方法的接口(如 RunnableComparatorActionListener 等)通常需要使用匿名内部类,这不但代码冗长,而且可读性较差

// 使用匿名内部类实现 Runnable 接口
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from a thread!");
    }
}).start();

Java 8 带来了 lambda 表达式支持,用于实现只有一个抽象方法的接口。它们允许将功能作为方法参数传递,从而实现更灵活和可读的代码

// 使用 Lambda 表达式实现 Runnable 接口
new Thread(() -> System.out.println("Hello from a thread!")).start();

基本语法

Java lambda 表达式由参数列表、箭头符号、函数主体组成

(param1, param2) -> expression

(param1, param2) -> {
    // 多行代码
}
  1. 参数列表:与函数接口的抽象方法参数列表相对应
  2. 箭头符号-> 用于分隔参数列表和函数体
  3. 函数体:如果只有一个表达式,可以省略花括号和 return 关键字;如果有多条语句,需要使用花括号并显式使用 return 关键字

字符串数组按照字符串长度排序

String[] words = { "horse", "elephent", "dog", "dolphin" };

// 参数类型可推导时候,可以缺省其类型
Arrays.sort(words, (x, y) -> x.length() - y.length());

Java 编译器可以根据上下文自动推断 Lambda 表达式的参数类型,因此在大多数情况下,无需显式声明参数类型。

函数式接口

函数式编程的核心是把函数传递给对象,面向对象设计的 Java 不能直接传递函数,为了保证简单性和一致性,不新增函数类型,在传递函数时必须构建一个对象,这个对象的类包含此函数,Java 给出的解决方案就是函数式接口

函数式接口(Functional Interface)是指只含有一个抽象方法的接口,可以使用 Lambda 表达式创建一个函数式接口的对象,Comparator接口只有一个抽象方法 compare,可以使用 lambda 表达式

Arrays.sort(words,
            (x, y) -> x.length() - y.length());

Arrays.sort 方法会接收实现了 Comparator 的类的对象,在这个对象上调用 compare 方法时会执行这个 lambda 表达式

继承来的方法不算作抽象方法,因为它们已经被实现了。所以即使 Comparator 也有 equals 方法(来自 Object 类),但它仍然被认为是函数式接口,因为它只有一个抽象方法 compare。

java.util.function 中有一个很常用的接口 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);
	
    // 其余静态或默认方法
}

ArrayList 类的removeIf方法的参数就是 Predicate 类型,这个接口专门用来传递 lambda 表达式

list.removeIf(e -> e == null);

可以通过 @FunctionalInterface 注解自定义函数式接口。该注解不是必须的,但它可以在编译时检查接口是否符合函数式接口的规范

@FunctionalInterface
public interface GreetingService {
    void sayMessage(String message);
    
    // 以下方法会导致编译错误,因为函数式接口只能有一个抽象方法
    // void anotherMethod();
}

方法引用

如果已经有现成方法实现了想传递给对象的函数,可以使用方法引用。方法引用(Method References)是 Lambda 表达式的一种简化形式,用于直接引用已有的方法、构造方法或特定对象的方法。它使得代码更加简洁和具备可读性

类::静态方法

Timer t = new Timer(1000, System.out::println);
Arrays.sort(strArray, String::compareToIgnoreCase)

对象::实例方法

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Consumer 接口表示一个接受单个输入参数并且没有返回值的操作。它定义了一个名为accept的方法,该方法接受一个参数并对其进行操作。Consumer主要用于在集合遍历、流式操作等场景中,对集合中的元素进行操作

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

List<Person> people = Arrays.asList(
    new Person("John"), 
    new Person("Jane"),
    new Person("Mike"));

Consumer<Person> printNameConsumer = Person::printName;

people.stream().forEach(printNameConsumer);

构造器::new

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Supplier 接口表示一个无参函数,它定义了一个名为 get 的方法,该方法返回一个值。Supplier 主要用于在需要提供数据的场景中

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

// 创建一个Supplier接口
Supplier<Person> personSupplier = Person::new;

// 使用Supplier接口的get方法来创建一个新的Person对象
Person person = personSupplier.get();
person.setName("John");

System.out.println(person.getName());

Stream API

Stream API 让开发者可以通过 lambda 表达式简明扼要的以流水线方式处理集合中的元素,可以对集合中的元素进行过滤、排序、映射、去重等操作,使得数据处理更加灵活。并且采用惰性求值的方式,只有在需要结果的时候才会进行计算,减少了不必要的计算量。Stream API 进行集合数据处理的核心方法有:

  • filter():用于对集合中的元素进行过滤
  • map():用于对集合中的元素进行映射
  • distinct():用于去除集合中的重复元素
  • sorted():用于对集合中的元素进行排序
  • limit():用于限制集合中元素的数量
  • skip():用于跳过集合中的元素
  • forEach():用于遍历集合中的元素
  • collect():用于将集合中的元素收集到一个集合中
  • reduce():用于对集合中的元素进行归约操作
List<String> list = Arrays.asList("apple", "banana", "orange", "pear", "peach");

List<String> filteredList = list.stream()
                                .filter(s -> s.startsWith("p"))
                                .map(s -> s + "-OK")
                                .sorted()
                                .collect(Collectors.toList());

System.out.println(filteredList);