Conjunto de Goiaba + Função = Mapa
1. Visão geral
Neste tutorial - ilustraremos um dos muitos recursos úteis do pacoteGuava'scollect:how to apply a Function to a Guava Set and obtain a Map.
Discutiremos duas abordagens - criar um mapa imutável e um mapa ao vivo com base nas operações de goiaba integradas e, em seguida, uma implementação de uma implementaçãoMap personalizada ao vivo
2. Configuração
Primeiro, vamos adicionar a bibliotecaGuava como uma dependência empom.xml:
com.google.guava
guava
19.0
Uma nota rápida - você pode verificar se há umnewer version here.
3. O mapeamentoFunction
Vamos primeiro definir a função que aplicaremos aos elementos sets:
Function function = new Function() {
@Override
public String apply(Integer from) {
return Integer.toBinaryString(from.intValue());
}
};
A função está simplesmente convertendo o valor de umInteger em sua representação bináriaString.
4. GoiabatoMap()
O Guava oferece uma classe de utilitário estático pertencente às instâncias deMap. Entre outras, ele tem duas operações que podem ser usadas para converter aSet emMap aplicandoFunction de Goiaba definido.
O seguinte snippet mostra a criação de umMap imutável:
Map immutableMap = Maps.toMap(set, function);
Os testes a seguir afirmam que o conjunto foi convertido corretamente:
@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"));
}
O problema com o mapa criado é que, se um elemento for adicionado ao conjunto de origem, o mapa derivado não será atualizado.
4. GoiabaasMap()
Se usarmos o exemplo anterior e criarmos um mapa usando o métodoMaps.asMap:
Map liveMap = Maps.asMap(set, function);
Obteremosa live map view - o que significa que as alterações no conjunto de origem serão refletidas no mapa também:
@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);
}
Observe que os testes afirmam corretamente, apesar de termos adicionado um elemento através de um conjunto e o procurado dentro do mapa.
5. Construindo LiveMap personalizado
Quando falamos da visualizaçãoMap deSet, basicamente estamos estendendo a capacidade deSet usandoGuavaFunction.
Na exibição ao vivoMap, as alterações emSet devem atualizarMapEntrySet em tempo real. Criaremos nosso próprioMap genérico, subclassificandoAbstractMap<K,V>, assim:
public class GuavaMapFromSet extends AbstractMap {
public GuavaMapFromSet(Set keys,
Function super K, ? extends V> function) {
}
}
Digno de nota é que o contrato principal de todas as subclasses deAbstractMap é implementar o métodoentrySet, como fizemos. Em seguida, examinaremos duas partes críticas do código nas subseções a seguir.
5.1. Entradas
Outro atributo em nossoMap seráentries, representando nossoEntrySet:
private Set> entries;
O campoentries sempre será inicializado usando a entradaSet doconstructor:
public GuavaMapFromSet(Set keys,Function super K, ? extends V> function) {
this.entries=keys;
}
Uma nota rápida aqui - para manter uma visualização ao vivo, usaremos o mesmoiterator na entradaSet para osMap'sEntrySet. subsequentes
Ao cumprir o contrato deAbstractMap<K,V>, implementamos o métodoentrySet no qual retornamosentries:
@Override
public Set> entrySet() {
return this.entries;
}
5.2. Cache
EsteMap armazena os valores obtidos porapplying Function to Set:
private WeakHashMap cache;
6. OSet Iterator
Usaremos a entradaSet'siterator para osMap'sEntrySet subsequentes. Para fazer isso, usamos uma classeEntrySet personalizada, bem como uma classeEntry personalizada.
6.1. A classeEntry
Primeiro, vamos ver como uma única entrada emMap se parecerá com:
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();
}
}
Claramente, neste código, não permitimos modificar oSet da visualizaçãoMapas uma chamada parasetValue lança umUnsupportedOperationException.
Preste muita atenção emgetValue - este é o ponto crucial de nossa funcionalidadelive view. Verificamoscache dentro de nossoMap para o elementokey (Set atual).
Se encontrarmos a chave, a retornamos, senãowe apply our function to the current key and obtain a value, e a armazenamos emcache.
Desta forma, sempre queSet possui um novo elemento, o mapa fica atualizado, pois os novos valores são calculados em tempo real.
6.2. OEntrySet
Agora vamos implementar oEntrySet:
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();
}
}
Cumprimos o contrato para estenderAbstractSet substituindo os métodositeratoresize. Mas tem mais.
Lembre-se de que a instância desteEntrySet formaráentries em nossa visualizaçãoMap. Normalmente, oEntrySet de um mapa simplesmente retorna umEntry completo para cada iteração.
No entanto, em nosso caso, precisamos usariterator da entradaSet para manter nossa exibição ao vivo. Sabemos bem que ele retornará apenas os elementos deSet , então também precisamos de umiterator personalizado.
6.3. OIterator
Aqui está a implementação de nossoiterator para oEntrySet acima:
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 deve residir dentro da classeMyEntrySet, dessa forma, podemos compartilharSet'siterator na inicialização.
Ao percorrer as entradas deGuavaMapFromSet usandoiterator, uma chamada paranext simplesmente recupera a chave deiterator deSet e constrói umSingleEntry.
7. Juntando tudo
Depois de juntar o que cobrimos neste tutorial, vamos substituir a variávelliveMap das amostras anteriores e substituí-la por nosso mapa personalizado:
@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"));
}
Alterando o conteúdo da entradaSet, veremos que oMap atualiza em tempo real:
@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. Conclusão
Neste tutorial, vimos as diferentes maneiras de alavancarGuava operações eobtain a Map view from a Set by applying a Function.
A implementação completa de todos esses exemplos e fragmentos de códigocan be found in my Guava github project - este é um projeto baseado em Eclipse, portanto, deve ser fácil de importar e executar como está.