Java Lambda 表达式(一):入门

为什么要使用 Lambda 表达式

在 Java 8 以前,假设我们要实现一个不论是多么简单的接口(例如只包含一个方法的接口),最简单的做法也是要使用匿名类,但匿名类的语法可能看起来不怎么实用且不怎么清楚。

譬如,对于 GUI 应用程序中的事件处理程序,我们在 Java 8 以前的常规做法是向方法传递匿名类。比如,以下的 HelloWorld GUI 应用程序源码:

package com.wuxianjiezh.test;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloWorld extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        // 在 Java 8 以前,我们最简洁的写法可能就是匿名类
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });

        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

虽然上面代码的匿名类通常比局部类更简洁,但对于只有一个方法的类,即使是匿名类也似乎有点过分和繁琐。

稍安毋躁,我们先在 Java 8 中查看一下 EventHandler 接口的源码:

package javafx.event;

import java.util.EventListener;

@FunctionalInterface
public interface EventHandler<T extends Event> extends EventListener {

    void handle(T event);
}

从上面的源码中,我们发现 EventHandler 接口只有一个抽象方法 void handle(T event);。细心的童鞋可能还发现了一个新奇的东西——@FunctionalInterface 注解,嗯!这个注解是什么意思呢?可不可以不写呢?不要急,后面我们会再讲到的。

对于 EventHandler 这样一个只有一个抽象方法的接口,我们称之为单一抽象方法接口(SAM,Single Abstract Method Interface)。而对于这样的接口,我们就可以使用 Java 8 引入的 Lambda 表达式来简化代码。

对于上述代码,我们可以使用 Lambda 表达代替匿名类表达式:

package com.wuxianjiezh.test;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloWorld extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        // 从 Java 8 开始,我们可以使用 Lambda 表达式
        btn.setOnAction(event -> System.out.println("Hello World!"));

        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

使用 Lambda 表达式的前置条件

Lambda 表达式并不是可以随意使用的,它有且仅有一个前置条件:Lambda 表达式只能用于实现函数接口(functional interface,函数式接口,功能接口)

函数接口和我们上面提到的单一抽象方法接口是完全相同的,只是叫法不同而已。

函数接口有以下几个特点:

  • 仅包含一个抽象方法的任何接口
  • 但函数接口可以包含一个或多个默认方法静态方法
  • @FunctionalInterface 是 Java 8 中新加入的注解,它用于指示接口是函数接口,如果不是,则会在编译期间就产生错误。这个注解不是必须的,可写可不写

Lambda 表达式的理想用例

现在,我们来模拟一个业务操作。假设某书单的图书由以下 Book 类表示:

package com.wuxianjiezh.test;

import java.util.ArrayList;
import java.util.List;

/**
 * 图书实体类。
 *
 * @author 吴仙杰
 */
public class Book {

    /**
     * 书名。
     */
    private String name;
    /**
     * 作者。
     */
    private String author;
    /**
     * 出版社。
     */
    private String publisher;
    /**
     * 定价。
     */
    private float cny;

    public Book(String name, String author, String publisher, float cny) {
        this.name = name;
        this.author = author;
        this.publisher = publisher;
        this.cny = cny;
    }

    /**
     * 打印图书信息。
     */
    public void printBook() {
        System.out.println("书名:" + this.name +
                "\t出版社:" + this.publisher +
                "\t定价:" + this.cny + "元");
    }

    /**
     * 创建书单。
     *
     * @return 书籍列表
     */
    public static List<Book> createBookList() {
        return new ArrayList<Book>() {{
            add(new Book("Python编程 : 从入门到实践", "[美] 埃里克·马瑟斯", "人民邮电出版社", 89f));
            add(new Book("代码大全(第2版)", "[美] 史蒂夫·迈克康奈尔", "电子工业出版社", 128f));
            add(new Book("Java编程思想 (第4版)", "[美] Bruce Eckel", "机械工业出版社", 108f));
            add(new Book("编程珠玑 : 第2版", "[美] Jon Bentley", "人民邮电出版社", 39f));
            add(new Book("JavaScript高级程序设计(第3版)", "[美] 尼古拉斯·泽卡斯", "人民邮电出版社", 99f));
            add(new Book("重构(第2版)全彩精装版 : 改善既有代码的设计", "Martin Fowler", "人民邮电出版社", 168f));
        }};
    }

