Functional Interfaces in Java 8 – Java 8中的功能接口

最后修改: 2016年 8月 26日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

This tutorial is a guide to different functional interfaces present in Java 8, as well as their general use cases, and usage in the standard JDK library.

本教程将介绍Java 8中的不同功能接口,以及它们的一般使用情况和在标准JDK库中的用法。

2. Lambdas in Java 8

2.Java 8中的Lambdas

Java 8 brought a powerful new syntactic improvement in the form of lambda expressions. A lambda is an anonymous function that we can handle as a first-class language citizen. For instance, we can pass it to or return it from a method.

Java 8以lambda表达式的形式带来了一个强大的新语法改进。lambda是一个匿名函数,我们可以把它作为一流的语言公民来处理。例如,我们可以把它传递给一个方法或从一个方法中返回。

Before Java 8, we would usually create a class for every case where we needed to encapsulate a single piece of functionality. This implied a lot of unnecessary boilerplate code to define something that served as a primitive function representation.

在Java 8之前,我们通常会为每个需要封装单一功能的情况创建一个类。这意味着有很多不必要的模板代码来定义作为原始函数表示的东西。

The article “Lambda Expressions and Functional Interfaces: Tips and Best Practices” describes in more detail the functional interfaces and best practices of working with lambdas. This guide focuses on some particular functional interfaces that are present in the java.util.function package.

“Lambda表达式和功能接口。提示和最佳实践”更详细地描述了功能接口和使用lambdas的最佳实践。本指南的重点是java.util.function包中的一些特殊功能接口。

3. Functional Interfaces

3.功能接口

It’s recommended that all functional interfaces have an informative @FunctionalInterface annotation. This clearly communicates the purpose of the interface, and also allows a compiler to generate an error if the annotated interface does not satisfy the conditions.

我们建议所有的功能接口都有一个内容丰富的@FunctionalInterface注释。这清楚地传达了接口的目的,同时也允许编译器在注解的接口不满足条件时产生错误。

Any interface with a SAM(Single Abstract Method) is a functional interface, and its implementation may be treated as lambda expressions.

任何带有SAM(Single Abstract Method)的接口都是一个功能接口,其实现可以被视为lambda表达式。

Note that Java 8’s default methods are not abstract and do not count; a functional interface may still have multiple default methods. We can observe this by looking at the Function’s documentation.

请注意,Java 8的default方法不是abstract,也不算数;一个函数式接口仍然可以有多个default方法。我们可以通过查看Function的文档来观察这一点。

4. Functions

4.功能

The most simple and general case of a lambda is a functional interface with a method that receives one value and returns another. This function of a single argument is represented by the Function interface, which is parameterized by the types of its argument and a return value:

lambda最简单和普遍的情况是一个函数接口,它有一个接收一个值并返回另一个值的方法。这种单一参数的函数由Function接口表示,它由其参数的类型和返回值组成。

public interface Function<T, R> { … }

One of the usages of the Function type in the standard library is the Map.computeIfAbsent method. This method returns a value from a map by key, but calculates a value if a key is not already present in a map. To calculate a value, it uses the passed Function implementation:

标准库中的Function类型的用法之一是Map.computeIfAbsent方法。这个方法通过键从地图中返回一个值,但是如果一个键在地图中不存在,就会计算一个值。为了计算一个值,它使用传递的Function实现。

Map<String, Integer> nameMap = new HashMap<>();
Integer value = nameMap.computeIfAbsent("John", s -> s.length());

In this case, we will calculate a value by applying a function to a key, put inside a map, and also returned from a method call. We may replace the lambda with a method reference that matches passed and returned value types.

在这种情况下,我们将通过对一个键应用一个函数来计算一个值,放在一个map里面,同时也从一个方法调用中返回。We可以用一个与传递和返回的值类型相匹配的方法引用来替换lambda

Remember that an object we invoke the method on is, in fact, the implicit first argument of a method. This allows us to cast an instance method length reference to a Function interface:

记住,我们调用方法的对象实际上是方法的隐含第一参数。这允许我们将一个实例方法的length引用投到Function接口。

Integer value = nameMap.computeIfAbsent("John", String::length);

The Function interface also has a default compose method that allows us to combine several functions into one and execute them sequentially:

Function接口也有一个默认的compose方法,允许我们将几个函数合并成一个,并按顺序执行。

Function<Integer, String> intToString = Object::toString;
Function<String, String> quote = s -> "'" + s + "'";

Function<Integer, String> quoteIntToString = quote.compose(intToString);

