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]。