É possível HashMap
retornar um valor padrão para todas as chaves que não foram encontradas no conjunto?
É possível HashMap
retornar um valor padrão para todas as chaves que não foram encontradas no conjunto?
Respostas:
[Atualizar]
Conforme observado por outras respostas e comentadores, no Java 8 você pode simplesmente ligar Map#getOrDefault(...)
.
[Original]
Não há implementação de mapa que faça isso exatamente, mas seria trivial implementar o seu próprio, estendendo o HashMap:
public class DefaultHashMap<K,V> extends HashMap<K,V> {
protected V defaultValue;
public DefaultHashMap(V defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public V get(Object k) {
return containsKey(k) ? super.get(k) : defaultValue;
}
}
(v == null)
para (v == null && !this.containsKey(k))
, caso eles tenham adicionado um null
valor propositalmente . Eu sei, este é apenas um caso de esquina, mas o autor pode se deparar com isso.
!this.containsValue(null)
. Isso é sutilmente diferente de !this.containsKey(k)
. A containsValue
solução falhará se alguma outra chave tiver sido atribuída explicitamente ao valor null
. Por exemplo: map = new HashMap(); map.put(k1, null); V v = map.get(k2);
Nesse caso, v
ainda estará null
correto?
No Java 8, use Map.getOrDefault . Ele pega a chave e o valor a retornar se nenhuma chave correspondente for encontrada.
getOrDefault
é muito bom, mas requer a definição padrão sempre que o mapa é acessado. Definir um valor padrão uma vez também traria benefícios de legibilidade ao criar um mapa estático de valores.
private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Use o DefaultedMap do Commons se não quiser reinventar a roda, por exemplo,
Map<String, String> map = new DefaultedMap<>("[NO ENTRY FOUND]");
String surname = map.get("Surname");
// surname == "[NO ENTRY FOUND]"
Você também pode passar um mapa existente se não estiver encarregado de criar o mapa em primeiro lugar.
O Java 8 introduziu um bom método padrão computeIfAbsent para fazer a Map
interface, que armazena valor lento e, portanto, não quebra o contrato do mapa:
Map<Key, Graph> map = new HashMap<>();
map.computeIfAbsent(aKey, key -> createExpensiveGraph(key));
Origem: http://blog.javabien.net/2014/02/20/loadingcache-in-java-8-without-guava/
Disclamer: Essa resposta não corresponde exatamente ao que o OP pediu, mas pode ser útil em alguns casos, corresponder ao título da pergunta quando o número de chaves for limitado e o armazenamento em cache de valores diferentes for rentável. Não deve ser usado no caso oposto, com muitas chaves e o mesmo valor padrão, pois isso desperdiçaria desnecessariamente memória.
Você não pode simplesmente criar um método estático que faça exatamente isso?
private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
return map.containsKey(key) ? map.get(key) : defaultValue;
}
Você pode simplesmente criar uma nova classe que herda HashMap e adicione o método getDefault. Aqui está um código de exemplo:
public class DefaultHashMap<K,V> extends HashMap<K,V> {
public V getDefault(K key, V defaultValue) {
if (containsKey(key)) {
return get(key);
}
return defaultValue;
}
}
Eu acho que você não deve substituir o método get (chave K) em sua implementação, por causa dos motivos especificados por Ed Staub em seu comentário e porque você quebrará o contrato da interface do Mapa (isso pode potencialmente levar a alguns difíceis de encontrar) insetos).
get
método. Por outro lado - sua solução não permite o uso da classe via interface, o que geralmente pode ser o caso.
Usar:
myHashMap.getOrDefault(key, defaultValue);
Faz isso por padrão. Retorna null
.
HashMap
apenas para esta funcionalidade quando null
está perfeitamente bem.
NullObject
padrão, ou não quiser espalhar verificações nulas por todo o código - um desejo que eu entendo completamente.
Achei o LazyMap bastante útil.
Quando o método get (Object) é chamado com uma chave que não existe no mapa, a fábrica é usada para criar o objeto. O objeto criado será adicionado ao mapa usando a chave solicitada.
Isso permite que você faça algo assim:
Map<String, AtomicInteger> map = LazyMap.lazyMap(new HashMap<>(), ()->new AtomicInteger(0));
map.get(notExistingKey).incrementAndGet();
A chamada para get
cria um valor padrão para a chave fornecida. Você especifica como criar o valor padrão com o argumento de fábrica para LazyMap.lazyMap(map, factory)
. No exemplo acima, o mapa é inicializado para um novo AtomicInteger
com o valor 0.
Não diretamente, mas você pode estender a classe para modificar seu método get. Aqui está um exemplo pronto para usar: http://www.java2s.com/Code/Java/Collections-Data-Structure/ExtendedVersionofjavautilHashMapthat fornece um método de extensão de método avançado para aceleração de valor de falha.htm
/**
* Extension of TreeMap to provide default value getter/creator.
*
* NOTE: This class performs no null key or value checking.
*
* @author N David Brown
*
* @param <K> Key type
* @param <V> Value type
*/
public abstract class Hash<K, V> extends TreeMap<K, V> {
private static final long serialVersionUID = 1905150272531272505L;
/**
* Same as {@link #get(Object)} but first stores result of
* {@link #create(Object)} under given key if key doesn't exist.
*
* @param k
* @return
*/
public V getOrCreate(final K k) {
V v = get(k);
if (v == null) {
v = create(k);
put(k, v);
}
return v;
}
/**
* Same as {@link #get(Object)} but returns specified default value
* if key doesn't exist. Note that default value isn't automatically
* stored under the given key.
*
* @param k
* @param _default
* @return
*/
public V getDefault(final K k, final V _default) {
V v = get(k);
return v == null ? _default : v;
}
/**
* Creates a default value for the specified key.
*
* @param k
* @return
*/
abstract protected V create(final K k);
}
Exemplo de uso:
protected class HashList extends Hash<String, ArrayList<String>> {
private static final long serialVersionUID = 6658900478219817746L;
@Override
public ArrayList<Short> create(Short key) {
return new ArrayList<Short>();
}
}
final HashList haystack = new HashList();
final String needle = "hide and";
haystack.getOrCreate(needle).add("seek")
System.out.println(haystack.get(needle).get(0));
Eu precisava ler os resultados retornados de um servidor em JSON onde não podia garantir que os campos estivessem presentes. Estou usando a classe org.json.simple.JSONObject, que é derivada do HashMap. Aqui estão algumas funções auxiliares que eu empreguei:
public static String getString( final JSONObject response,
final String key )
{ return getString( response, key, "" ); }
public static String getString( final JSONObject response,
final String key, final String defVal )
{ return response.containsKey( key ) ? (String)response.get( key ) : defVal; }
public static long getLong( final JSONObject response,
final String key )
{ return getLong( response, key, 0 ); }
public static long getLong( final JSONObject response,
final String key, final long defVal )
{ return response.containsKey( key ) ? (long)response.get( key ) : defVal; }
public static float getFloat( final JSONObject response,
final String key )
{ return getFloat( response, key, 0.0f ); }
public static float getFloat( final JSONObject response,
final String key, final float defVal )
{ return response.containsKey( key ) ? (float)response.get( key ) : defVal; }
public static List<JSONObject> getList( final JSONObject response,
final String key )
{ return getList( response, key, new ArrayList<JSONObject>() ); }
public static List<JSONObject> getList( final JSONObject response,
final String key, final List<JSONObject> defVal ) {
try { return response.containsKey( key ) ? (List<JSONObject>) response.get( key ) : defVal; }
catch( ClassCastException e ) { return defVal; }
}
Em projetos Java / Kotlin mistos, considere também o Map.withDefault do Kotlin .