HashMap, чтобы вернуть значение по умолчанию для не найденных ключей?


151

Можно ли HashMapвернуть значение по умолчанию для всех ключей, которые не найдены в наборе?


Вы можете проверить наличие ключа и вернуть значение по умолчанию. Или расширить класс и изменить поведение. или даже вы можете использовать нуль - и поставить некоторые проверки, где вы хотите его использовать.
— SudhirJ

2
Это связано / дублирует stackoverflow.com/questions/4833336/… некоторые другие варианты обсуждаются там.
— Марк Батлер

3
Посмотрите решение Java 8 для getOrDefault() ссылки
— Трей Джонн

Ответы:


136

[Обновить]

Как отмечают другие ответы и комментаторы, начиная с 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;
  }
}

20
Просто чтобы быть точным, вы можете изменить условие с (v == null)на, (v == null && !this.containsKey(k))если они намеренно добавили nullзначение. Я знаю, это всего лишь угловой случай, но автор может столкнуться с этим.
— Адам Пейнтер

@maerics: я заметил, что вы использовали !this.containsValue(null). Это немного отличается от !this.containsKey(k). containsValueРешение потерпит неудачу , если некоторые другие ключ был явно присвоено значение null. Например: map = new HashMap(); map.put(k1, null); V v = map.get(k2);в этом случае vвсе равно будет null, правильно?
— Адам Пейнтер

21
В целом , я думаю, что это плохая идея - я бы запихнул поведение по умолчанию в клиента или делегата, который не претендует на то, чтобы быть картой. В частности, отсутствие действительного keySet () или entrySet () вызовет проблемы со всем, что ожидает соблюдения контракта Map. А бесконечный набор допустимых ключей, который включает в себяKey (), может привести к снижению производительности, которую сложно диагностировать. Не сказать, однако, что это может не служить какой-то конкретной цели.
— Эд Стауб

Одна из проблем этого подхода заключается в том, что значение является сложным объектом. Карта <String, List> #put не будет работать должным образом.
— Eyal

Не работает на ConcurrentHashMap. Там вы должны проверить результат get для нуля.
— Двим

162

В Java 8 используйте Map.getOrDefault . Он принимает ключ и значение, которое возвращается, если не найдено ни одного соответствующего ключа.


14
getOrDefaultэто очень хорошо, но требует определения по умолчанию каждый раз, когда к карте обращаются. Однократное определение значения по умолчанию также будет иметь преимущества для удобства чтения при создании статической карты значений.
— ACH

3
Это тривиально, чтобы реализовать себя. private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
— Джек Сатриано,

@JackSatriano Да, но вам придется жестко закодировать значение по умолчанию или иметь статическую переменную для него.
— Blrp

1
Ниже приведен ответ с использованием computeIfAbsent, лучше, когда значение по умолчанию дорого или каждый раз должно отличаться.
— Hectorpal

Хотя это хуже для памяти и сэкономит время вычислений, только если значение по умолчанию дорого построить / вычислить. Если это дешево, вы, вероятно, обнаружите, что оно работает хуже, поскольку его нужно вставить в карту, а не просто вернуть значение по умолчанию. Конечно, еще один вариант, хотя.
— Spycho

73

Используйте DefonstedMap Commons, если вы не хотите изобретать велосипед, например,

Map<String, String> map = new DefaultedMap<>("[NO ENTRY FOUND]");
String surname = map.get("Surname"); 
// surname == "[NO ENTRY FOUND]"

Вы также можете передать существующую карту, если вы не отвечаете за ее создание.


26
+1, хотя иногда проще заново изобрести колесо, чем вводить большие зависимости для крошечных кусочков простой функциональности.
— maerics

3
и забавно то, что многие проекты, с которыми я работаю, уже имеют что-то подобное в classpath (Apache Commons или Google Guava)
— bartosz.r

@ bartosz.r, определенно не на мобильном телефоне
— Pacerier

44

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, но может быть полезен в некоторых случаях, когда заголовок вопроса совпадает, когда число ключей ограничено и кэширование различных значений будет выгодным. Он не должен использоваться в противоположном случае с большим количеством ключей и тем же значением по умолчанию, так как это приведет к ненужной трате памяти.


Не то, что спросил ОП: он не хочет никаких побочных эффектов на карте. Кроме того, сохранение значения по умолчанию для каждого отсутствующего ключа является бесполезной потерей памяти.
— numéro6

@ numéro6, да, это не совсем соответствует тому, что спросил OP, но некоторые люди, прибегающие к поиску, все еще находят этот ответ полезным. Как уже упоминалось в других ответах, невозможно добиться именно того, что попросил ОП, не нарушая контракт карты. Другой обходной путь, не упомянутый здесь, состоит в том, чтобы использовать другую абстракцию вместо Map .
— Вадим

Можно добиться именно того, что попросил ОП, не нарушая контракт карты. Никакого обходного пути не требуется, просто использование getOrDefault является правильным (наиболее обновленным) способом, computeIfAbsent - неправильным путем: вы потеряете время на вызов функции mappingFunction и памяти, сохраняя результат (оба для каждого отсутствующего ключа). Я не вижу веской причины сделать это вместо getOrDefault. То, что я описываю, является точной причиной, почему в контракте Map есть два различных метода: есть два различных варианта использования, которые не следует путать (некоторые из них мне пришлось исправить на работе). Этот ответ распространяет путаницу.
— numéro6

14

Разве вы не можете просто создать статический метод, который делает именно это?

private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
    return map.containsKey(key) ? map.get(key) : defaultValue;
}

где хранить статику?
— Пейсер

10

Вы можете просто создать новый класс, который наследует 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 (это может привести к некоторому трудному для поиска ошибок).


4
У вас есть смысл не переопределять getметод. С другой стороны - ваше решение не позволяет использовать класс через интерфейс, что часто может иметь место.
— Кшиштоф Яблоньский


3

Это делает это по умолчанию. Это возвращается null.


@Larry, мне кажется немного глупым выделять подкласс HashMapтолько для этой функциональности, когда nullвсе в порядке.
— mrkhrts

15
Это не хорошо, если вы используете NullObjectшаблон или не хотите разбрасывать нулевые проверки по всему коду - желание, которое я полностью понимаю.
— Дэйв Ньютон


1

Я нашел 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>()с завода.


0
/**
 * 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));

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; }
}   

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.