1. Обзор
В этом руководстве мы проиллюстрируем одну из многих полезных функций пакета сбора Guava: как применить функцию к набору Guava
и получить
карту
.
Мы обсудим два подхода — создание неизменяемой карты и динамической карты на основе встроенных операций гуавы, а затем реализацию пользовательской реализации живой карты
.
2. Настройка
Во-первых, мы добавим библиотеку Guava в качестве зависимости в pom.xml:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
Небольшое примечание — вы можете проверить, есть ли более новая версия, здесь .
3. Функция отображения
Давайте сначала определим функцию, которую мы будем применять к элементам наборов:
Function<Integer, String> function = new Function<Integer, String>() {
@Override
public String apply(Integer from) {
return Integer.toBinaryString(from.intValue());
}
};
Функция просто преобразует значение Integer
в его двоичное строковое
представление.
4. Гуава toMap ()
Guava предлагает статический служебный класс, относящийся к экземплярам 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. Гуава как карта ()
Если мы воспользуемся предыдущим примером и создадим карту с помощью метода 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. Создание пользовательской живой карты
Когда мы говорим о представлении карты набора
,
мы в основном расширяем возможности набора
с помощью функции
Guava . ``
В представлении « Карта
в реальном времени » изменения в наборе
должны обновлять набор записей
карты
в режиме реального времени. Мы создадим нашу собственную универсальную карту
с подклассом 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. Записи
Другим атрибутом в нашей карте
будут записи
, представляющие наш EntrySet:
private Set<Entry<K, V>> entries;
Поле записей
всегда будет инициализировано с помощью входного набора
из конструктора:
public GuavaMapFromSet(Set<K> keys,Function<? super K, ? extends V> function) {
this.entries=keys;
}
Небольшое примечание: чтобы сохранить представление в реальном времени, мы будем использовать тот же итератор
во входном наборе
для последующего набора элементов Map
.
При выполнении контракта AbstractMap<K,V
>
мы реализуем метод entrySet
, в котором затем возвращаем записи
:
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
return this.entries;
}
5.2. Кэш
Эта карта
хранит значения, полученные путем применения функции
к набору:
private WeakHashMap<K, V> cache;
6. Итератор множества
Мы будем использовать итератор входного
набора
для последующего набора записей
карты
. Для этого мы используем настраиваемый EntrySet
, а также настраиваемый класс Entry .
``
6.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();
}
}
Понятно, что в этом коде мы не разрешаем изменять набор
из представления карты ,
поскольку
вызов setValue
вызывает исключение UnsupportedOperationException.
Обратите особое внимание на getValue
— это суть нашей функциональности просмотра в реальном времени . Мы проверяем кеш
внутри нашей карты
на наличие текущего ключа
( элемент 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();
}
}
Мы выполнили контракт на расширение AbstractSet
, переопределив методы итератора
и размера .
Но есть еще кое-что.
Помните, что экземпляр этого EntrySet
будет формировать записи
в нашем представлении карты
. Обычно EntrySet
карты просто возвращает полную запись
для каждой итерации.
Однако в нашем случае нам нужно использовать итератор
из входного набора
, чтобы сохранить наше живое представление .
Мы хорошо знаем, что он будет возвращать только элементы Set , поэтому нам также нужен собственный
итератор
.
6.3. Итератор
_ ``
Вот реализация нашего итератора
для указанного выше EntrySet
:
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
при инициализации. ``
При переборе записей GuavaMapFromSet
с использованием итератора
вызов next
просто извлекает ключ из итератора Set
и
создает 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"));
}
Изменив содержимое input Set
, мы увидим, что Карта
обновляется в режиме реального времени:
@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, поэтому его легко импортировать и запускать как есть.