グアバセット+機能=マップ
1. 概要
このチュートリアルでは、Guavaのcollectパッケージに含まれる多くの便利な機能の1つであるhow to apply a Function to a Guava Set and obtain a Mapについて説明します。
組み込みのグアバ操作に基づいて不変マップとライブマップを作成し、次にカスタムライブMap実装を実装するという2つのアプローチについて説明します。
2. セットアップ
まず、pom.xml:の依存関係としてGuavaライブラリを追加します
com.google.guava
guava
19.0
簡単なメモ–newer version hereがあるかどうかを確認できます。
3. マッピングFunction
まず、sets要素に適用する関数を定義しましょう。
Function function = new Function() {
@Override
public String apply(Integer from) {
return Integer.toBinaryString(from.intValue());
}
};
この関数は、Integerの値をそのバイナリString表現に変換するだけです。
4. グアバtoMap()
Guavaは、Mapインスタンスに関連する静的ユーティリティクラスを提供します。 特に、定義されたGuavaのFunctionを適用することにより、SetをMapに変換するために使用できる2つの操作があります。
次のスニペットは、不変のMapの作成を示しています。
Map immutableMap = Maps.toMap(set, function);
次のテストは、セットが適切に変換されたことを表明します。
@Test
public void givenStringSetAndSimpleMap_whenMapsToElementLength_thenCorrect() {
Set set = new TreeSet(Arrays.asList(32, 64, 128));
Map immutableMap = Maps.toMap(set, function);
assertTrue(immutableMap.get(32).equals("100000")
&& immutableMap.get(64).equals("1000000")
&& immutableMap.get(128).equals("10000000"));
}
作成されたマップの問題は、ソースセットに要素が追加された場合、派生マップが更新されないことです。
4. グアバasMap()
前の例を使用し、Maps.asMapメソッドを使用してマップを作成する場合:
Map liveMap = Maps.asMap(set, function);
a live map viewを取得します。これは、元のセットへの変更がマップにも反映されることを意味します。
@Test
public void givenStringSet_whenMapsToElementLength_thenCorrect() {
Set set = new TreeSet(Arrays.asList(32, 64, 128));
Map liveMap = Maps.asMap(set, function);
assertTrue(liveMap.get(32).equals("100000")
&& liveMap.get(64).equals("1000000")
&& liveMap.get(128).equals("10000000"));
set.add(256);
assertTrue(liveMap.get(256).equals("100000000") && liveMap.size() == 4);
}
セットを介して要素を追加し、マップ内で検索したにもかかわらず、テストは適切にアサートされることに注意してください。
5. カスタムライブMapの構築
SetのMapビューについて話すとき、基本的にはGuavaFunctionを使用してSetの機能を拡張しています。
ライブMapビューでは、Setを変更すると、MapEntrySetがリアルタイムで更新されます。 次のように、AbstractMap<K,V>をサブクラス化して、独自の汎用Mapを作成します。
public class GuavaMapFromSet extends AbstractMap {
public GuavaMapFromSet(Set keys,
Function function) {
}
}
注目に値するのは、AbstractMapのすべてのサブクラスの主な契約は、これまでと同様に、entrySetメソッドを実装することです。 次に、以下のサブセクションでコードの2つの重要な部分を見ていきます。
5.1. エントリー
Mapの別の属性はentriesで、EntrySet:を表します。
private Set> entries;
entriesフィールドは、常にconstructor:からの入力Setを使用して初期化されます。
public GuavaMapFromSet(Set keys,Function function) {
this.entries=keys;
}
ここでの簡単な注意–ライブビューを維持するために、後続のMapのEntrySet.の入力Setで同じiteratorを使用します
AbstractMap<K,V>のコントラクトを実行する際に、entrySetメソッドを実装します。このメソッドでは、entriesを返します。
@Override
public Set> entrySet() {
return this.entries;
}
5.2. キャッシュ
このMapは、applying Function to Set:によって取得された値を格納します
private WeakHashMap cache;
6. Set Iterator
入力Setのiteratorを後続のMapのEntrySetに使用します。 これを行うには、カスタマイズされたEntrySetクラスとカスタマイズされたEntryクラスを使用します。
6.1. Entryクラス
まず、Mapの1つのエントリがどのようになるかを見てみましょう。
private class SingleEntry implements Entry {
private K key;
public SingleEntry( K key) {
this.key = key;
}
@Override
public K getKey() {
return this.key;
}
@Override
public V getValue() {
V value = GuavaMapFromSet.this.cache.get(this.key);
if (value == null) {
value = GuavaMapFromSet.this.function.apply(this.key);
GuavaMapFromSet.this.cache.put(this.key, value);
}
return value;
}
@Override
public V setValue( V value) {
throw new UnsupportedOperationException();
}
}
明らかに、このコードでは、MapビューasからSetを変更することは許可されていません。setValueを呼び出すとUnsupportedOperationException.がスローされます。
getValueに細心の注意を払ってください–これがlive view機能の核心です。 現在のkey(Set要素)のMap内のcacheをチェックします。
キーが見つかった場合はそれを返し、そうでない場合はwe apply our function to the current key and obtain a valueを返し、cacheに格納します。
このように、Setに新しい要素がある場合は常に、新しい値がオンザフライで計算されるため、マップは最新です。
6.2. EntrySet
ここで、EntrySetを実装します。
private class MyEntrySet extends AbstractSet> {
private Set keys;
public MyEntrySet(Set keys) {
this.keys = keys;
}
@Override
public Iterator> iterator() {
return new LiveViewIterator();
}
@Override
public int size() {
return this.keys.size();
}
}
iteratorメソッドとsizeメソッドをオーバーライドすることにより、AbstractSetを拡張する契約を履行しました。 しかし、もっとあります。
このEntrySetのインスタンスは、Mapビューでentriesを形成することを忘れないでください。 通常、マップのEntrySetは、反復ごとに完全なEntryを返すだけです。
ただし、この場合、入力Setからのiteratorを使用して、ライブビュー.を維持する必要があります。Setの要素のみが返されることはよくわかっています。 、したがって、カスタムiteratorも必要です。
6.3. Iterator
上記のEntrySetに対するiteratorの実装は次のとおりです。
public class LiveViewIterator implements Iterator> {
private Iterator inner;
public LiveViewIterator () {
this.inner = MyEntrySet.this.keys.iterator();
}
@Override
public boolean hasNext() {
return this.inner.hasNext();
}
@Override
public Map.Entry next() {
K key = this.inner.next();
return new SingleEntry(key);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
LiveViewIteratorはMyEntrySetクラス内に存在する必要があります。これにより、初期化時にSetのiteratorを共有できます。
iteratorを使用してGuavaMapFromSetのエントリをループする場合、nextを呼び出すと、Setのiteratorからキーが取得され、SingleEntry。
7. すべてを一緒に入れて
このチュートリアルで説明した内容をつなぎ合わせた後、前のサンプルのliveMap変数を置き換えて、カスタムマップに置き換えましょう。
@Test
public void givenIntSet_whenMapsToElementBinaryValue_thenCorrect() {
Set set = new TreeSet<>(Arrays.asList(32, 64, 128));
Map customMap = new GuavaMapFromSet(set, function);
assertTrue(customMap.get(32).equals("100000")
&& customMap.get(64).equals("1000000")
&& customMap.get(128).equals("10000000"));
}
入力Setの内容を変更すると、Mapがリアルタイムで更新されることがわかります。
@Test
public void givenStringSet_whenMapsToElementLength_thenCorrect() {
Set set = new TreeSet(Arrays.asList(32, 64, 128));
Map customMap = Maps.asMap(set, function);
assertTrue(customMap.get(32).equals("100000")
&& customMap.get(64).equals("1000000")
&& customMap.get(128).equals("10000000"));
set.add(256);
assertTrue(customMap.get(256).equals("100000000") && customMap.size() == 4);
}
8. 結論
このチュートリアルでは、Guava操作とobtain a Map view from a Set by applying a Functionを活用するさまざまな方法について説明しました。
これらすべての例とコードスニペットcan be found in my Guava github projectの完全な実装–これはEclipseベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。