1. Introduction
1.绪论
Java 8 introduced functional style programming, allowing us to parameterize general-purpose methods by passing in functions.
Java 8引入了函数式编程,允许我们通过传入函数来对通用方法进行参数化。
We’re probably most familiar with the single-parameter Java 8 functional interfaces like Function, Predicate, and Consumer.
我们可能最熟悉的是Function、Predicate和Consumer等单参数Java 8功能接口。
In this tutorial, we’re going to look at functional interfaces that use two parameters. Such functions are called binary functions and are represented in Java with the BiFunction functional interface.
在本教程中,我们将研究使用两个参数的函数接口。这样的函数被称为二元函数,在Java中用BiFunction功能接口表示。
2. Single-Parameter Functions
2.单参数函数
Let’s quickly recap how we use a single-parameter or unary function, as we do in streams:
让我们快速回顾一下我们是如何使用单参数或单值函数的,就像我们在streams中所做的那样。
List<String> mapped = Stream.of("hello", "world")
.map(word -> word + "!")
.collect(Collectors.toList());
assertThat(mapped).containsExactly("hello!", "world!");
As we can see, the map uses Function, which takes a single parameter and allows us to perform an operation on that value, returning a new value.
我们可以看到,map使用了Function,它接受一个参数,并允许我们对该值进行操作,返回一个新的值。
3. Two-Parameter Operations
3.双参数操作
The Java Stream library provides us with a reduce function that allows us to combine the elements of a stream. We need to express how the values we have accumulated so far are transformed by adding the next item.
Java Stream库为我们提供了一个reduce函数,它允许我们组合一个流的元素。我们需要表达的是,通过添加下一个项目,到目前为止我们所积累的值是如何转变的。
The reduce function uses the functional interface BinaryOperator<T>, which takes two objects of the same type as its inputs.
reduce函数使用功能接口BinaryOperator<T>,它将两个相同类型的对象作为其输入。
Let’s imagine we want to join all the items in our stream by putting the new ones at the front with a dash separator. We’ll take a look at a few ways to implement this in the following sections.
让我们想象一下,我们想通过把新的项目放在前面的破折号分隔符来连接我们流中的所有项目。在下面的章节中,我们将看一下实现这一目的的几种方法。
3.1. Using a Lambda
3.1.使用Lambda
The implementation of a lambda for a BiFunction is prefixed by two parameters, surrounded by brackets:
一个BiFunction的lambda的实现是以两个参数为前缀的,周围有括号。
String result = Stream.of("hello", "world")
.reduce("", (a, b) -> b + "-" + a);
assertThat(result).isEqualTo("world-hello-");
As we can see, the two values, a and b are Strings. We have written a lambda that combines them to make the desired output, with the second one first, and a dash in between.
我们可以看到,两个值,a和b是字符串。我们写了一个lambda,把它们结合起来,做出所需的输出,第二个在前,中间有一个破折号。
We should note that reduce uses a starting value — in this case, the empty string. Thus, we end up with a trailing dash with the above code, as the first value from our stream is joined with it.
我们应该注意,reduce使用一个起始值–在本例中是空字符串。因此,我们在上面的代码中最终出现了一个尾部的破折号,因为我们的流中的第一个值与它连在一起。
Also, we should note that Java’s type inference allows us to omit the types of our parameters most of the time. In situations where the type of a lambda is not clear from the context, we can use types for our parameters:
另外,我们应该注意到,Java的类型推理允许我们在大多数时候省略参数的类型。在lambda的类型从上下文中看不清楚的情况下,我们可以为我们的参数使用类型。
String result = Stream.of("hello", "world")
.reduce("", (String a, String b) -> b + "-" + a);
3.2. Using a Function
3.2.使用一个函数
What if we wanted to make the above algorithm not put a dash on the end? We could write more code in our lambda, but that might get messy. Let’s extract a function instead:
如果我们想让上面的算法不在结尾处加一个破折号呢?我们可以在我们的lambda中写更多的代码,但这可能会变得很混乱。让我们提取一个函数来代替。
private String combineWithoutTrailingDash(String a, String b) {
if (a.isEmpty()) {
return b;
}
return b + "-" + a;
}
And then call it:
然后再打电话给它。
String result = Stream.of("hello", "world")
.reduce("", (a, b) -> combineWithoutTrailingDash(a, b));
assertThat(result).isEqualTo("world-hello");
As we can see, the lambda calls our function, which is easier to read than putting the more complex implementation inline.
我们可以看到,lambda调用了我们的函数,这比把更复杂的实现放在内联更容易阅读。
3.3. Using a Method Reference
3.3.使用方法参考
Some IDEs will automatically prompt us to convert the lambda above into a method reference, as it’s often clearer to read.
一些IDE会自动提示我们将上面的lambda转换为方法引用,因为它通常更容易阅读。
Let’s rewrite our code to use a method reference:
让我们重写我们的代码,使用方法引用。
String result = Stream.of("hello", "world")
.reduce("", this::combineWithoutTrailingDash);
assertThat(result).isEqualTo("world-hello");
Method references often make the functional code more self-explanatory.
方法引用常常使功能代码更加自明。。
4. Using BiFunction
4.使用BiFunction
So far, we’ve demonstrated how to use functions where both parameters are of the same type. The BiFunction interface allows us to use parameters of different types, with a return value of a third type.
到目前为止,我们已经演示了如何使用两个参数都是同一类型的函数。BiFunction接口允许我们使用不同类型的参数,其返回值为第三种类型。
Let’s imagine that we’re creating an algorithm to combine two lists of equal size into a third list by performing an operation on each pair of elements:
让我们想象一下,我们正在创建一个算法,通过对每一对元素进行操作,将两个大小相同的列表合并成第三个列表。
List<String> list1 = Arrays.asList("a", "b", "c");
List<Integer> list2 = Arrays.asList(1, 2, 3);
List<String> result = new ArrayList<>();
for (int i=0; i < list1.size(); i++) {
result.add(list1.get(i) + list2.get(i));
}
assertThat(result).containsExactly("a1", "b2", "c3");
4.1. Generalize the Function
4.1.归纳函数
We can generalize this specialized function using a BiFunction as the combiner:
我们可以用一个BiFunction作为组合器来概括这个专门的函数。
private static <T, U, R> List<R> listCombiner(
List<T> list1, List<U> list2, BiFunction<T, U, R> combiner) {
List<R> result = new ArrayList<>();
for (int i = 0; i < list1.size(); i++) {
result.add(combiner.apply(list1.get(i), list2.get(i)));
}
return result;
}
Let’s see what’s going on here. There are three types of parameters: T for the type of item in the first list, U for the type in the second list, and then R for whatever type the combination function returns.
让我们看看这里发生了什么。有三种类型的参数。T表示第一个列表中项目的类型,U表示第二个列表中的类型,然后R表示组合函数返回的任何类型。
We use the BiFunction provided to this function by calling its apply method to get the result.
我们使用提供给这个函数的BiFunction,调用其apply方法来获得结果。
4.2. Calling the Generalized Function
4.2.调用泛化函数
Our combiner is a BiFunction, which allows us to inject an algorithm, whatever the types of input and output. Let’s try it out:
我们的组合器是一个BiFunction,它允许我们注入一个算法,不管输入和输出的类型是什么。让我们试试吧。
List<String> list1 = Arrays.asList("a", "b", "c");
List<Integer> list2 = Arrays.asList(1, 2, 3);
List<String> result = listCombiner(list1, list2, (letter, number) -> letter + number);
assertThat(result).containsExactly("a1", "b2", "c3");
And we can use this for completely different types of inputs and outputs, too.
而且我们也可以将其用于完全不同类型的输入和输出。
Let’s inject an algorithm to determine if the value in the first list is greater than the value in the second and produce a boolean result:
让我们注入一个算法来确定第一个列表中的值是否大于第二个列表中的值,并产生一个boolean结果。
List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);
List<Boolean> result = listCombiner(list1, list2, (doubleNumber, floatNumber) -> doubleNumber > floatNumber);
assertThat(result).containsExactly(true, true, false);
4.3. A BiFunction Method Reference
4.3.一个BiFunction方法引用
Let’s rewrite the above code with an extracted method and a method reference:
让我们用一个提取的方法和一个方法引用重写上面的代码。
List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);
List<Boolean> result = listCombiner(list1, list2, this::firstIsGreaterThanSecond);
assertThat(result).containsExactly(true, true, false);
private boolean firstIsGreaterThanSecond(Double a, Float b) {
return a > b;
}
We should note that this makes the code a little easier to read, as the method firstIsGreaterThanSecond describes the algorithm injected as a method reference.
我们应该注意到,这使得代码更容易阅读,因为方法firstIsGreaterThanSecond描述了作为方法引用注入的算法。
4.4. BiFunction Method References Using this
4.4.BiFunction方法引用使用this
Let’s imagine we want to use the above BiFunction-based algorithm to determine if two lists are equal:
让我们设想一下,我们想用上述基于BiFunction-的算法来确定两个列表是否相等。
List<Float> list1 = Arrays.asList(0.1f, 0.2f, 4f);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);
List<Boolean> result = listCombiner(list1, list2, (float1, float2) -> float1.equals(float2));
assertThat(result).containsExactly(true, true, true);
We can actually simplify the solution:
我们实际上可以简化解决方案。
List<Boolean> result = listCombiner(list1, list2, Float::equals);
This is because the equals function in Float has the same signature as a BiFunction. It takes an implicit first parameter of this, an object of type Float. The second parameter, other, of type Object, is the value to compare.
这是因为Float中的equals函数的签名与BiFunction相同。它接受一个隐含的第一个参数this,一个Float类型的对象。第二个参数,other,类型为Object,是要比较的值。
5. Composing BiFunctions
5.组成BiFunctions
What if we could use method references to do the same thing as our numeric list comparison example?
如果我们可以使用方法引用来做与我们的数字列表比较例子相同的事情呢?
List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Double> list2 = Arrays.asList(0.1d, 0.2d, 4d);
List<Integer> result = listCombiner(list1, list2, Double::compareTo);
assertThat(result).containsExactly(1, 1, -1);
This is close to our example but returns an Integer, rather than the original Boolean. This is because the compareTo method in Double returns Integer.
这与我们的例子很接近,但是返回Integer,而不是原来的Boolean。这是因为Double中的compareTo方法返回Integer。
We can add the extra behavior we need to achieve our original by using andThen to compose a function. This produces a BiFunction that first does one thing with the two inputs and then performs another operation.
我们可以通过使用andThen组成一个函数来添加我们需要的额外行为。这将产生一个BiFunction,首先对两个输入做一件事,然后执行另一个操作。
Next, let’s create a function to coerce our method reference Double::compareTo into a BiFunction:
接下来,让我们创建一个函数,将我们的方法引用Double::compareTo胁迫成BiFunction。
private static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> function) {
return function;
}
A lambda or method reference only becomes a BiFunction after it has been converted by a method invocation. We can use this helper function to convert our lambda into the BiFunction object explicitly.
一个lambda或方法引用只有在被方法调用转换后才会成为BiFunction。我们可以使用这个辅助函数将我们的lambda明确地转换为BiFunction对象。
Now, we can use andThen to add behavior on top of the first function:
现在,我们可以使用andThen来在第一个函数的基础上添加行为。
List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Double> list2 = Arrays.asList(0.1d, 0.2d, 4d);
List<Boolean> result = listCombiner(list1, list2,
asBiFunction(Double::compareTo).andThen(i -> i > 0));
assertThat(result).containsExactly(true, true, false);
6. Conclusion
6.结语
In this tutorial, we’ve explored BiFunction and BinaryOperator in terms of the provided Java Streams library and our own custom functions. We’ve looked at how to pass BiFunctions using lambdas and method references, and we’ve seen how to compose functions.
在本教程中,我们从所提供的Java Streams库和我们自己的自定义函数方面探索了BiFunction和BinaryOperator。我们研究了如何使用lambdas和方法引用来传递BiFunctions,我们还看到了如何组合函数。
The Java libraries only provide one- and two-parameter functional interfaces. For situations that require more parameters, see our article on currying for more ideas.
Java库只提供单参数和双参数的功能接口。对于需要更多参数的情况,请参见我们关于currying的文章,以获得更多想法。
As always, the complete code samples are available over on GitHub.
一如既往,完整的代码样本可在GitHub上获得over。