assertEquals("'5'", quoteIntToString.apply(5));

The quoteIntToString function is a combination of the quote function applied to a result of the intToString function.

quoteIntToString函数是quote函数的组合,适用于intToString函数的结果。

5. Primitive Function Specializations

5.原始函数的特殊化

Since a primitive type can’t be a generic type argument, there are versions of the Function interface for the most used primitive types double, int, long, and their combinations in argument and return types:

由于原始类型不能成为通用类型的参数,所以对于最常用的原始类型double,int,long以及它们在参数和返回类型中的组合,都有Function接口的版本。

  • IntFunction, LongFunction, DoubleFunction: arguments are of specified type, return type is parameterized
  • ToIntFunction, ToLongFunction, ToDoubleFunction: return type is of specified type, arguments are parameterized
  • DoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction: having both argument and return type defined as primitive types, as specified by their names

As an example, there is no out-of-the-box functional interface for a function that takes a short and returns a byte, but nothing stops us from writing our own:

举例来说,对于一个接收short并返回byte的函数,并没有开箱即用的功能接口,但没有什么能阻止我们自己编写。

@FunctionalInterface
public interface ShortToByteFunction {

    byte applyAsByte(short s);

}

Now we can write a method that transforms an array of short to an array of byte using a rule defined by a ShortToByteFunction:

现在我们可以写一个方法,使用ShortToByteFunction定义的规则将一个short数组转换为byte数组。

public byte[] transformArray(short[] array, ShortToByteFunction function) {
    byte[] transformedArray = new byte[array.length];
    for (int i = 0; i < array.length; i++) {
        transformedArray[i] = function.applyAsByte(array[i]);
    }
    return transformedArray;
}

Here’s how we could use it to transform an array of shorts to an array of bytes multiplied by 2:

下面是我们如何使用它来将一个短线数组转换为一个乘以2的字节数组。

short[] array = {(short) 1, (short) 2, (short) 3};
byte[] transformedArray = transformArray(array, s -> (byte) (s * 2));

byte[] expectedArray = {(byte) 2, (byte) 4, (byte) 6};
assertArrayEquals(expectedArray, transformedArray);

6. Two-Arity Function Specializations

6.二元函数的专业化

To define lambdas with two arguments, we have to use additional interfaces that contain “Bi” keyword in their names: BiFunction, ToDoubleBiFunction, ToIntBiFunction, and ToLongBiFunction.

为了定义有两个参数的lambdas,我们必须使用额外的接口,在其名称中包含”Bi”关键字。BiFunction, ToDoubleBiFunction, ToIntBiFunction, 和ToLongBiFunction

BiFunction has both arguments and a return type generified, while ToDoubleBiFunction and others allow us to return a primitive value.

BiFunction同时生成了参数和返回类型,而ToDoubleBiFunction和其他函数允许我们返回一个原始值。

One of the typical examples of using this interface in the standard API is in the Map.replaceAll method, which allows replacing all values in a map with some computed value.

在标准API中使用这个接口的一个典型例子是Map.replaceAll方法,它允许用一些计算值来替换一个地图中的所有值。

Let’s use a BiFunction implementation that receives a key and an old value to calculate a new value for the salary and return it.

让我们使用一个BiFunction实现,它接收一个键和一个旧值,计算出工资的新值并返回。

Map<String, Integer> salaries = new HashMap<>();
salaries.put("John", 40000);
salaries.put("Freddy", 30000);
salaries.put("Samuel", 50000);

salaries.replaceAll((name, oldValue) -> 
  name.equals("Freddy") ? oldValue : oldValue + 10000);

7. Suppliers

7.供应商

The Supplier functional interface is yet another Function specialization that does not take any arguments. We typically use it for lazy generation of values. For instance, let’s define a function that squares a double value. It will not receive a value itself, but a Supplier of this value:

Supplier功能接口是另一个不接受任何参数的Function专门化。我们通常使用它来懒惰地生成值。例如,让我们定义一个函数,它可以将一个double值平方化。它不会接收一个值本身,而是这个值的Supplier

public double squareLazy(Supplier<Double> lazyValue) {
    return Math.pow(lazyValue.get(), 2);
}

This allows us to lazily generate the argument for invocation of this function using a Supplier implementation. This can be useful if the generation of the argument takes a considerable amount of time. We’ll simulate that using Guava’s sleepUninterruptibly method:

这允许我们使用Supplier实现来懒散地生成调用此函数的参数。如果参数的生成需要相当长的时间,这就很有用了。我们将使用Guava的sleepUninterruptibly方法进行模拟。