    public String getName() {
        return name;
    }

    public String getAuthor() {
        return author;
    }

    public String getPublisher() {
        return publisher;
    }

    public float getCny() {
        return cny;
    }
}

其中 Book#createBookList 代表我们所创建的书单。

现在我们要编写一个测试类 BookListTest,要从书单中筛选出由“人民邮电出版社”出版,并且价格在 100 元以内(包含 100 元)的图书。

在局部类中指定搜索条件代码

package com.wuxianjiezh.test;

import java.util.List;
import java.util.Objects;

/**
 * 书单测试类。
 *
 * <p>假设我们的目的是为了筛选出书单中由“人民邮电出版社”出版,
 * 并且价格在 100 元以内(包含 100 元)的图书。
 *
 * @author 吴仙杰
 */
public class BookListTest {

    private static final String PUBLISHER = "人民邮电出版社";
    private static final float MAX_AMOUNT = 100f;

    // 用于指定搜索条件的接口
    interface CheckBook {
        boolean test(Book book);
    }

    /**
     * 打印出符合条件的图书信息。
     *
     * @param bookList 书单
     * @param filter   过滤器函数
     */
    public static void printBooks(List<Book> bookList, BookListTest.CheckBook filter) {
        for (Book book : bookList) {
            if (filter.test(book)) {
                book.printBook();
            }
        }
    }

    public static void main(String[] args) {

        // 创建书单
        List<Book> bookList = Book.createBookList();

        // 在局部类中指定搜索条件代码
        class CheckMyBook implements CheckBook {

            @Override
            public boolean test(Book book) {
                return Objects.equals(book.getPublisher(), PUBLISHER)
                        && book.getCny() <= MAX_AMOUNT;
            }
        }

        // 打印出符合条件的图书信息
        printBooks(bookList, new CheckMyBook());
    }
}

在匿名类中指定搜索条件代码

package com.wuxianjiezh.test;

import java.util.List;
import java.util.Objects;

/**
 * 书单测试类。
 *
 * <p>假设我们的目的是为了筛选出书单中由“人民邮电出版社”出版,
 * 并且价格在 100 元以内(包含 100 元)的图书。
 *
 * @author 吴仙杰
 */
public class BookListTest {

    private static final String PUBLISHER = "人民邮电出版社";
    private static final float MAX_AMOUNT = 100f;

    // 用于指定搜索条件的接口
    interface CheckBook {
        boolean test(Book book);
    }

    /**
     * 打印出符合条件的图书信息。
     *
     * @param bookList 书单
     * @param filter   过滤器函数
     */
    public static void printBooks(List<Book> bookList, BookListTest.CheckBook filter) {
        for (Book book : bookList) {
            if (filter.test(book)) {
                book.printBook();
            }
        }
    }

    public static void main(String[] args) {

        // 创建书单
        List<Book> bookList = Book.createBookList();

        // 在匿名类中指定搜索条件代码
        // 打印出符合条件的图书信息
        printBooks(bookList, new CheckBook() {

            @Override
            public boolean test(Book book) {
                return Objects.equals(book.getPublisher(), PUBLISHER)
                        && book.getCny() <= MAX_AMOUNT;
            }
        });
    }
}

使用 Lambda 表达式指定搜索条件代码

现在,我们考虑一下上面的 CheckBook 接口:

interface CheckBook {
    boolean test(Book book);
}

很明显,我们的 CheckBook 接口是一个函数接口,故我们这里就可以使用 Lambda 表达式来简化代码:

package com.wuxianjiezh.test;

import java.util.List;
import java.util.Objects;

/**
 * 书单测试类。
 *
 * <p>假设我们的目的是为了筛选出书单中由“人民邮电出版社”出版,
 * 并且价格在 100 元以内(包含 100 元)的图书。
 *
 * @author 吴仙杰
 */
