VavrのコレクションAPIガイド

1概要

以前はJavaslangと呼ばれていたVavrライブラリは、Java用の機能ライブラリです。この記事では、その強力なコレクションAPIを探ります。

このライブラリに関するより多くの情報を得るためには、リンクを読んでください:/vavr[この記事]。

2永続コレクション

永続コレクションを変更すると、現在のバージョンを維持したまま新しいバージョンのコレクションが作成されます。

同じコレクションの複数のバージョンを管理すると、CPUとメモリの使用効率が悪くなる可能性があります。しかし、Vavrコレクションライブラリは、コレクションの異なるバージョン間でデータ構造を共有することによってこれを解決します。

これは、Javaの Collections ユーティリティクラスの__unmodifiableCollection()とは根本的に異なります。

そのようなコレクションを変更しようとすると、新しいバージョンが作成されるのではなく、 UnsupportedOperationException が発生します。

さらに、基になるコレクションは、直接参照することでまだ変更可能です。

3トラバース可能

Traversable はすべてのVavrコレクションの基本型です - このインタフェースはすべてのデータ構造の間で共有されるメソッドを定義します。

size() get() filter() isEmpty() などの便利なデフォルトメソッドと、サブインタフェースによって継承されるその他のメソッドを提供します。

さらにコレクションライブラリを調べてみましょう。

4 Seq

シーケンスから始めましょう。

Seq インタフェースはシーケンシャルデータ構造を表します。これは、 List Stream Queue Array Vector 、および CharSeq の親インターフェースです。これらすべてのデータ構造には独自の特性があり、それらについては後で説明します。

4.1. リスト

List は、 LinearSeq インターフェースを拡張する、熱心に評価された一連の要素です。

永続的なリストは、頭と尾から再帰的に形成されます。

  • 頭 - 最初の要素

  • Tail - 残りの要素を含むリスト(そのリストも形成されます)

頭と尾から)

List を作成するために使用できる List APIには静的ファクトリメソッドがあります。静的な of() メソッドを使用して、1つ以上のオブジェクトから List のインスタンスを作成できます。

静的な empty() を使用して空の List を作成し、 ofAll() を使用して Iterable typeから List を作成することもできます。

List<String> list = List.of(
  "Java", "PHP", "Jquery", "JavaScript", "JShell", "JAVA");

リストを操作する方法のいくつかの例を見てみましょう。

最初の N 個の要素を削除するには、 drop() とその派生形を使用します。

List list1 = list.drop(2);
assertFalse(list1.contains("Java") && list1.contains("PHP"));

List list2 = list.dropRight(2);
assertFalse(list2.contains("JAVA") && list2.contains("JShell"));

List list3 = list.dropUntil(s -> s.contains("Shell"));
assertEquals(list3.size(), 2);

List list4 = list.dropWhile(s -> s.length() > 0);
assertTrue(list4.isEmpty());

drop(int n) は、最初の要素から始めて n 個の要素をリストから削除し、 dropRight() はリストの最後の要素から始めて同じことを行います。

dropUntil() は、述語がtrueと評価されるまでリストから要素を削除し続けますが、 dropWhile() は述語がtrueの間要素を削除し続けます。

右側から要素を削除し始める dropRightWhile() dropRightUntil() もあります。

次に、 take(int n) を使ってリストから要素を取得します。リストから n 個の要素を取り出して停止します。リストの末尾から要素を取得する takeRight(int n) もあります。

List list5 = list.take(1);
assertEquals(list5.single(), "Java");

List list6 = list.takeRight(1);
assertEquals(list6.single(), "JAVA");

List list7 = list.takeUntil(s -> s.length() > 6);
assertEquals(list7.size(), 3);

最後に、 takeUntil() は述語が真になるまでリストから要素を取り出し続けます。述語引数を取る takeWhile() バリアントもあります。

さらに、APIには他の便利なメソッドがあります。たとえば、実際には重複しない要素のリストを返す distinct() や、同等性を判断するための Comparator を受け入れる distinctBy() などです。

