グアバセット+関数=マップ
1. 概要
このチュートリアルでは、Guavaのcollectパッケージの多くの便利な機能の1つを説明します。Guavaセットに関数を適用してマップを取得する方法 。
組み込みのguava操作に基づいて不変マップとライブマップを作成し、次にカスタムライブMap実装を実装するという2つのアプローチについて説明します。
2. 設定
まず、 Guavaライブラリをpom.xml:の依存関係として追加します。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
簡単なメモ–
3. マッピング機能
まず、sets要素に適用する関数を定義しましょう。
Function<Integer, String> function = new Function<Integer, String>() {
@Override
public String apply(Integer from) {
return Integer.toBinaryString(from.intValue());
}
};
この関数は、Integerの値をそのバイナリString表現に変換するだけです。
4. グアバtoMap()
Guavaは、Mapインスタンスに関連する静的ユーティリティクラスを提供します。 特に、定義されたGuavaの関数を適用することにより、SetをMapに変換するために使用できる2つの操作があります。
次のスニペットは、不変のマップの作成を示しています。
Map<Integer, String> immutableMap = Maps.toMap(set, function);
次のテストは、セットが適切に変換されていることを示しています。
@Test
public void givenStringSetAndSimpleMap_whenMapsToElementLength_thenCorrect() {
Set set = new TreeSet(Arrays.asList(32, 64, 128));
Map<Integer, String> 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<Integer, String> liveMap = Maps.asMap(set, function);
ライブマップビューが表示されます。これは、元のセットへの変更がマップにも反映されることを意味します。
@Test
public void givenStringSet_whenMapsToElementLength_thenCorrect() {
Set<Integer> set = new TreeSet<Integer>(Arrays.asList(32, 64, 128));
Map<Integer, String> 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. カスタムライブの構築マップ
マップセットのビューについて話すとき、基本的にグアバを使用してセットの機能を拡張しています。機能。
ライブマップビューで、セットを変更すると、マップ EntrySetがリアルタイムで更新されます。 独自のジェネリックを作成します地図 、サブクラス化 AbstractMap
public class GuavaMapFromSet<K, V> extends AbstractMap<K, V> {
public GuavaMapFromSet(Set<K> keys,
Function<? super K, ? extends V> function) {
}
}
注目に値するのは、 AbstractMap のすべてのサブクラスの主な契約は、これまでに行ったように、entrySetメソッドを実装することです。 次に、次のサブセクションでコードの2つの重要な部分を見ていきます。
5.1. エントリー
マップの別の属性はentriesで、 EntrySet:を表します。
private Set<Entry<K, V>> entries;
entries フィールドは、コンストラクターからの入力 Set を使用して常に初期化されます:
public GuavaMapFromSet(Set<K> keys,Function<? super K, ? extends V> function) {
this.entries=keys;
}
ここで簡単に説明します。ライブビューを維持するために、後続のマップのの入力セットで同じイテレータを使用します。 EntrySet。
の契約を履行する際に AbstractMap
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
return this.entries;
}
5.2. キャッシュ
このMapは、関数をSetに適用することによって取得された値を格納します:
private WeakHashMap<K, V> cache;
6. セットイテレータ
入力Setのiteratorを、後続のMapのEntrySetに使用します。 これを行うには、カスタマイズされたEntrySetとカスタマイズされたEntryクラスを使用します。
6.1. エントリクラス
まず、Mapの1つのエントリがどのようになるかを見てみましょう。
private class SingleEntry implements Entry<K, V> {
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 ViewからSetを変更することはできません。を呼び出すと、setValueがUnsupportedOperationException。
getValue に細心の注意を払ってください–これがライブビュー機能の核心です。 Map内のcacheで、現在の key ( Set 要素)を確認します。
キーが見つかった場合はそれを返します。それ以外の場合は、現在のキーに関数を適用して値を取得し、キャッシュに保存します。
このように、 Set に新しい要素がある場合は常に、新しい値がその場で計算されるため、マップは最新になります。
6.2. EntrySet
ここで、EntrySetを実装します。
private class MyEntrySet extends AbstractSet<Entry<K, V>> {
private Set<K> keys;
public MyEntrySet(Set<K> keys) {
this.keys = keys;
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new LiveViewIterator();
}
@Override
public int size() {
return this.keys.size();
}
}
iteratorおよびsizeメソッドをオーバーライドすることにより、AbstractSetを拡張する契約を履行しました。 しかし、それだけではありません。
このEntrySetのインスタンスは、マップビューのエントリを形成することを忘れないでください。 通常、マップの EntrySet は、反復ごとに完全なEntryを返すだけです。
ただし、この場合、を使用する必要がありますイテレータ入力から設定ライブビューを維持するため
6.3. イテレータ
上記のEntrySetに対するiteratorの実装は次のとおりです。
public class LiveViewIterator implements Iterator<Entry<K, V>> {
private Iterator<K> inner;
public LiveViewIterator () {
this.inner = MyEntrySet.this.keys.iterator();
}
@Override
public boolean hasNext() {
return this.inner.hasNext();
}
@Override
public Map.Entry<K, V> 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<Integer> set = new TreeSet<>(Arrays.asList(32, 64, 128));
Map<Integer, String> customMap = new GuavaMapFromSet<Integer, String>(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<Integer> set = new TreeSet<Integer>(Arrays.asList(32, 64, 128));
Map<Integer, String> 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 操作を活用し、関数を適用してセットからマップビューを取得するさまざまな方法について説明しました。
これらすべての例とコードスニペットの完全な実装は、私のGuava githubプロジェクトにあります。これはEclipseベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。