Можно ли HashMap
вернуть значение по умолчанию для всех ключей, которые не найдены в наборе?
getOrDefault()
ссылки
Можно ли HashMap
вернуть значение по умолчанию для всех ключей, которые не найдены в наборе?
getOrDefault()
ссылки
Ответы:
[Обновить]
Как отмечают другие ответы и комментаторы, начиная с Java 8 вы можете просто позвонить Map#getOrDefault(...)
.
[Оригинал]
Нет реализации Map, которая делает это точно, но было бы тривиально реализовать свою собственную, расширяя 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)
на, (v == null && !this.containsKey(k))
если они намеренно добавили null
значение. Я знаю, это всего лишь угловой случай, но автор может столкнуться с этим.
!this.containsValue(null)
. Это немного отличается от !this.containsKey(k)
. containsValue
Решение потерпит неудачу , если некоторые другие ключ был явно присвоено значение null
. Например: map = new HashMap(); map.put(k1, null); V v = map.get(k2);
в этом случае v
все равно будет null
, правильно?
В Java 8 используйте Map.getOrDefault . Он принимает ключ и значение, которое возвращается, если не найдено ни одного соответствующего ключа.
getOrDefault
это очень хорошо, но требует определения по умолчанию каждый раз, когда к карте обращаются. Однократное определение значения по умолчанию также будет иметь преимущества для удобства чтения при создании статической карты значений.
private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Используйте DefonstedMap Commons, если вы не хотите изобретать велосипед, например,
Map<String, String> map = new DefaultedMap<>("[NO ENTRY FOUND]");
String surname = map.get("Surname");
// surname == "[NO ENTRY FOUND]"
Вы также можете передать существующую карту, если вы не отвечаете за ее создание.
Java 8 представила хороший метод по умолчанию computeIfAbsent для Map
интерфейса, который хранит ленивые значения и не нарушает контракт карты:
Map<Key, Graph> map = new HashMap<>();
map.computeIfAbsent(aKey, key -> createExpensiveGraph(key));
Происхождение: http://blog.javabien.net/2014/02/20/loadingcache-in-java-8-without-guava/
Отказ от ответственности: этот ответ не соответствует точно заданному OP, но может быть полезен в некоторых случаях, когда заголовок вопроса совпадает, когда число ключей ограничено и кэширование различных значений будет выгодным. Он не должен использоваться в противоположном случае с большим количеством ключей и тем же значением по умолчанию, так как это приведет к ненужной трате памяти.
Разве вы не можете просто создать статический метод, который делает именно это?
private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
return map.containsKey(key) ? map.get(key) : defaultValue;
}
Вы можете просто создать новый класс, который наследует HashMap, и добавить метод getDefault. Вот пример кода:
public class DefaultHashMap<K,V> extends HashMap<K,V> {
public V getDefault(K key, V defaultValue) {
if (containsKey(key)) {
return get(key);
}
return defaultValue;
}
}
Я думаю, что вам не следует переопределять метод get (K key) в вашей реализации из-за причин, указанных Эдом Стаубом в его комментарии, и из-за того, что вы нарушите контракт интерфейса Map (это может привести к некоторому трудному для поиска ошибок).
get
метод. С другой стороны - ваше решение не позволяет использовать класс через интерфейс, что часто может иметь место.
Использование:
myHashMap.getOrDefault(key, defaultValue);
Это делает это по умолчанию. Это возвращается null
.
HashMap
только для этой функциональности, когда null
все в порядке.
NullObject
шаблон или не хотите разбрасывать нулевые проверки по всему коду - желание, которое я полностью понимаю.
Я нашел LazyMap весьма полезным.
Когда метод get (Object) вызывается с ключом, который не существует на карте, фабрика используется для создания объекта. Созданный объект будет добавлен на карту с использованием запрошенного ключа.
Это позволяет вам сделать что-то вроде этого:
Map<String, AtomicInteger> map = LazyMap.lazyMap(new HashMap<>(), ()->new AtomicInteger(0));
map.get(notExistingKey).incrementAndGet();
Вызов get
создает значение по умолчанию для данного ключа. Вы указываете, как создать значение по умолчанию с заводским аргументом в LazyMap.lazyMap(map, factory)
. В приведенном выше примере карта инициализируется как новая AtomicInteger
со значением 0.
Не напрямую, но вы можете расширить класс, чтобы изменить его метод get. Вот готовый к использованию пример: http://www.java2s.com/Code/Java/Collections-Data-Structure/ExtendedVersionofjavautilHashMapthatprovidesanextendedgetmethodaccpetingadefaultvalueue.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);
}
Пример использования:
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));
Мне нужно было прочитать результаты, полученные с сервера в JSON, где я не мог гарантировать наличие полей. Я использую класс org.json.simple.JSONObject, который является производным от HashMap. Вот некоторые вспомогательные функции, которые я использовал:
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; }
}
В смешанных проектах Java / Kotlin также рассмотрите Kotlin's Map.withDefault .