1. 概要

以前はJavaslangとして知られていたVavrライブラリは、Java用の機能ライブラリです。 この記事では、その強力なコレクションAPIについて説明します。

このライブラリの詳細については、この記事をお読みください。

2. 永続的なコレクション

永続的なコレクションを変更すると、現在のバージョンを保持しながら、コレクションの新しいバージョンが生成されます。

同じコレクションの複数のバージョンを維持すると、CPUとメモリの使用効率が低下する可能性があります。 ただし、Vavrコレクションライブラリは、コレクションの異なるバージョン間でデータ構造を共有することにより、これを克服します。

これは、基になるコレクションのラッパーを提供するだけの CollectionsユーティリティクラスのJavaのunmodizableCollection()とは根本的に異なります。

このようなコレクションを変更しようとすると、新しいバージョンが作成されるのではなく、UnsupportedOperationExceptionが発生します。 さらに、基になるコレクションは、直接参照することで変更可能です。

3. トラバーサブル

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

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

コレクションライブラリをさらに詳しく見ていきましょう。

4. Seq

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

Seq インターフェースは、順次データ構造を表します。 これは、 List Stream Queue Array Vector 、およびの親インターフェイスです。 CharSeq。 これらのデータ構造にはすべて、以下で説明する独自のプロパティがあります。

4.1. リスト

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

永続的なリストは、ヘッドとテールから再帰的に形成されます。

  • 頭–最初の要素
  • テール–残りの要素を含むリスト(このリストもヘッドとテールから形成されます)

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

静的empty()を使用して空のリスト ofAll()を作成し、からリストを作成することもできます。 ] Iterable タイプ:

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

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

drop()とそのバリアントを使用して、最初のN要素を削除できます。

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()は、述語がtrueになるまでリストから要素を取得し続けます。 述語引数を取るtakeWhile()バリアントもあります。

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

非常に興味深いことに、リストのすべての要素の間に要素を挿入する 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との2つのエントリを持つMapを返します。 X162X]false。

trueキーは関数で指定された条件を満たす要素のリストにマップされます;falseキーはにマップされます]リストしない要素のリスト。

予想どおり、リストを変更する場合、元のリストは実際には変更されません。 代わりに、リストの新しいバージョンが常に返されます。

スタックセマンティクス(後入れ先出し(LIFO)による要素の取得)を使用して、リストと対話することもできます。 この点で、 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 インターフェースには、Javaドキュメントにきちんと文書化されている他の興味深くて本当に便利なメソッドがあります。

4.2. キュー

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

Queue は、内部的に2つのリンクリストで構成されています。フロントリストとリアリストです。 前面のリストにはデキューされた要素が含まれ、背面のリストにはキューに入れられた要素が含まれます。

これにより、enqueueおよびdequeue操作をO(1)で実行できます。 フロントリストの要素がなくなると、フロントとリアリストが入れ替わり、リアリストが逆になります。

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

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)));

The デキュー関数は、ヘッド要素をから削除しますとを返しますタプル2 。 タプルには、最初のエントリとして削除されたhead要素と、2番目のエントリとしてキューの残りの要素が含まれています。

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 は最後の要素(の結果)を評価します。 tail() Stream(1、?)のみになります。 これは、テールの最初の要素だけが評価されたことを意味します。

この動作により、パフォーマンスが向上し、 Stream を使用して、(理論的には)無限に長いシーケンスを表すことができます。

Vavr Stream は不変であり、EmptyまたはConsの場合があります。 Cons は、ヘッド要素とレイジー計算されたテール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()、および以前に検討した他のいくつかのメソッドも使用できます。

最後に、 Streamtabulate()を簡単にデモンストレーションしましょう。 このメソッドは、長さnStreamを返します。これには、関数を適用した結果の要素が含まれています。

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

使用することもできますジップ() を生成するにはストリームタプル2 、2つを組み合わせることによって形成される要素が含まれていますストリーム

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 arrayによって支えられています。 基本的に、これはタイプ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 は、ArrayListの中間の一種であり、ランダムアクセスと一定時間の変更の両方を可能にする別のインデックス付き要素シーケンスを提供します。

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 の実装は、 Hashアレイマップトライ(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。 セット要素から文字列を生成することもできます。

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. ビットセット

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()は、BitSet。の親インターフェイスであるTraversableインターフェイスで定義されていることに注意してください。

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

6. マップ

マップはKey-Valueデータ構造です。 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> 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は、SortedMapインターフェースの実装です。 TreeSet と同様に、コンパレータインスタンスは、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 のエントリは、キーの自然な順序で並べ替えられます。 ただし、並べ替えに使用するコンパレータを指定することはできます。

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のコレクションAPIによって提供されるさまざまな機能データ構造について学習しました。 VavrのコレクションJavaDocおよびユーザーガイドにあるより便利で生産的なAPIメソッドがあります。

最後に、ライブラリは[X46X]Valueを拡張するTry Option Either 、およびFutureも定義していることに注意してください。 インターフェース、および結果としてJavaのIterableインターフェースを実装します。 これは、状況によってはコレクションとして動作できることを意味します。

この記事のすべての例の完全なソースコードは、Githubにあります。