Skip to content

Instantly share code, notes, and snippets.

@cesarvasconcelos
Last active October 6, 2020 03:54
Show Gist options
  • Save cesarvasconcelos/3f0b272034f9543195a030ddff56a39e to your computer and use it in GitHub Desktop.
Save cesarvasconcelos/3f0b272034f9543195a030ddff56a39e to your computer and use it in GitHub Desktop.
Novos métodos Map Java 8
import java.util.*;
public class NewMapMethodsJava8 {
public static void main( String[] args )
{
// nova versão do antigo método get() em mapas
// antes do Java 8, Map.get() recebia a chave e
// retornava valor. Mas, se chave não existisse,
// get() retornaria null. Daí a ambiguidade:
// nos mapas que permitem valores nulos,
// o fato de retornar nulo me diz que
// a chave existe, mas o valor associado é null?
// ou quer dizer que a chave não existe?
// Javadoc alertava isso!
// dizendo que se o map permitir valores nulos,
// pode ser que a chave ainda exista. E que
// para saber se chave realmente existe, tinha de recorrer a containsKey()
// If this map permits null values, then a return value of null
// does not necessarily indicate that the map contains no mapping for the key;
// it's also possible that the map explicitly maps the key to null.
// The containsKey operation may be used to distinguish these two cases.
// ou seja, no caso de mapas que permitem V nulos,
// o get() antigo não me diz se a chave existe ou não
// e tenho de recorrer a containsKey()
Map<String, List<String>> map = new HashMap<>();
map.put( "Comédia", new ArrayList<>( Arrays.asList( "AceVentura", "AutoCompadecida" ) ) );
map.put( "Terror", null );
// antes tinha de usar uma view do Map.entrySet para poder imprimir o par-valor (entry)
Set<Map.Entry<String, List<String>>> entries = map.entrySet();
for ( Map.Entry<String, List<String>> entry : entries )
System.out.println( "Key:" + entry.getKey() + " Value:" + entry.getValue() );
// podia evitar toda a digitação do tipo Map.Entry<String, List<String>>
// se usasse a variável local com "var" Java 10
for ( var entry : map.entrySet() )
System.out.println( "Key:" + entry.getKey() + " Value:" + entry.getValue() );
// ou até evitar os dois anteriores bastando usar forEach()
map.forEach( ( key, value ) -> { // forEach() também é novidade Java 8
System.out.println( "Key:" + key + " Value:" + value );
} );
System.out.println( map.get( "Comédia" ) );
System.out.println( map.get( "Terror" ) ); // chave existe mas valor é que é nulo
System.out.println( map.get( "Romance" ) ); // problema-->chave não existe? ou valor é nulo?
// após o Java 8
// um novo método: getOrDefault()
// Returns the value to which the specified key is mapped,
// or defaultValue if this map contains no mapping for the key.
System.out.println(
map.getOrDefault( "Comédia", new ArrayList<>() ) );
System.out.println(
map.getOrDefault( "Terror", new ArrayList<>() ) ); // null: agora sei que valor é que é nulo
System.out.println(
map.getOrDefault( "Romance", new ArrayList<>() ) ); // []: agora sei que chave é que não existe
// novo método: putIfAbsent como alternativa ao antigo método put() em mapas
// no put de antes, programador tinha de verificar se chave não existe antes de inserir no mapa
// pois se chave existisse, o antigo put() sobrescrevia/apagava o valor V associado à chave K
// o put retornava o old V (que foi sobrescrito), caso chave existisse ou null.
// Javadoc: Returns the previous value V associated with key, or null if there was no mapping for key.
// (A null return can *also* indicate that the map previously associated null with key,
// if the implementation supports null values.)
// no Java 8, ficou mais fácil: o putIfAbsent adiciona, MAS ANTES verifica automati/te se
// chave já existe. Se sim, não substitui o valor existente, mas o retornará.
map.put( "Ficção",
new ArrayList<>( Arrays.asList( "Matrix" ) ) ); // antes: se chave existir, V será substituído
// depois: se chave existir, o valor existente não será substituído, mas retornado.
// diferente de put() o método abaixo garante que ou
// a lista original de filmes será retornada se K existir,
// ou null, se K não existir (daí cuidado ao tentar fazer chaining com putIfAbsent(K,D).add())
// pois se K não existir, null será retornado e ao fazer null.add(), teremos:
// java.lang.NullPointerException
map.putIfAbsent( "Ficção", new ArrayList<>() )
.add( "StarWars" ); // putIfAbsent não sobrescreverá a lista original
// cuidado ao tentar fazer chaining de chaining com putIfAbsent(K,D).add())
// map.putIfAbsent( "gênero-noir-francês", new ArrayList<>() )
// .add( "Filme Noir" ); // como K não existe, null.add() geraria java.lang.NullPointerException
map.forEach( ( key, value ) -> {
System.out.println( "Key:" + key + " Value:" + value );
} );
// novos métodos para replace() em mapas
// replace(K key, V value): retorna V anterior
// replace(K key ,V oldValue, V newValue): só faz replace se oldValue existir
List<String> original = map.replace( "Ficção", new ArrayList<>( Arrays.asList( "Sinais" ) ) );
System.out.println( original );
map.forEach( ( key, value ) -> {
System.out.println( "Key:" + key + " Value:" + value );
} );
map.replace( "Ficção", new ArrayList<>( Arrays.asList( "Sinais" ) ),
new ArrayList<>( Arrays.asList( "HarryPotter" ) ) ); //ok
map.replace( "Ficção", new ArrayList<>( Arrays.asList( "SINAIS" ) ),
new ArrayList<>( Arrays.asList( "HarryPotter2" ) ) ); //não
map.forEach( ( key, value ) -> {
System.out.println( "Key:" + key + " Value:" + value );
} );
// um novo método:remove(Object key, Object value)
// remove apenas se o par existir por completo
// i.e., for realmente o value que existe no mapa
// Removes the entry for the specified key
// *only* if it is currently mapped to the specified value.
map.remove( "Terror", new ArrayList<>( Arrays.asList( "A Freira" ) ) ); // não remove, mesmo que chave exista
map.remove( "Terror", null ); // ok, remove pois o "par" está correto
map.forEach( ( key, value ) -> {
System.out.println( "Key:" + key + " Value:" + value );
} );
// uma nova família de métodos: compute, computeIfPresent e computeIfAbsent
// basicamente são úteis até para adicionar e fazer remapeamentos
// note que todas as versões devem retornar um novo V que foi computado
// ou até o V que já existente no mapa antes, mas com uma computação a mais
// 1a versão: recebe além da chave K, a Bifunção (K,V e retorna V), ou seja
// V compute(K key, BiFunction<? superK, ? superV, ? extends V> function ) ou
// compute(key, (key,value) -> newValue ) só roda função se K existir
// bifunção irá receber K,V e retornar o valor V que será usado
map.compute( "Comédia", ( k, v ) -> {
if ( v.size() > 1 ) v.add( "Chaplin" );
else v.add( "Trapalhões" );
return v;
} );
// e se key não existir? Exception in thread "main" java.lang.NullPointerException
//map.compute( "Anime", ( k, v ) -> {
// if ( v.size() > 1 ) v.add( "Japão1" );
// else v.add( "Japão2" );
// return v;
//} );
// 2a versão: V computeifAbsent(K key, Function<? super K, ? super V> mappingfunction )
// ou apenas computeIfAbsent(key, key -> newValue )
// computar a função, mas apenas se chave não existir
// se K existe, retorne V existente;
// se K não existir, rode a função, e retorne o V computado
// i.e., função, CASO SEJA RODADA, deve receber K e retornar um o valor V computado
// note abaixo que a função lambda será executada apenas 1a vez, pois key Francês não existe
map.computeIfAbsent( "Francês", k -> new ArrayList<>() ) // como K não existe, a função (k->v)
.add( "Jaci Borrô" );// será rodada para produzir valor qdo K não existir
// dica: no caso acima, note que é bem mais seguro fazer chaining de add com computeIfAbset(K,V).add()
// do que com putIfAbsent; pois putIfAsent retorna null *quando K não existe*
// o que resultará em risco de *null*.add() com NullPointerException
// por outro lado, computeIfAbset(K,V) retornará a lista vazia (V que foi computado) quando K não existe*
// daí posso fazer add sem problemas de NullPointerException
// já na 2a vez abaixo note que, se chave existe, computeIfAbsent não faz nada
// e apenas retorna o V existente. Daí posso dar add facilmente
map.computeIfAbsent( "Francês", k -> new ArrayList<>() ) // se chave existe, retorne V existente
.add( "Paris 2" ); // uma boa sintaxe anternativa à putIfAbsent
map.computeIfAbsent( "Francês", k -> new ArrayList<>() ) // se chave existe, retorne V existente
.add( "Sauvignon 3" ); // dai posso pegar V existente e inserir
// outro exemplo: note abaixo que a função lambda será executada, pois key Ação não existe
map.computeIfAbsent( "Ação", k -> new ArrayList<>( Arrays.asList( "Comandos em " + k.toUpperCase() ) ) );
// agora abaixo note que a função lambda NÃO será executada, pois key Ação já existia
List<String> açãoList = map.computeIfAbsent( "Ação", k -> new ArrayList<>() );
System.out.println(açãoList);
// 3a versão: V computeIfPresent(K key, BiFunction<? superK, ? superV, ? extends V> function )
// ou apenas computeIfPresent(key, (key,value) -> newValue )
// computar a função, apenas se chave existir
// bifunção irá receber K,V e retornar um novo valor V a ser usado
// abaixo, como key Romance não existe, bifunção não será executada
map.computeIfPresent( "Romance", ( k, v ) -> Arrays.asList( "Linda Mulher" ) );
// agora abaixo note que a função lambda será executada, pois key Ação já existia
map.computeIfPresent( "Ação", ( k, v ) -> Arrays.asList( v.get( 0 ), "Rambo" ) );
map.forEach( ( key, value ) -> {
System.out.println( "Key:" + key + " Value:" + value );
} );
// novo método merge: V merge(K key, V newValue, BiFunction<? superV, ? superV, ? extends V> function)
// se chave não estiver no mapa, apenas adiciona o par K key, V newValue
// note que BiFunction existe para o caso de a key a entrar no merge já existir
// se não existir, merge apenas adiciona. Mas, se key existir, BiFunction recebe três valores:
// o V já existente no map destinatário, o V que quer entrar no mapa e o V resultante do merge
Map<String, List<String>> map2 = new HashMap<>();
map2.put( "Comédia", new ArrayList<>( Arrays.asList( "Trapalhões", "Deadpool" ) ) ); // veja que comédia existirá em map
map2.put( "Western", new ArrayList<>( Arrays.asList( "Kill Bill", "Faroeste 2" ) ) ); // este não existe
// quero percorrer o mapa2, par a par, e fazer merge em map1
map2.forEach(
// abaixo, um BiConsumer representando o par K,V de map2 que quero jogar em map1; BiConsumer não retorna nada
( keyMapa2, valueMap2 ) -> {
map.merge( keyMapa2, valueMap2, // <--- dois primeiros parâmetros de merge naturalmente são: o que inserir em map1?
// se keyMap2 não existir em map1, merge só insere par <keyMapa2, valueMap2> em map1
// MAS, se keyMap2 já existir em map1, tenho:
// A) uma lista de filmes já existente em map1
// B) uma nova lista de map2 querendo entrar em map1
// C) uma lista resultante/final que irá prevalecer no final do merge
// Solução? use BiFunction<VMap1,VMap2,VFinal> dirá o que fazer com esse par<V1,V2> existentes
// o BiFunction<VMap1,VMap2,VFinal> vai ajudar a resolver o conflito sobre o que fazer
// com os valores dos mapas de origem e de destino
( existingValueMap1, existingValueMap2 ) -> {
existingValueMap1.addAll( existingValueMap2 );
return existingValueMap1;
}
);
}
);
map.forEach( ( key, value ) -> {
System.out.println( "Key:" + key + " Value:" + value );
} );
// um novo método:replaceAll(BiFunction function)
// substituirá todos os V existentes do mapa por um novo V (retornado pela BiFunction)
// ou seja, replaceAll( (K, oldV) -> newV )
map.replaceAll( ( keyStrGênero, oldListV ) -> {
// quero manter os V da lista existente
ArrayList<String> newListV = new ArrayList<>(
Optional.ofNullable( oldListV ) // caso V existente seja null
.orElse( List.of() ) // entao lista nova
);
newListV.add( "Xuxa" ); // e adicionar 1 item de Xuxa em todas as listas V
return newListV;
} );
// ou até substituir todas as listas por uma newListV
map.replaceAll( ( strKey, oldList ) -> Collections.singletonList( "Trapalhões" ) );
map.forEach( ( key, value ) -> {
System.out.println( "Key:" + key + " Value:" + value );
} );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment