1. 概要

この記事では、 jOOL ライブラリ( jOOQ の別の製品)について説明します。

2. Mavenの依存関係

pom.xmlにMaven依存関係を追加することから始めましょう。

<dependency>
    <groupId>org.jooq</groupId>
    <artifactId>jool</artifactId>
    <version>0.9.12</version>
</dependency>

最新バージョンはここにあります。

3. 機能インターフェイス

Java 8では、機能インターフェイスはかなり制限されています。 これらは最大数の2つのパラメーターを受け入れ、多くの追加機能はありません。

jOOLは、16個のパラメーター( Function1からFunction16まで)を受け入れることができ、追加の便利なメソッドで強化された一連の新しい機能インターフェイスを提供することで、これを修正します。

たとえば、3つの引数を取る関数を作成するには、 Function3:を使用できます。

Function3<String, String, String, Integer> lengthSum
  = (v1, v2, v3) -> v1.length() + v2.length() + v3.length();

純粋なJavaでは、自分で実装する必要があります。 さらに、jOOLの機能インターフェイスには、部分適用を簡単に実行できるメソッド applyPartially()があります。

Function2<Integer, Integer, Integer> addTwoNumbers = (v1, v2) -> v1 + v2;
Function1<Integer, Integer> addToTwo = addTwoNumbers.applyPartially(2);

Integer result = addToTwo.apply(5);

assertEquals(result, (Integer) 7);

Function2 タイプのメソッドがある場合、 toBiFunction()メソッドを使用して、標準のJava BiFunctionに簡単に変換できます。

BiFunction biFunc = addTwoNumbers.toBiFunction();

同様に、 Function1タイプにはtoFunction()メソッドがあります。

4. タプル

タプルは、関数型プログラミングの世界では非常に重要な構成要素です。 これは、各値が異なるタイプを持つことができる値の型付きコンテナーです。 タプルは関数の引数としてよく使用されます

これらは、一連のイベントで変換を行うときにも非常に役立ちます。 jOOLには、Tuple1からTuple16タイプによって提供される、1から16までの値をラップできるタプルがあります。

tuple(2, 2)

そして4つの値のために:

tuple(1,2,3,4);

3つの値を持つタプルのシーケンスがある場合の例を考えてみましょう。

Seq<Tuple3<String, String, Integer>> personDetails = Seq.of(
  tuple("michael", "similar", 49),
  tuple("jodie", "variable", 43));
Tuple2<String, String> tuple = tuple("winter", "summer");

List<Tuple4<String, String, String, String>> result = personDetails
  .map(t -> t.limit2().concat(tuple)).toList();

assertEquals(
  result,
  Arrays.asList(tuple("michael", "similar", "winter", "summer"), tuple("jodie", "variable", "winter", "summer"))
);

タプルにはさまざまな種類の変換を使用できます。 まず、 limit2() から2つの値のみを取得する方法タプル3。 次に、 concat() 2つのタプルを連結するメソッド。

その結果、Tuple4タイプの値が得られます。

5. Seq

Seq コンストラクトは、 Stream に高レベルのメソッドを追加しますが、多くの場合、その下のメソッドを使用します。

5.1. 操作が含まれています

内の要素の存在をチェックするメソッドのいくつかのバリエーションを見つけることができますシーケンス それらの方法のいくつかは、 anyMatch() からの方法ストリームクラス:

assertTrue(Seq.of(1, 2, 3, 4).contains(2));

assertTrue(Seq.of(1, 2, 3, 4).containsAll(2, 3));

assertTrue(Seq.of(1, 2, 3, 4).containsAny(2, 5));

5.2. オペレーションに参加

2つのストリームがあり、それらを結合する場合(2つのデータセットのSQL結合操作と同様)、標準の Stream クラスを使用することは、これを行うための非常に洗練された方法ではありません。

Stream<Integer> left = Stream.of(1, 2, 4);
Stream<Integer> right = Stream.of(1, 2, 3);

List<Integer> rightCollected = right.collect(Collectors.toList());
List<Integer> collect = left
  .filter(rightCollected::contains)
  .collect(Collectors.toList());

assertEquals(collect, Arrays.asList(1, 2));

java.lang.IllegalStateExceptionを防ぐために、正しいストリームをリストに収集する必要があります。ストリームはすでに操作されているか閉じられています。次に、次の方法で副作用操作を行う必要があります。 フィルターメソッドからrightCollectedリストにアクセスします。 これはエラーが発生しやすく、2つのデータセットを結合するための洗練された方法ではありません。

幸い、 Seqには、データセットの内部、左、右の結合を行うための便利なメソッドがあります。これらのメソッドは、エレガントなAPIを公開する実装を隠します。

innerJoin()メソッドを使用して、内部結合を実行できます。

assertEquals(
  Seq.of(1, 2, 4).innerJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2))
);

それに応じて、右結合と左結合を行うことができます。

assertEquals(
  Seq.of(1, 2, 4).leftOuterJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2), tuple(4, null))
);

assertEquals(
  Seq.of(1, 2, 4).rightOuterJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2), tuple(null, 3))
);

2つのデータセットのデカルト結合を可能にするcrossJoin()メソッドもあります。

assertEquals(
  Seq.of(1, 2).crossJoin(Seq.of("A", "B")).toList(),
  Arrays.asList(tuple(1, "A"), tuple(1, "B"), tuple(2, "A"), tuple(2, "B"))
);

