Java 开发工具包(JDK)中有许多函数式接口。这里我们来回顾一下常用的入门接口集合,这些接口是我们常常会遇到且需要熟悉的。我们在这里看到的所有接口都属于 java.util.function 包。
在本书中,我们一直在使用函数式接口、Lambda 表达式、方法引用和构造函数引用的新语法。本附录是一个快速的语法参考,使用从本书不同部分选取的示例代码。
@FunctionalInterface
public interface TailCall<T> {
TailCall<T> apply();
default boolean isComplete() { return false; }
//...
}函数式接口必须有一个抽象(未实现)方法。它可以有零个或多个默认方法或已实现的方法,也可以有静态方法。
lazyEvaluator(() -> evaluate(1), () -> evaluate(2));如果 Lambda 表达式没有参数,空参数列表周围的括号 () 是必需的。→ 用于分隔 Lambda 表达式的参数和主体。
friends.forEach((final String name) -> System.out.println(name));Java 编译器可以根据上下文推断 Lambda 表达式的类型。在某些上下文不足以进行推断或者我们希望代码更清晰的情况下,我们可以在参数名前指定类型。
friends.forEach((name) -> System.out.println(name));如果我们不提供参数类型,Java 编译器会尝试推断参数类型。使用推断类型可以减少代码冗余,并且更省力。但如果我们为一个参数指定了类型,就必须为 Lambda 表达式中的所有参数指定类型。
friends.forEach(name -> System.out.println(name));如果 Lambda 表达式只接受一个参数并且其类型是推断得出的,参数周围的括号 () 是可选的。我们可以写成 name → … 或 (name) → …,提议使用前者,由于它更简洁。
friends.stream()
.reduce((name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);如果 Lambda 表达式接受多个参数或没有参数,参数列表周围的括号 () 是必需的。
friends.stream()
.reduce("Steve", (name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);方法的参数可以是普通类、基本类型和函数式接口的混合。方法的任何参数都可以是函数式接口,我们可以将 Lambda 表达式或方法引用作为参数传递。
final Predicate<String> startsWithN = name -> name.startsWith("N");为了便于复用和避免代码重复,我们一般希望将 Lambda 表达式存储在变量中。
FileWriterEAM.use("eam2.txt", writerEAM -> {
writerEAM.writeStuff("how");
writerEAM.writeStuff("sweet");
});我们应该尽量保持 Lambda 表达式简短,但偶尔也会有多行代码的情况。此时需要使用花括号 {},并且如果 Lambda 表达式需要返回值,则必须使用 return 关键字。
public static Predicate<String> checkIfStartsWith(final String letter) {
return name -> name.startsWith(letter);
}如果方法的返回类型是函数式接口,我们可以在实则现中返回一个 Lambda 表达式。
final Function<String, Predicate<String>> startsWithLetter =
letter -> name -> name.startsWith(letter);我们可以构建本身返回 Lambda 表达式的 Lambda 表达式。这里 Function 接口的实现接受一个 String 类型的 letter,并返回一个符合 Predicate 接口的 Lambda 表达式。
public static Predicate<String> checkIfStartsWith(final String letter) {
return name -> name.startsWith(letter);
}在 Lambda 表达式内部,我们可以访问外部方法作用域中的变量。例如,在 checkIfStartsWith 方法中,letter 变量在 Lambda 表达式内部被访问。绑定到外部作用域变量的 Lambda 表达式称为闭包。
friends.stream()
.map(String::toUpperCase);如果 Lambda 表达式直接将参数作为目标传递给一个简单的方法调用,我们可以用方法引用替换它。上述示例代码等同于:
friends.stream()
.map(name -> name.toUpperCase());str.chars()
.filter(Character::isDigit);如果 Lambda 表达式直接将参数作为参数传递给一个静态方法,我们可以用方法引用替换它。上述示例代码等同于:
str.chars()
.filter(ch -> Character.isDigit(ch));str.chars()
.forEach(System.out::println);如果 Lambda 表达式直接将参数作为参数传递给另一个实例上的方法(例如 System.out 上的 println 方法),我们可以用方法引用替换它。上述示例代码等同于:
str.chars()
.forEach(ch -> System.out.println(ch));people.stream()
.sorted(Person::ageDifference)如果 Lambda 表达式直接将第一个参数作为方法调用的目标,其余参数作为该方法的参数,我们可以用方法引用替换它。上述示例代码等同于:
people.stream()
.sorted((person1, person2) -> person1.ageDifference(person2))Supplier<Heavy> supplier = Heavy::new;我们可以使用简洁的构造函数引用语法让 Java 编译器创建对适当构造函数的调用,而不是直接调用构造函数。这与方法引用类似,只是它引用的是构造函数并会实例化对象。上述示例代码等同于:
Supplier<Heavy> supplier = () -> new Heavy();symbols.map(StockUtil::getPrice)
.filter(StockUtil.isPriceLessThan(500))
.reduce(StockUtil::pickHigh)
.get();我们可以组合函数,通过一系列操作来转换对象,就像这个示例一样。在函数式编程风格中,函数组合或链式调用是实现关联操作的强劲结构。