JavaBiFunctionインターフェースのガイド

1.はじめに

Java 8では関数型プログラミングが導入され、関数を渡すことで汎用メソッドをパラメーター化できるようになりました。

我々は、おそらくのような単一パラメータのJava 8の機能のインターフェイスに最も精通している機能述語、および消費者

このチュートリアルでは、2つのパラメーターを使用する関数型インターフェースについて説明します。このような関数はバイナリ関数と呼ばれ、JavaではBiFunction関数インターフェイスを使用して表されます。

2.単一パラメーター関数

ストリームで行うように、単一パラメーターまたは単項関数をどのように使用するかを簡単に要約してみましょう。

List mapped = Stream.of("hello", "world") .map(word -> word + "!") .collect(Collectors.toList()); assertThat(mapped).containsExactly("hello!", "world!");

ご覧のとおり、マップFunctionを使用しています。これは、単一のパラメーターを受け取り、その値に対して操作を実行して、新しい値を返すことを可能にします。

3.2パラメータ操作

Java Streamライブラリは、ストリームの要素を組み合わせることができるreduce関数を提供します。次の項目を追加することで、これまでに蓄積した価値がどのように変化するかを表現する必要があります。

低減機能は、機能インタフェース使用BinaryOperatorその入力と同じタイプの2つのオブジェクトをとり、。

ダッシュセパレーターを使用して新しいアイテムを前面に配置することにより、ストリーム内のすべてのアイテムを結合したいとします。次のセクションでは、これを実装するいくつかの方法を見ていきます。

3.1。ラムダを使用する

BiFunctionのラムダの実装には、角かっこで囲まれた2つのパラメーターが接頭辞として付けられます。

String result = Stream.of("hello", "world") .reduce("", (a, b) -> b + "-" + a); assertThat(result).isEqualTo("world-hello-");

ご覧のとおり、abの2つの値は文字列です。それらを組み合わせて目的の出力を作成するラムダを作成しました。2番目の出力を最初に、その間にダッシュを配置します。

reduceは開始値(この場合は空の文字列)を使用することに注意してください。したがって、ストリームの最初の値がそれに結合されるため、上記のコードの末尾にダッシュが表示されます。

また、Javaの型推論では、ほとんどの場合、パラメーターの型を省略できることに注意してください。ラムダのタイプがコンテキストから明確でない状況では、パラメーターにタイプを使用できます。

String result = Stream.of("hello", "world") .reduce("", (String a, String b) -> b + "-" + a);

3.2。関数の使用

上記のアルゴリズムを最後にダッシュを付けないようにしたい場合はどうなりますか?ラムダにもっとコードを書くこともできますが、それは面倒になるかもしれません。代わりに関数を抽出しましょう:

private String combineWithoutTrailingDash(String a, String b) { if (a.isEmpty()) { return b; } return b + "-" + a; }

そしてそれを呼びます:

String result = Stream.of("hello", "world") .reduce("", (a, b) -> combineWithoutTrailingDash(a, b)); assertThat(result).isEqualTo("world-hello");

ご覧のとおり、ラムダは関数を呼び出します。これは、より複雑な実装をインラインに配置するよりも読みやすくなっています。

3.3。メソッドリファレンスの使用

一部のIDEは、上記のラムダをメソッド参照に変換するように自動的に促します。これは、読みやすいことが多いためです。

メソッド参照を使用するようにコードを書き直してみましょう。

String result = Stream.of("hello", "world") .reduce("", this::combineWithoutTrailingDash); assertThat(result).isEqualTo("world-hello");

多くの場合、メソッド参照により、関数型コードがよりわかりやすくなります。

4.BiFunctionの使用

これまで、両方のパラメーターが同じタイプである関数の使用方法を示してきました。BiFunctionのインターフェースは、私たちは、異なるタイプのパラメータを使用することを可能にする第三のタイプの戻り値と、。