Supplier<Double> lazyValue = () -> {
    Uninterruptibles.sleepUninterruptibly(1000, TimeUnit.MILLISECONDS);
    return 9d;
};

Double valueSquared = squareLazy(lazyValue);

Another use case for the Supplier is defining logic for sequence generation. To demonstrate it, let’s use a static Stream.generate method to create a Stream of Fibonacci numbers:

Supplier的另一个用例是定义序列生成的逻辑。为了证明这一点,让我们使用静态的Stream.generate方法来创建Fibonacci数字的Stream

int[] fibs = {0, 1};
Stream<Integer> fibonacci = Stream.generate(() -> {
    int result = fibs[1];
    int fib3 = fibs[0] + fibs[1];
    fibs[0] = fibs[1];
    fibs[1] = fib3;
    return result;
});

The function that we pass to the Stream.generate method implements the Supplier functional interface. Notice that to be useful as a generator, the Supplier usually needs some sort of external state. In this case, its state comprises the last two Fibonacci sequence numbers.

我们传递给Stream.generate方法的函数实现了Supplier功能接口。注意,为了成为一个有用的生成器,Supplier通常需要某种外部状态。在这种情况下,它的状态包括最后两个斐波那契数列数字。

To implement this state, we use an array instead of a couple of variables because all external variables used inside the lambda have to be effectively final.

为了实现这种状态,我们使用一个数组而不是几个变量,因为在lambda内部使用的所有外部变量都必须是有效的final

Other specializations of the Supplier functional interface include BooleanSupplier, DoubleSupplier, LongSupplier and IntSupplier, whose return types are corresponding primitives.

Supplier功能接口的其它特殊化包括BooleanSupplierDoubleSupplierLongSupplierIntSupplier,其返回类型是相应的基元。

8. Consumers

8.消费者

As opposed to the Supplier, the Consumer accepts a generified argument and returns nothing. It is a function that is representing side effects.

Supplier相反,Consumer接受一个生成的参数,并且不返回任何东西。它是一个代表副作用的函数。

For instance, let’s greet everybody in a list of names by printing the greeting in the console. The lambda passed to the List.forEach method implements the Consumer functional interface:

例如,让我们通过在控制台中打印问候语来问候一个名字列表中的所有人。传递给List.forEach方法的lambda实现了Consumer功能接口。

List<String> names = Arrays.asList("John", "Freddy", "Samuel");
names.forEach(name -> System.out.println("Hello, " + name));

There are also specialized versions of the ConsumerDoubleConsumer, IntConsumer and LongConsumer — that receive primitive values as arguments. More interesting is the BiConsumer interface. One of its use cases is iterating through the entries of a map:

还有一些专门的Consumer版本–DoubleConsumerIntConsumerLongConsumer–它们接收原始值作为参数。更有趣的是BiConsumer接口。它的一个用例是遍历一个地图的条目。

Map<String, Integer> ages = new HashMap<>();
ages.put("John", 25);
ages.put("Freddy", 24);
ages.put("Samuel", 30);

ages.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));

Another set of specialized BiConsumer versions is comprised of ObjDoubleConsumer, ObjIntConsumer, and ObjLongConsumer, which receive two arguments; one of the arguments is generified, and the other is a primitive type.

另一组专门的BiConsumer版本由ObjDoubleConsumerObjIntConsumerObjLongConsumer组成,它们接收两个参数;其中一个参数是生成的,而另一个是原始类型。

9. Predicates

9.谓词

In mathematical logic, a predicate is a function that receives a value and returns a boolean value.

在数理逻辑中,谓词是一个接收一个值并返回一个布尔值的函数。

The Predicate functional interface is a specialization of a Function that receives a generified value and returns a boolean. A typical use case of the Predicate lambda is to filter a collection of values:

Predicate功能接口是Function的特化,它接收一个生成的值并返回一个布尔值。Predicate lambda的一个典型用例是过滤一个值的集合。

List<String> names = Arrays.asList("Angela", "Aaron", "Bob", "Claire", "David");

List<String> namesWithA = names.stream()
  .filter(name -> name.startsWith("A"))
  .collect(Collectors.toList());

In the code above, we filter a list using the Stream API and keep only the names that start with the letter “A”. The Predicate implementation encapsulates the filtering logic.

在上面的代码中,我们使用Stream API 过滤一个列表,只保留以字母 “A “开头的名字。Predicate实现封装了过滤逻辑。