public class BookListTest {

    private static final String PUBLISHER = "人民邮电出版社";
    private static final float MAX_AMOUNT = 100f;

    // 用于指定搜索条件的接口
    interface CheckBook {
        boolean test(Book book);
    }

    /**
     * 打印出符合条件的图书信息。
     *
     * @param bookList 书单
     * @param filter   过滤器函数
     */
    public static void printBooks(List<Book> bookList, BookListTest.CheckBook filter) {
        for (Book book : bookList) {
            if (filter.test(book)) {
                book.printBook();
            }
        }
    }

    public static void main(String[] args) {

        // 创建书单
        List<Book> bookList = Book.createBookList();

        // 使用 Lambda 表达式指定搜索条件代码
        // 打印出符合条件的图书信息
        printBooks(bookList,
                (Book book) -> Objects.equals(book.getPublisher(), PUBLISHER)
                        && book.getCny() <= MAX_AMOUNT);
    }
}

将标准函数接口(Predicate)与 Lambda 表达式一起使用

我们再考虑一下上面的 CheckBook 接口:

interface CheckBook {
    boolean test(Book book);
}

CheckBook 接口是一个非常简单的函数接口,只包含一个接受一个参数并返回一个 boolean 值的抽象方法。由于这个接口实在太简单,我们并不想在自己的程序中定义。那是否有办法不用自己定义这样一个如此简单的函数接口呢?

当然有办法!贴心的 JDK 已经帮助我们定义好了的几个标准的函数接口,这些接口可以在 java.util.function 包中找到。

比如,我们可以使用 Predicate<T> 接口代替 CheckBook 接口。 Predicate<T> 接口是一个泛型接口,它只包含一个根据一个参数并返回 boolean 值的抽象方法:

package java.util.function;

import java.util.Objects;

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 *
 * @param <T> the type of the input to the predicate
 *
 * @since 1.8
 */
@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);

    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<T> 接口代替 CheckBook 接口来简化代码:

package com.wuxianjiezh.test;

import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * 书单测试类。
 *
 * <p>假设我们的目的是为了筛选出书单中由“人民邮电出版社”出版,
 * 并且价格在 100 元以内(包含 100 元)的图书。
 *
 * @author 吴仙杰
 */
public class BookListTest {

    private static final String PUBLISHER = "人民邮电出版社";
    private static final float MAX_AMOUNT = 100f;

    /**
     * 打印出符合条件的图书信息。
     *
     * @param bookList 书单
     * @param filter   过滤器函数
     */
    public static void printBooks(List<Book> bookList, Predicate<Book> filter) {
        for (Book book : bookList) {
            if (filter.test(book)) {
                book.printBook();
            }
        }
    }

    public static void main(String[] args) {

        // 创建书单
        List<Book> bookList = Book.createBookList();

        // 使用 Lambda 表达式指定搜索条件代码
        // 打印出符合条件的图书信息
        printBooks(bookList,
                // Lambda 表达式可以省略参数的数据类型;如果只有一个参数,则还可以省略括号
                book -> Objects.equals(book.getPublisher(), PUBLISHER)
                        && book.getCny() <= MAX_AMOUNT);
    }
}

将标准函数接口(Consumer)与 Lambda 表达式一起使用

我们再考虑一下上面的 printBooks 方法,是否在哪里还可以再使用 Lambda 表达式呢?

public static void printBooks(List<Book> bookList, Predicate<Book> filter) {
    for (Book book : bookList) {
        if (filter.test(book)) {
            book.printBook();
        }
    }
}

这个方法检查 List 参数 bookList 中包含的每个 Book 实例是否满足 Predicate 参数 filter 中指定的条件。如果 Book 实例满足 filter 指定的条件,则在 Book 实例上调用 printBook 方法。本身 printBooks 方法并不返回任何值。

printBooks 分析得差不多了,那么我们在 JDK 的 java.util.function 包中找一下,是否存在一个根据一个参数但没有返回值的函数接口呢?

功夫不负有心人,我们发现了 Consumer<T> 标准函数接口,它只包含一个根据一个参数但没返回值的抽象方法:

