1概要

このチュートリアルでは –

Guava



collect

パッケージの便利な機能の1つを説明します。

関数をGuava

Set

に適用して

Map


を取得する方法。

ここでは、2つのアプローチについて説明します。組み込みのグアバ操作に基づいて不変マップとライブマップを作成してから、カスタムライブ

Map

実装を実装する方法です。


2セットアップ

まず、__pom.xmlの依存関係として

Guava

ライブラリを追加します。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>19.0</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の

Function

を適用することによって

Set



Map

に変換するために使用できる2つの操作があります。

次のスニペットは、不変の

Map

を作成する方法を示しています。

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

  • ライブマップビュー** が表示されます。つまり、元のSetへの変更がマップにも反映されます。

@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カスタムライブの作成

Map



Set



Map

Viewについて話すとき、私たちは基本的に

Guava


Function

を使って

Set

の機能を拡張しています。

ライブ

Map

ビューでは、

Set

への変更はリアルタイムで

Map

EntrySet

を更新するはずです。次のように、独自の汎用

Map

、サブクラス化

AbstractMap <K、V> __を作成します。

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. エントリー


Map

のもう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;
}

ここでのちょっとしたメモ – ライブビューを維持するために、後続の

Map

s

EntrySetの入力

Set

に同じ

iterator__を使用します。


AbstractMap <K、V>

の規約を満たすために、

entrySet

メソッドを実装し、その中で

entries

を返します。

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


5.2. キャッシュ

この

Map

は、

Setに

Function__を適用した値を格納します。

private WeakHashMap<K, V> cache;


6.

イテレータの設定


後続の

Map

‘s

EntrySet

には、入力

Set

‘s s

iterator

を使用します。これを行うには、カスタマイズされた

EntrySet

とカスタマイズされた

Entry

クラスを使います。


6.1.

Entry

クラス

まず、

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

を変更することは許可されていません。


getValue

に細心の注意を払ってください – これが私たちの

ライブビュー

機能の核心です。現在の

key



Set

要素)について、

Map

内の

cache

をチェックします。

キーが見つかったらそれを返し、そうでなければ

現在のキーに関数を適用して値を取得

してから

cache

に格納します。

このように、

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

のインスタンスが、

Map

Viewの

entries

を形成することを忘れないでください。通常、マップの

EntrySet

は、繰り返しごとに完全な

Entry

を返すだけです。

ただし、この場合は、ライブビューを維持するために入力

Set

から

iterator

を使用する必要があります。

Set

の要素のみが返されることがわかっているので、カスタム

iterator

も必要です。


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

s

iterator

を共有できます。


iterator

を使用して

GuavaMapFromSetのエントリをループする場合、

next

を呼び出すと

Set

sの

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

操作を利用し、

Function

** を適用して

Set

から

Map

ビューを取得するさまざまな方法を調べました。

これらすべての例とコードスニペットの完全な実装

はhttps://github.com/eugenp/tutorials/tree/master/guava-collections[my Guava github project]

にあります。これはEclipseベースのプロジェクトなので、インポートしてそのまま実行するのは簡単なはずです。