As in all of the previous examples, there are IntPredicate, DoublePredicate and LongPredicate versions of this function that receive primitive values.

和前面所有的例子一样,这个函数有IntPredicateDoublePredicateLongPredicate版本,可以接收原始值。

10. Operators

10.操作员

Operator interfaces are special cases of a function that receive and return the same value type. The UnaryOperator interface receives a single argument. One of its use cases in the Collections API is to replace all values in a list with some computed values of the same type:

Operator接口是一个函数的特殊情况,它接收和返回相同的值类型。UnaryOperator接口接收一个参数。它在集合API中的一个用例是用一些相同类型的计算值来替换列表中的所有值。

List<String> names = Arrays.asList("bob", "josh", "megan");

names.replaceAll(name -> name.toUpperCase());

The List.replaceAll function returns void as it replaces the values in place. To fit the purpose, the lambda used to transform the values of a list has to return the same result type as it receives. This is why the UnaryOperator is useful here.

List.replaceAll函数返回void,因为它替换了原位的值。为了符合目的,用于转换列表中的值的 lambda 必须返回与它收到的相同的结果类型。这就是为什么UnaryOperator在这里很有用。

Of course, instead of name -> name.toUpperCase(), we can simply use a method reference:

当然,代替name -> name.toUpperCase(),我们可以简单地使用一个方法引用。

names.replaceAll(String::toUpperCase);

One of the most interesting use cases of a BinaryOperator is a reduction operation. Suppose we want to aggregate a collection of integers in a sum of all values. With Stream API, we could do this using a collector, but a more generic way to do it would be to use the reduce method:

BinaryOperator最有趣的用例之一是还原操作。假设我们想将一个整数集合汇总为所有值的总和。通过Stream API,我们可以使用收集器来完成这个操作,但是更通用的方法是使用reduce方法。

List<Integer> values = Arrays.asList(3, 5, 8, 9, 12);

int sum = values.stream()
  .reduce(0, (i1, i2) -> i1 + i2);

The reduce method receives an initial accumulator value and a BinaryOperator function. The arguments of this function are a pair of values of the same type; the function itself also contains a logic for joining them in a single value of the same type. The passed function must be associative, which means that the order of value aggregation does not matter, i.e. the following condition should hold:

reduce方法接收一个初始累积器值和一个BinaryOperator函数。该函数的参数是一对相同类型的值;该函数本身也包含一个逻辑,用于将它们连接成一个相同类型的单一值。传递的函数必须是关联的,这意味着值聚合的顺序并不重要,也就是说,以下条件应该成立。

op.apply(a, op.apply(b, c)) == op.apply(op.apply(a, b), c)

The associative property of a BinaryOperator operator function allows us to easily parallelize the reduction process.

二元操作符操作符函数的关联属性使我们能够轻松地将还原过程并行化。

Of course, there are also specializations of UnaryOperator and BinaryOperator that can be used with primitive values, namely DoubleUnaryOperator, IntUnaryOperator, LongUnaryOperator, DoubleBinaryOperator, IntBinaryOperator, and LongBinaryOperator.

当然,还有UnaryOperatorBinaryOperator的特殊化,可用于原始值,即DoubleUnaryOperatorIntUnaryOperator, LongUnaryOperator, DoubleBinaryOperator, IntBinaryOperator, 和LongBinaryOperator

11. Legacy Functional Interfaces

11.遗留的功能接口

Not all functional interfaces appeared in Java 8. Many interfaces from previous versions of Java conform to the constraints of a FunctionalInterface, and we can use them as lambdas. Prominent examples include the Runnable and Callable interfaces that are used in concurrency APIs. In Java 8, these interfaces are also marked with a @FunctionalInterface annotation. This allows us to greatly simplify concurrency code:

并非所有的功能接口都出现在Java 8中。许多以前版本的Java接口符合FunctionalInterface的约束,我们可以将它们作为lambdas使用。突出的例子包括RunnableCallable接口,它们被用于并发API。在Java 8中,这些接口也被标记为@FunctionalInterface注解。这使我们能够大大简化并发代码。

Thread thread = new Thread(() -> System.out.println("Hello From Another Thread"));
thread.start();

12. Conclusion

12.结论

In this article, we examined different functional interfaces present in the Java 8 API that we can use as lambda expressions. The source code for the article is available over on GitHub.

在这篇文章中,我们研究了Java 8 API中存在的不同功能接口,我们可以将其作为lambda表达式使用。本文的源代码可在GitHub上找到