非常に興味深いことに、リストのすべての要素の間に要素を挿入する intersperse() もあります。これは String 操作にとても便利です。

List list8 = list
  .distinctBy((s1, s2) -> s1.startsWith(s2.charAt(0) + "") ? 0 : 1);
assertEquals(list8.size(), 2);

String words = List.of("Boys", "Girls")
  .intersperse("and")
  .reduce((s1, s2) -> s1.concat( " " + s2 ))
  .trim();
assertEquals(words, "Boys and Girls");

リストをカテゴリに分けたいですか?そのためのAPIもあります。

Iterator<List<String>> iterator = list.grouped(2);
assertEquals(iterator.head().size(), 2);

Map<Boolean, List<String>> map = list.groupBy(e -> e.startsWith("J"));
assertEquals(map.size(), 2);
assertEquals(map.get(false).get().size(), 1);
assertEquals(map.get(true).get().size(), 5);

group(int n)は、 List をそれぞれ n__個の要素のグループに分割します。

groupdBy() は、リストを分割するためのロジックを含む Function を受け取り、 true false の2つのエントリを持つ Map を返します。

true キーは Functionで指定された条件を満たす List 要素にマップし、 false キーは False キーを満たさない要素の List__キーにマップします。

予想通り、 List を変更するとき、オリジナルの List は実際には変更されません。代わりに、 List の新しいバージョンが常に返されます。

スタック・セマンティクス、つまり要素の後入れ先出し(LIFO)検索を使用して List と対話することもできます。この点で、 peek() pop() push() など、スタックを操作するためのAPIメソッドがあります。

List<Integer> intList = List.empty();

List<Integer> intList1 = intList.pushAll(List.rangeClosed(5,10));

assertEquals(intList1.peek(), Integer.valueOf(10));

List intList2 = intList1.pop();
assertEquals(intList2.size(), (intList1.size() - 1) );

pushAll() 関数はスタックに整数範囲を挿入するために使用され、 peek() はスタックの先頭を取得するために使用されます。結果を Option オブジェクトにラップできる peekOption() もあります。

List インターフェースには、http://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/collection/List.html[Java docs]にきちんと記載されている、他にも興味深く、そして本当に役に立つメソッドがあります。]。

4.2. キュー

不変の Queue は先入れ先出し(FIFO)検索を可能にする要素を格納します。

Queue は、内部的には2つのリンクリスト、前面の List 、および背面の List で構成されています。フロントの List にはデキューされる要素が含まれ、リアの List にはエンキューされる要素が含まれます。

これにより、O(1)で enqueue および dequeue 操作を実行できます。前面の List が要素を使い果たすと、前面と背面の List’s が入れ替わり、背面の List が反転します。

キューを作成しましょう。

Queue<Integer> queue = Queue.of(1, 2);
Queue<Integer> secondQueue = queue.enqueueAll(List.of(4,5));

assertEquals(3, queue.size());
assertEquals(5, secondQueue.size());

Tuple2<Integer, Queue<Integer>> result = secondQueue.dequeue();
assertEquals(Integer.valueOf(1), result.__1);

Queue<Integer> tailQueue = result.__2;
assertFalse(tailQueue.contains(secondQueue.get(0)));

dequeue 関数は Queue からhead要素を削除し、 Tuple2 <T、Q> を返します。タプルには、最初のエントリとして削除されたhead要素と2番目のエントリとしての Queue の残りの要素が含まれています。

combination(n) を使用して、 Queue 内のすべての可能な N 個の要素の組み合わせを取得できます。

Queue<Queue<Integer>> queue1 = queue.combinations(2);
assertEquals(queue1.get(2).toCharSeq(), CharSeq.of("23"));

繰り返しますが、元の Queue は要素のエンキュー/デキュー中には変更されません。

4.3. ストリーム

