jOOLの紹介

jOOLの概要

1. 概要

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

2. メーベン依存

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


    org.jooq
    jool
    0.9.12

最新バージョンhereを見つけることができます。

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

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

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

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

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

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

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

Integer result = addToTwo.apply(5);

assertEquals(result, (Integer) 7);

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

BiFunction biFunc = addTwoNumbers.toBiFunction();

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

4. タプル

タプルは、関数型プログラミングの世界で非常に重要な構成要素です。 これは、値ごとに異なる型を持つことができる値の型付きコンテナです。 Tuples are often used as function arguments

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

tuple(2, 2)

そして、4つの値に対して:

tuple(1,2,3,4);

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

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

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

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

タプルに対してさまざまな種類の変換を使用できます。 最初に、limit2()メソッドを呼び出してTuple3.から2つの値のみを取得します。次に、concat()メソッドを呼び出して2つのタプルを連結します。

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

5. Seq

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

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

Seq.内の要素の存在をチェックするメソッドのいくつかのバリエーションを見つけることができます。これらのメソッドのいくつかは、Streamクラスの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 left = Stream.of(1, 2, 4);
Stream right = Stream.of(1, 2, 3);

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

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

java.lang.IllegalStateException: stream has already been operated upon or closed.を防ぐために、rightストリームをリストに収集する必要があります。次に、filterメソッドからrightCollectedリストにアクセスして副作用操作を行う必要があります。 エラーが発生しやすく、2つのデータセットを結合するエレガントな方法ではありません。

幸い、Seq has useful methods to do inner, left and right joins on data sets.これらのメソッドは、その実装を非表示にして、エレガントな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.groupingByコレクターでcollect()メソッドを使用する必要があるためです。

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

Map> 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つのシーケンスに圧縮する必要があります。

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

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つのシーケンスを圧縮しているが、特定の方法でそれらを圧縮したい場合、要素を圧縮する方法を定義するBiFunctionメソッドに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 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 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 projectにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。