Функция набора гуавы = карта

Набор гуавы + функция = карта

1. обзор

В этом руководстве мы проиллюстрируем одну из многих полезных функций в пакетеGuava‘scollect:how to apply a Function to a Guava Set and obtain a Map.

Мы обсудим два подхода - создание неизменяемой карты и живой карты на основе встроенных операций с гуавой, а затем реализацию пользовательской реализации liveMap.

2. Настроить

Сначала мы добавим библиотекуGuava в качестве зависимости вpom.xml:


    com.google.guava
    guava
    19.0

Небольшое примечание - вы можете проверить, есть лиnewer version here.

3. ОтображениеFunction

Давайте сначала определим функцию, которую мы применим к элементам множеств:

    Function function = new Function() {
        @Override
        public String apply(Integer from) {
            return Integer.toBinaryString(from.intValue());
        }
    };

Функция просто преобразует значениеInteger в его двоичное представлениеString.

4. ГуаваtoMap()

Guava предлагает статический служебный класс, относящийся к экземплярамMap. Среди прочего, у него есть две операции, которые можно использовать для преобразованияSet вMap путем применения определенного Guava'sFunction.

В следующем фрагменте показано создание неизменяемого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 - это означает, что изменения в исходном Set также будут отражены на карте:

@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. Создание пользовательского LiveMap

Когда мы говорим о представленииMap дляSet, мы в основном расширяем возможностиSet, используяGuavaFunction.

В режиме реального времениMap изменения вSet должны обновлятьMapEntrySet в реальном времени. Мы создадим наш собственный общийMap, подклассAbstractMap<K,V>, например:

public class GuavaMapFromSet extends AbstractMap {
    public GuavaMapFromSet(Set keys,
        Function function) {
    }
}

Стоит отметить, что основной контракт всех подклассовAbstractMap заключается в реализации методаentrySet, как мы это сделали. Затем мы рассмотрим 2 критические части кода в следующих подразделах.

5.1. Записи

Другой атрибут в нашемMap будетentries, представляющий нашEntrySet:

private Set> entries;

Полеentries всегда будет инициализировано с использованием вводаSet изconstructor:

public GuavaMapFromSet(Set keys,Function function) {
    this.entries=keys;
}

Небольшое примечание: чтобы поддерживать просмотр в реальном времени, мы будем использовать тот жеiterator во входных данныхSet для последующихMap ’sEntrySet.

Выполняя контракт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‘siterator для последующихMap ’sEntrySet. Для этого мы используем индивидуальный классEntrySet, а также индивидуальный классEntry.

6.1. КлассEntry

Во-первых, давайте посмотрим, как будет выглядеть одна запись вMap:

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

Очевидно, что в этом коде мы не разрешаем изменятьSet изMap Viewas, вызовsetValue вызываетUnsupportedOperationException.

Обратите особое внимание наgetValue - это суть нашей функциональностиlive view. Мы проверяемcache внутри нашегоMap для текущего элементаkey (Set).

Если мы находим ключ, мы возвращаем его, иначе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();
    }
}

Мы выполнили контракт на расширениеAbstractSet, переопределив методыiterator иsize. Но это еще не все.

Помните, что экземпляр этогоEntrySet сформируетentries в нашем представленииMap. ОбычноEntrySet карты просто возвращает полныйEntry для каждой итерации.

Однако в нашем случае нам нужно использоватьiterator из вводаSet для поддержания нашего просмотра в реальном времени. Мы хорошо знаем, что он вернет только элементыSet , поэтому нам также нужен собственныйiterator.

6.3. Iterator

Вот реализация нашегоiterator для указанного вышеEntrySet:

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‘siterator при инициализации.

При просмотре записейGuavaMapFromSet с использованиемiterator вызовnext просто извлекает ключ изiteratorSet и создает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"));
}

Изменяя содержание inputSet, мы увидим, что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, поэтому его должно быть легко импортировать и запускать как есть.