Stream は遅延リンクリストの実装であり、 java.util.stream とはまったく異なります。 java.util.stream とは異なり、Vavr Stream はデータを格納し、次の要素を遅延評価します。

整数の Stream があるとしましょう。

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

s.toString() の結果をコンソールに出力すると、 Stream(2、?) だけが表示されます。これは、末尾が評価されていない間に評価されたのは Stream の先頭だけであることを意味します。

s.get(3) を呼び出し、続いて s.tail() の結果を表示すると、 Stream(1、3、4、?) が返されます。反対に、 s.get(3) first を呼び出さないと、 Stream は最後の要素を評価します - s.tail() の結果は Stream(1、?) のみです。これは、末尾の最初の要素だけが評価されたことを意味します。

この振る舞いはパフォーマンスを向上させることができ、(理論的には)無限に長いシーケンスを表すために Stream を使用することを可能にします。

Vavr Stream は不変であり、 Empty または Cons になります。 Cons はhead要素と遅延計算されたtail Stream で構成されています。 List とは異なり、 Stream では、head要素のみがメモリに保持されます。末尾要素は要求に応じて計算されます。

10個の正の整数の Stream を作成し、偶数の合計を計算しましょう。

Stream<Integer> intStream = Stream.iterate(0, i -> i + 1)
  .take(10);

assertEquals(10, intStream.size());

long evenSum = intStream.filter(i -> i % 2 == 0)
  .sum()
  .longValue();

assertEquals(20, evenSum);

Java 8の Stream APIとは対照的に、Vavrの Stream は一連の要素を格納するためのデータ構造です。

そのため、 get() append()、 insert()などの要素を操作するためのメソッドがあります。 drop() distinct()__、および以前に考慮された他のいくつかのメソッドも利用可能です。

最後に、 Stream tabulate() を簡単に説明しましょう。このメソッドは、長さ n Stream を返します。これには、関数を適用した結果である要素が含まれています。

Stream<Integer> s1 = Stream.tabulate(5, (i)-> i + 1);
assertEquals(s1.get(2).intValue(), 3);

zip() を使用して、 Stream of Tuple2 <Integer、Integer> を生成することもできます。これには、2つの Streams を組み合わせて形成される要素が含まれます。

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

Stream<Tuple2<Integer, Integer>> s2 = s.zip(List.of(7,8,9));
Tuple2<Integer, Integer> t1 = s2.get(0);

assertEquals(t1.__1().intValue(), 2);
assertEquals(t1.__2().intValue(), 7);

4.4. アレイ

Array は、効率的なランダムアクセスを可能にする、不変のインデックス付きのシーケンスです。それはオブジェクトのJava配列によって支えられています。基本的に、これは T 型のオブジェクトの配列のための Traversable ラッパーです。

静的メソッド of()を使用して Array をインスタンス化できます。静的な range() および rangeBy() メソッドを使用して範囲要素を生成することもできます。 rangeBy()__には、ステップを定義するための3番目のパラメータがあります。

range() メソッドと rangeBy() メソッドは、開始値から終了値 - 1を引いた要素だけを生成します。終了値を含める必要がある場合は、 rangeClosed() または rangeClosedBy() を使用できます。

Array<Integer> rArray = Array.range(1, 5);
assertFalse(rArray.contains(5));

Array<Integer> rArray2 = Array.rangeClosed(1, 5);
assertTrue(rArray2.contains(5));

Array<Integer> rArray3 = Array.rangeClosedBy(1,6,2);
assertEquals(rArray3.size(), 3);

インデックスで要素を操作しましょう。

Array<Integer> intArray = Array.of(1, 2, 3);
Array<Integer> newArray = intArray.removeAt(1);

assertEquals(3, intArray.size());
assertEquals(2, newArray.size());
assertEquals(3, newArray.get(1).intValue());

Array<Integer> array2 = intArray.replace(1, 5);
assertEquals(array2.get(0).intValue(), 5);

4.5. ベクター