5.3. Seqの操作

Seq には、要素のシーケンスを操作するための多くの便利な方法があります。 それらのいくつかを見てみましょう。

cycle()メソッドを使用して、ソースシーケンスから要素を繰り返し取得できます。 無限のストリームが作成されるため、結果をリストに収集するときは注意が必要です。したがって、 limit()メソッドを使用して、無限のシーケンスを有限のシーケンスに変換する必要があります。

assertEquals(
  Seq.of(1, 2, 3).cycle().limit(9).toList(),
  Arrays.asList(1, 2, 3, 1, 2, 3, 1, 2, 3)
);

1つのシーケンスから2番目のシーケンスにすべての要素を複製したいとします。 duplicate()メソッドはまさにそれを行います:

assertEquals(
  Seq.of(1, 2, 3).duplicate().map((first, second) -> tuple(first.toList(), second.toList())),
  tuple(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3))
);

duplicate()メソッドの戻り型は、2つのシーケンスのタプルです。

整数のシーケンスがあり、そのシーケンスをいくつかの述語を使用して2つのシーケンスに分割したいとします。 partition()メソッドを使用できます。

assertEquals(
  Seq.of(1, 2, 3, 4).partition(i -> i > 2)
    .map((first, second) -> tuple(first.toList(), second.toList())),
  tuple(Arrays.asList(3, 4), Arrays.asList(1, 2))
);

5.4. 要素のグループ化

Stream APIを使用して要素をキーでグループ化するのは面倒で直感的ではありません。これは、 Collectors.groupingBycollectorでcollect()メソッドを使用する必要があるためです。

Seq は、 groupBy()メソッドの背後にあるコードを非表示にして Map を返すため、 collect()メソッドを明示的に使用する必要はありません。 :

Map<Integer, List<Integer>> expectedAfterGroupBy = new HashMap<>();
expectedAfterGroupBy.put(1, Arrays.asList(1, 3));
expectedAfterGroupBy.put(0, Arrays.asList(2, 4));

assertEquals(
  Seq.of(1, 2, 3, 4).groupBy(i -> i % 2),
  expectedAfterGroupBy
);

5.5. 要素をスキップする

要素のシーケンスがあり、述語が一致しないときに要素をスキップしたいとします。 述語が満たされると、要素は結果のシーケンスで着地する必要があります。

そのためにskipWhile()メソッドを使用できます。

assertEquals(
  Seq.of(1, 2, 3, 4, 5).skipWhile(i -> i < 3).toList(),
  Arrays.asList(3, 4, 5)
);

skipUntil()メソッドを使用して同じ結果を得ることができます。

assertEquals(
  Seq.of(1, 2, 3, 4, 5).skipUntil(i -> i == 3).toList(),
  Arrays.asList(3, 4, 5)
);

5.6. シーケンスの圧縮

要素のシーケンスを処理する場合、多くの場合、要素を1つのシーケンスに圧縮する必要があります。

zip() APIは、2つのシーケンスを1つに圧縮するために使用できます。

assertEquals(
  Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c")).toList(),
  Arrays.asList(tuple(1, "a"), tuple(2, "b"), tuple(3, "c"))
);

結果のシーケンスには、2つの要素のタプルが含まれます。

2つのシーケンスを圧縮しているが、特定の方法で圧縮したい場合は、要素を圧縮する方法を定義する zip()メソッドにBiFunctionを渡すことができます。

assertEquals(
  Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c"), (x, y) -> x + ":" + y).toList(),
  Arrays.asList("1:a", "2:b", "3:c")
);

zipWithIndex() APIを使用して、このシーケンスの要素のインデックスを使用してシーケンスを圧縮すると便利な場合があります。

assertEquals(
  Seq.of("a", "b", "c").zipWithIndex().toList(),
  Arrays.asList(tuple("a", 0L), tuple("b", 1L), tuple("c", 2L))
);

6. チェックされた例外をチェックされていないものに変換する

文字列を受け取り、チェックされた例外をスローできるメソッドがあるとしましょう。

public Integer methodThatThrowsChecked(String arg) throws Exception {
    return arg.length();
}

次に、 Stream の要素をマップして、そのメソッドを各要素に適用します。 その例外をより高く処理する方法はないため、 map()メソッドでその例外を処理する必要があります。

List<Integer> collect = Stream.of("a", "b", "c").map(elem -> {
    try {
        return methodThatThrowsChecked(elem);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}).collect(Collectors.toList());

assertEquals(
    collect,
    Arrays.asList(1, 1, 1)
);

Javaの機能インターフェイスの設計のため、この例外でできることはあまりありません。そのため、catch句では、チェックされた例外をチェックされていない例外に変換しています。

幸い、jOOLには、チェックされた例外をチェックされていない例外に変換できるメソッドを持つUncheckedクラスがあります。

List<Integer> collect = Stream.of("a", "b", "c")
  .map(Unchecked.function(elem -> methodThatThrowsChecked(elem)))
  .collect(Collectors.toList());

assertEquals(
  collect,
  Arrays.asList(1, 1, 1)
);

methodThatThrowsChecked()の呼び出しを、その下の例外の変換を処理する Unchecked.function()メソッドにラップしています。

7. 結論

この記事では、Java標準の StreamAPIに便利な追加メソッドを追加するjOOLライブラリの使用方法を示します。

これらすべての例とコードスニペットの実装は、 GitHubプロジェクトにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。