package java.util.function;

import java.util.Objects;

/**
 * Represents an operation that accepts a single input argument and returns no
 * result. Unlike most other functional interfaces, {@code Consumer} is expected
 * to operate via side-effects.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #accept(Object)}.
 *
 * @param <T> the type of the input to the operation
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

现在我们就可以再次简化代码为:

package com.wuxianjiezh.test;

import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * 书单测试类。
 *
 * <p>假设我们的目的是为了筛选出书单中由“人民邮电出版社”出版,
 * 并且价格在 100 元以内(包含 100 元)的图书。
 *
 * @author 吴仙杰
 */
public class BookListTest {

    private static final String PUBLISHER = "人民邮电出版社";
    private static final float MAX_AMOUNT = 100f;

    /**
     * 打印出符合条件的图书信息。
     *
     * @param bookList 书单
     * @param filter   过滤器函数
     * @param consumer 消费者函数
     */
    public static void printBooks(List<Book> bookList,
                                  Predicate<Book> filter,
                                  Consumer<Book> consumer) {
        for (Book book : bookList) {
            if (filter.test(book)) {
                consumer.accept(book);
            }
        }
    }

    public static void main(String[] args) {

        // 创建书单
        List<Book> bookList = Book.createBookList();

        // 使用 Lambda 表达式指定搜索条件代码
        // 打印出符合条件的图书信息
        printBooks(bookList,
                book -> Objects.equals(book.getPublisher(), PUBLISHER)
                        && book.getCny() <= MAX_AMOUNT,
                book -> book.printBook());
    }
}

将标准函数接口(Function)与 Lambda 表达式一起使用

JDK 还提供了一个常用的标准函数接口 Function<T,R> ,它只包含一个根据一个参数并返回结果的抽象方法:

package java.util.function;

import java.util.Objects;

/**
 * Represents a function that accepts one argument and produces a result.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #apply(Object)}.
 *
 * @param <T> the type of the input to the function
 * @param <R> the type of the result of the function
 *
 * @since 1.8
 */
@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);

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

我们可以借助这个标准函数接口,实现对数据的额外处理。假设我们现在打算打印出符合筛选条件的图书作者,则可通过以下代码实现:

package com.wuxianjiezh.test;

import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * 书单测试类。
 *
 * <p>假设我们的目的是为了筛选出书单中由“人民邮电出版社”出版,
 * 并且价格在 100 元以内(包含 100 元)的图书作者。
 *
 * @author 吴仙杰
 */
public class BookListTest {

    private static final String PUBLISHER = "人民邮电出版社";
    private static final float MAX_AMOUNT = 100f;

    /**
     * 打印出符合条件的图书信息。
     *
     * @param bookList 书单
     * @param filter   过滤器函数
     * @param mapper   映射器函数
     * @param consumer 消费者函数
     */
    public static void printBooks(List<Book> bookList,
                                  Predicate<Book> filter,
                                  Function<Book, String> mapper,
                                  Consumer<String> consumer) {
        for (Book book : bookList) {
            if (filter.test(book)) {
                String data = mapper.apply(book);
                consumer.accept(data);
            }
        }
    }

    public static void main(String[] args) {

        // 创建书单
        List<Book> bookList = Book.createBookList();

        // 使用 Lambda 表达式指定搜索条件代码
        // 打印出符合条件的图书作者
        printBooks(bookList,
                book -> Objects.equals(book.getPublisher(), PUBLISHER)
                        && book.getCny() <= MAX_AMOUNT,
                book -> book.getAuthor(),
                author -> System.out.println(author));
    }
}

更广泛地使用泛型

在 Java 中,我们可以使用泛型使得类或方法变得更加通用:

package com.wuxianjiezh.test;

import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * 书单测试类。
 *
 * <p>假设我们的目的是为了筛选出书单中由“人民邮电出版社”出版,
 * 并且价格在 100 元以内(包含 100 元)的图书作者。
 *
 * @author 吴仙杰
 */
public class BookListTest {

    private static final String PUBLISHER = "人民邮电出版社";
    private static final float MAX_AMOUNT = 100f;