Vector は、 Array List の間の一種で、ランダムアクセスと修正の両方を一定時間で可能にする別のインデックス付き要素シーケンスを提供します。

Vector<Integer> intVector = Vector.range(1, 5);
Vector<Integer> newVector = intVector.replace(2, 6);

assertEquals(4, intVector.size());
assertEquals(4, newVector.size());

assertEquals(2, intVector.get(1).intValue());
assertEquals(6, newVector.get(1).intValue());

4.6. CharSeq

CharSeq は、一連のプリミティブ文字を表現するためのコレクションオブジェクトです。これは本質的にコレクション操作を追加した String ラッパーです。

CharSeq を作成するには:

CharSeq chars = CharSeq.of("vavr");
CharSeq newChars = chars.replace('v', 'V');

assertEquals(4, chars.size());
assertEquals(4, newChars.size());

assertEquals('v', chars.charAt(0));
assertEquals('V', newChars.charAt(0));
assertEquals("Vavr", newChars.mkString());

5 セット

このセクションでは、コレクションライブラリのさまざまな Set 実装について詳しく説明します。 Set データ構造のユニークな特徴は、それが重複値を許さないということです。

ただし、 Set にはさまざまな実装があります - HashSet が基本的な実装です。 TreeSet は重複する要素を許可せず、並べ替えることができます。 LinkedHashSet は、その要素の挿入順序を維持します。

これらの実装を1つずつ詳しく見ていきましょう。

5.1. HashSet

HashSet には、 of() ofAll() 、および range() メソッドのバリエーションのように、新しいインスタンスを作成するための静的ファクトリメソッドがあります。

diff() メソッドを使用すると、2つのセットの違いを知ることができます。

また、 union() メソッドと intersect() メソッドは、2つのセットの和集合と積集合を返します。

HashSet<Integer> set0 = HashSet.rangeClosed(1,5);
HashSet<Integer> set1 = HashSet.rangeClosed(3, 6);

assertEquals(set0.union(set1), HashSet.rangeClosed(1,6));
assertEquals(set0.diff(set1), HashSet.rangeClosed(1,2));
assertEquals(set0.intersect(set1), HashSet.rangeClosed(3,5));

要素の追加や削除などの基本操作も実行できます。

HashSet<String> set = HashSet.of("Red", "Green", "Blue");
HashSet<String> newSet = set.add("Yellow");

assertEquals(3, set.size());
assertEquals(4, newSet.size());
assertTrue(newSet.contains("Yellow"));

HashSet の実装はhttps://en.wikipedia.org/wiki/Hash array mapped trie[Hash array mapped trie(HAMT)]によって支えられています。これは通常の HashTable__と比較して優れたパフォーマンスを誇り、その構造はそれをバックアップするのに適しています。永続的なコレクション。

5.2. TreeSet

不変の TreeSet SortedSet インターフェースの実装です。ソートされた要素の Set を格納し、二分探索木を使用して実装されています。その操作はすべてO(log n)時間で実行されます。

デフォルトでは、 TreeSet の要素は自然な順序でソートされます。

自然なソート順を使用して SortedSet を作成しましょう。

SortedSet<String> set = TreeSet.of("Red", "Green", "Blue");
assertEquals("Blue", set.head());

SortedSet<Integer> intSet = TreeSet.of(1,2,3);
assertEquals(2, intSet.average().get().intValue());

カスタマイズされた方法で要素を並べるには、 TreeSetを作成しながら Comparator__インスタンスを渡します。集合要素から文字列を生成することもできます。

SortedSet<String> reversedSet
  = TreeSet.of(Comparator.reverseOrder(), "Green", "Red", "Blue");
assertEquals("Red", reversedSet.head());

String str = reversedSet.mkString(" and ");
assertEquals("Red and Green and Blue", str);

5.3. BitSet

Vavrコレクションには、不変の BitSet 実装も含まれています。 BitSet インタフェースは SortedSet インタフェースを拡張します。 BitSet は、 BitSet.Builder の静的メソッドを使用してインスタンス化できます。

