1. 概要

このチュートリアルでは、Guavacollectパッケージの多くの便利な機能の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>

簡単なメモ–新しいバージョンがここ[X64X]にあるかどうかを確認できます。

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の関数を適用することにより、SetMapに変換するために使用できる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 >> 、実装します entrySet 次に戻るメソッドエントリ

@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
    return this.entries;
}

5.2. キャッシュ

このMapは、関数をSetに適用することによって取得された値を格納します:

private WeakHashMap<K, V> cache;

6. セットイテレータ

入力Setiteratorを、後続のMapEntrySetに使用します。 これを行うには、カスタマイズされた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を変更することはできません。を呼び出すと、setValueUnsupportedOperationException。

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

LiveViewIteratorMyEntrySetクラス内に存在する必要があります。このようにして、初期化時にSetiteratorを共有できます。

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ベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。