Набор гуавы + функция = карта
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 super K, ? extends V> function) {
}
}
Стоит отметить, что основной контракт всех подклассовAbstractMap заключается в реализации методаentrySet, как мы это сделали. Затем мы рассмотрим 2 критические части кода в следующих подразделах.
5.1. Записи
Другой атрибут в нашемMap будетentries, представляющий нашEntrySet:
private Set> entries;
Полеentries всегда будет инициализировано с использованием вводаSet изconstructor:
public GuavaMapFromSet(Set keys,Function super K, ? extends V> 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, поэтому его должно быть легко импортировать и запускать как есть.