Set データ構造の他の実装と同様に、 BitSet では、重複エントリをセットに追加することはできません。

それは Traversable インターフェースから操作のためのメソッドを継承します。

標準のJavaライブラリの java.util.BitSet とは異なることに注意してください。 BitSet データには String 値を含めることはできません。

ファクトリメソッド of() を使用して BitSet インスタンスを作成する方法を見てみましょう。

BitSet<Integer> bitSet = BitSet.of(1,2,3,4,5,6,7,8);
BitSet<Integer> bitSet1 = bitSet.takeUntil(i -> i > 4);
assertEquals(bitSet1.size(), 4);

takeUntil() を使用して BitSetの最初の4つの要素を選択します。操作は新しいインスタンスを返しました。 takeUntil() Traversable インターフェースで定義されていることに注意してください。これは、 BitSetの親インターフェースです。

Traversable インタフェースで定義されている、上に示した他のメソッドと操作も BitSet に適用できます。

6. 地図

マップはキー値データ構造です。 Vavrの Map は不変であり、 HashMap TreeMap 、および LinkedHashMap の実装があります。

通常、マップ規約では重複キーを許可しません。ただし、重複キーが別のキーにマップされることがあります。

6.1. HashMap

HashMap は、不変の Map インターフェースの実装です。キーのハッシュコードを使用してキーと値のペアを格納します。

Vavrの Map は、従来の Entry 型の代わりに Tuple2 を使用してキーと値のペアを表します。

Map<Integer, List<Integer>> map = List.rangeClosed(0, 10)
  .groupBy(i -> i % 2);

assertEquals(2, map.size());
assertEquals(6, map.get(0).get().size());
assertEquals(5, map.get(1).get().size());

HashSet と同様に、 HashMap 実装はハッシュ配列マップトライ(HAMT)によって支えられており、ほとんどすべての操作で一定の時間が得られます。

filterKeys() メソッドを使用するか、 filterValues() メソッドを使用して値でマップエントリをフィルタできます。どちらのメソッドも Predicate を引数として受け入れます。

Map<String, String> map1
  = HashMap.of("key1", "val1", "key2", "val2", "key3", "val3");

Map<String, String> fMap
  = map1.filterKeys(k -> k.contains("1") || k.contains("2"));
assertFalse(fMap.containsKey("key3"));

Map<String, String> fMap2
  = map1.filterValues(v -> v.contains("3"));
assertEquals(fMap2.size(), 1);
assertTrue(fMap2.containsValue("val3"));

map() メソッドを使用してマップエントリを変換することもできます。たとえば、 map1 Map <String、Integer> に変換しましょう。

Map<String, Integer> map2 = map1.map(
  (k, v) -> Tuple.of(k, Integer.valueOf(v.charAt(v.length() - 1) + "")));
assertEquals(map2.get("key1").get().intValue(), 1);

6.2. TreeMap

不変の TreeMap は、 SortedMap インターフェースの実装です。 TreeSet と同様に、 Comparator インスタンスは TreeMap の要素をカスタムソートするために使用されます。

SortedMap の作成方法を説明しましょう。

SortedMap<Integer, String> map
  = TreeMap.of(3, "Three", 2, "Two", 4, "Four", 1, "One");

assertEquals(1, map.keySet().toJavaArray()[0]);
assertEquals("Four", map.get(4).get());

デフォルトでは、 TreeMap のエントリはキーの自然な順序でソートされます。しかし、ソートに使用される Comparator を指定することができます。

TreeMap<Integer, String> treeMap2 =
  TreeMap.of(Comparator.reverseOrder(), 3,"three", 6, "six", 1, "one");
assertEquals(treeMap2.keySet().mkString(), "631");

TreeSet と同様に、 TreeMap 実装もツリーを使用してモデル化されているため、その操作はO(log n)時間です。 map.get(key) は、マップ内の指定されたキーの値をラップする Option を返します。

