Чи можливо HashMap
повернути значення за замовчуванням для всіх ключів, які не знайдені в наборі?
getOrDefault()
посилання
Чи можливо HashMap
повернути значення за замовчуванням для всіх ключів, які не знайдені в наборі?
getOrDefault()
посилання
Відповіді:
[Оновлення]
Як зазначають інші відповіді та коментатори, щодо Java 8 ви можете просто зателефонувати Map#getOrDefault(...)
.
[Оригінал]
Немає втілення карт, яке б це робило саме так, але було б тривіально реалізувати власну, розширивши 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"); }
Використовуйте Commons ' DefaultedMap, якщо ви не хочете винаходити колесо, наприклад,
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/
Відмова від відповідальності: Ця відповідь не відповідає саме тому, що запитувала ОП, але може бути корисною в деяких випадках, узгоджуючи заголовок питання, коли число ключів обмежене і кешування різних значень було б вигідним. Він не повинен використовуватися в протилежному випадку з великою кількістю клавіш і таким же значенням за замовчуванням, оскільки це зайве марно пам'ять.
Ви не можете просто створити статичний метод, який робить саме це?
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 (ключ) у вашій реалізації через причини, зазначені Едом Стаубом у своєму коментарі, і тому, що ви порушите договір інтерфейсу 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.
List<String>
- використовуючи прийняту відповідь, я ризикну використовувати той самий список для кожного нового ключа, а не (скажімо) a new ArrayList<String>()
з заводу.
Не безпосередньо, але ви можете розширити клас, щоб змінити його метод get. Ось готовий для використання приклад: http://www.java2s.com/Code/Java/Collections-Data-Structure/ExtendedVersionofjavautilHashMapthatprovidesanextendedgetmethodaccpetingadefaultvalue.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 також враховують Map Kot.withDefault Котліна .