要素の各ペアに対して操作を実行することにより、同じサイズの2つのリストを3番目のリストに結合するアルゴリズムを作成していると想像してみましょう。

List list1 = Arrays.asList("a", "b", "c"); List list2 = Arrays.asList(1, 2, 3); List 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。関数を一般化する

BiFunctionをコンバイナーとして使用して、この特殊な関数を一般化できます

private static  List listCombiner( List list1, List list2, BiFunction combiner) { List result = new ArrayList(); for (int i = 0; i < list1.size(); i++) { result.add(combiner.apply(list1.get(i), list2.get(i))); } return result; }

ここで何が起こっているのか見てみましょう。パラメータには3つのタイプがあります。最初のリストのアイテムのタイプを表すT、2番目のリストのタイプを表すU、そして組み合わせ関数が返すタイプを表すRです。

この関数に提供されているBiFunctionを使用して、applyメソッド呼び出して結果を取得します。

4.2。一般化された関数の呼び出し

コンバイナはBiFunctionであり、入力と出力のタイプに関係なく、アルゴリズムを挿入できます。それを試してみましょう:

List list1 = Arrays.asList("a", "b", "c"); List list2 = Arrays.asList(1, 2, 3); List result = listCombiner(list1, list2, (a, b) -> a + b); assertThat(result).containsExactly("a1", "b2", "c3");

また、これをまったく異なるタイプの入力と出力に使用することもできます。

最初のリストの値が2番目のリストの値よりも大きいかどうかを判断するアルゴリズムを注入して、ブール結果を生成してみましょう。

List list1 = Arrays.asList(1.0d, 2.1d, 3.3d); List list2 = Arrays.asList(0.1f, 0.2f, 4f); List result = listCombiner(list1, list2, (a, b) -> a > b); assertThat(result).containsExactly(true, true, false);

4.3。A BiFunctionメソッドリファレンス

抽出されたメソッドとメソッド参照を使用して、上記のコードを書き直してみましょう。

List list1 = Arrays.asList(1.0d, 2.1d, 3.3d); List list2 = Arrays.asList(0.1f, 0.2f, 4f); List 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.

4.4. BiFunction Method References Using this

Let's imagine we want to use the above BiFunction-based algorithm to determine if two lists are equal:

List list1 = Arrays.asList(0.1f, 0.2f, 4f); List list2 = Arrays.asList(0.1f, 0.2f, 4f); List result = listCombiner(list1, list2, (a, b) -> a.equals(b)); assertThat(result).containsExactly(true, true, true);

We can actually simplify the solution:

List 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.

5. Composing BiFunctions

What if we could use method references to do the same thing as our numeric list comparison example?

List list1 = Arrays.asList(1.0d, 2.1d, 3.3d); List list2 = Arrays.asList(0.1d, 0.2d, 4d); List 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.

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.

Next, let's create a function to coerce our method reference Double::compareTo into a BiFunction:

private static  BiFunction asBiFunction(BiFunction 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.

Now, we can use andThen to add behavior on top of the first function:

List list1 = Arrays.asList(1.0d, 2.1d, 3.3d); List list2 = Arrays.asList(0.1d, 0.2d, 4d); List result = listCombiner(list1, list2, asBiFunction(Double::compareTo).andThen(i -> i > 0)); assertThat(result).containsExactly(true, true, false);

6. Conclusion

このチュートリアルでは、提供されているJavaStreamsライブラリと独自のカスタム関数の観点からBiFunctionBinaryOperatorについて説明しました。私たちは、合格する方法を見てきましたBiFunctionsをラムダとメソッド参照を使用して、我々は機能を構成する方法を見てきました。

Javaライブラリは、1パラメータおよび2パラメータの関数型インターフェイスのみを提供します。より多くのパラメータを必要とする状況については、より多くのアイデアについてカリー化に関する記事を参照してください。

いつものように、完全なコードサンプルはGitHubで入手できます。