** 7. Javaとの相互運用性

コレクションAPIは、Javaのコレクションフレームワークと完全に相互運用可能です。実際にこれがどのように行われるかを見てみましょう。

7.1. JavaからVavrへの変換

Vavrの各コレクション実装は、 java.util.Iterable を取る静的ファクトリメソッド ofAll() を持っています。これにより、JavaコレクションからVavrコレクションを作成できます。同様に、別のファクトリメソッド ofAll() もJavaの Stream を直接取ります。

Javaの List を不変の List に変換するには、次のようにします。

java.util.List<Integer> javaList = java.util.Arrays.asList(1, 2, 3, 4);
List<Integer> vavrList = List.ofAll(javaList);

java.util.stream.Stream<Integer> javaStream = javaList.stream();
Set<Integer> vavrSet = HashSet.ofAll(javaStream);

もう1つの便利な関数は collector() です。これは Stream.collect() と共に使用してVavrコレクションを取得できます。

List<Integer> vavrList = IntStream.range(1, 10)
  .boxed()
  .filter(i -> i % 2 == 0)
  .collect(List.collector());

assertEquals(4, vavrList.size());
assertEquals(2, vavrList.head().intValue());

7.2. VavrからJavaへの変換

Value インターフェースには、Vavr型をJava型に変換するためのメソッドが多数あります。これらのメソッドは toJavaXXX() という形式です。

いくつか例を挙げましょう。

Integer[]array = List.of(1, 2, 3)
  .toJavaArray(Integer.class);
assertEquals(3, array.length);

java.util.Map<String, Integer> map = List.of("1", "2", "3")
  .toJavaMap(i -> Tuple.of(i, Integer.valueOf(i)));
assertEquals(2, map.get("2").intValue());

Java 8 Collectors を使ってVavrコレクションから要素を集めることもできます。

java.util.Set<Integer> javaSet = List.of(1, 2, 3)
  .collect(Collectors.toSet());

assertEquals(3, javaSet.size());
assertEquals(1, javaSet.toArray()[0]);

7.3. Javaコレクションビュー

あるいは、このライブラリーは、Javaコレクションへの変換時により優れたパフォーマンスを発揮する、いわゆるコレクションビューを提供します。前のセクションの変換メソッドは、すべての要素を反復処理してJavaコレクションを構築します。

一方、ビューは標準のJavaインタフェースを実装し、メソッド呼び出しを基になるVavrコレクションに委任します。

これを書いている時点では、 List ビューだけがサポートされています。各シーケンシャルコレクションには2つのメソッドがあります。1つは不変ビューを作成する方法で、もう1つは可変ビュー用です。

不変ビューでミューテーターメソッドを呼び出すと、 UnsupportedOperationException が発生します。

例を見てみましょう。

@Test(expected = UnsupportedOperationException.class)
public void givenVavrList__whenViewConverted__thenException() {
    java.util.List<Integer> javaList = List.of(1, 2, 3)
      .asJava();

    assertEquals(3, javaList.get(2).intValue());
    javaList.add(4);
}

不変ビューを作成するには

java.util.List<Integer> javaList = List.of(1, 2, 3)
  .asJavaMutable();
javaList.add(4);

assertEquals(4, javaList.get(3).intValue());

8結論

このチュートリアルでは、VavrのCollection APIによって提供されるさまざまな機能データ構造について学びました。 Vavrのコレクションhttp://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/collection/package-frame.html[JavaDoc]には、もっと便利で生産的なAPIメソッドがあります。 ユーザーガイド

最後に、ライブラリは Value インタフェースを拡張し、その結果としてJavaの Iterable インタフェースを実装する Try Option Either 、および Future も定義していることに注意することが重要です。これは、いくつかの状況でそれらがコレクションとして振舞うことができることを意味します。

この記事のすべての例の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/vavr/src/test/java/com/baeldung/vavr/collections[over on Github]。