    /**
     * 打印出符合条件的数据信息。
     *
     * @param source   源数据列表
     * @param filter   过滤器函数
     * @param mapper   映射器函数
     * @param consumer 消费者函数
     */
    public static <X, Y> void processElements(List<X> source,
                                              Predicate<X> filter,
                                              Function<X, Y> mapper,
                                              Consumer<Y> consumer) {
        for (X item : source) {
            if (filter.test(item)) {
                Y data = mapper.apply(item);
                consumer.accept(data);
            }
        }
    }

    public static void main(String[] args) {

        // 创建书单
        List<Book> bookList = Book.createBookList();

        // 使用 Lambda 表达式指定搜索条件代码
        // 打印出符合条件的图书作者
        processElements(bookList,
                book -> Objects.equals(book.getPublisher(), PUBLISHER)
                        && book.getCny() <= MAX_AMOUNT,
                book -> book.getAuthor(),
                author -> System.out.println(author));
    }
}

使用聚合操作

聚合操作(aggregate operation)通常接受 Lambda 表达式作为参数,并且可以按我们的需要自定义它们的行为方式。

下面我们使用聚合操作更进一步简化上面的代码:

package com.wuxianjiezh.test;

import java.util.List;
import java.util.Objects;

/**
 * 书单测试类。
 *
 * <p>假设我们的目的是为了筛选出书单中由“人民邮电出版社”出版,
 * 并且价格在 100 元以内(包含 100 元)的图书作者。
 *
 * @author 吴仙杰
 */
public class BookListTest {

    private static final String PUBLISHER = "人民邮电出版社";
    private static final float MAX_AMOUNT = 100f;

    public static void main(String[] args) {

        // 创建书单
        List<Book> bookList = Book.createBookList();

        // 使用 Lambda 表达式指定搜索条件代码
        // 打印出符合条件的图书作者
        bookList
                // 获取源的流
                .stream()
                // 过滤出与 `Predicate` 对象匹配的对象
                .filter(book -> Objects.equals(book.getPublisher(), PUBLISHER)
                        && book.getCny() <= MAX_AMOUNT)
                // 将对象映射到由 `Function` 对象指定的另一个值
                .map(book -> book.getAuthor())
                // 执行由 `Consumer` 对象所指定的动作
                .forEach(author -> System.out.println(author));
    }
}

filtermapforEach 这些操作被称为聚合操作。

关于聚合操作我们必须要了解以下几点:

  • 聚合操作处理的是从流(stream)中来的元素,而非直接操作来自集合中的元素
  • 流是一系列元素;与集合不同,它不是存储元素的数据结构
  • 流通过管道(pipeline)传递来自源(例如集合)的值
  • 管道是一系列流操作,例如:filtermapforEach

Lambda 表达式的语法

Lambda 表达式由以下几部分组成:

  • 参数部分
    • 参数类型是可选的,若忽略参数类型,则编译器会自动从上下文中推断参数的类型。例如:(int a) 可简写为 (a)
    • 参数可以有零个、一个或多个:
      • 零个:空的小括号用于表示一组空的参数。例如:() -> 1
      • 一个:如果不显式指明类型,则不必使用小括号。例如:a -> return a*a
      • 多个:参数用小括号括起来,并以逗号分隔。例如:(a, b) -> a+b
  • 箭头标记(->
  • 表达式体,由单个表达式或语句块组成
    • 单个表达式:Java 运行时将计算该表达式,然后返回其值。可以用小括号(())括起来
    • 语句块:若有多条语句则必须用大括号({})括起来。否则,可以省略大括号

以下表达式体中的单个表达式:

book -> Objects.equals(book.getPublisher(), PUBLISHER)
&& book.getCny() <= MAX_AMOUNT

与以下表达式体中的语句块是等效的:

book -> {
    return Objects.equals(book.getPublisher(), PUBLISHER)
    && book.getCny() <= MAX_AMOUNT;
}

Lambda 表达式看起来很像方法声明。我们可以将 Lambda 表达式视为匿名方法——没有名称的方法。

  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值