Czy jest możliwe HashMap
zwrócenie wartości domyślnej dla wszystkich kluczy, których nie ma w zestawie?
Czy jest możliwe HashMap
zwrócenie wartości domyślnej dla wszystkich kluczy, których nie ma w zestawie?
Odpowiedzi:
[Aktualizacja]
Jak zauważyli inni odpowiedzi i komentatorzy, od wersji Java 8 możesz po prostu zadzwonić Map#getOrDefault(...)
.
[Oryginalny]
Nie ma implementacji Map, która robi to dokładnie, ale zaimplementowanie własnej przez rozszerzenie HashMap byłoby trywialne:
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)
do (v == null && !this.containsKey(k))
w przypadku, gdy celowo dodali null
wartość. Wiem, to tylko przypadek narożny, ale autor może się na to natknąć.
!this.containsValue(null)
. To jest nieco inne niż !this.containsKey(k)
. containsValue
Rozwiązanie zawiedzie, jeśli jakiś inny klucz został jednoznacznie przypisana wartość null
. Na przykład: w map = new HashMap(); map.put(k1, null); V v = map.get(k2);
tym przypadku v
nadal będzie null
, prawda?
W Javie 8 użyj Map.getOrDefault . Pobiera klucz i wartość do zwrócenia, jeśli nie zostanie znaleziony pasujący klucz.
getOrDefault
jest bardzo ładny, ale wymaga domyślnej definicji przy każdym dostępie do mapy. Jednokrotne zdefiniowanie wartości domyślnej przyniosłoby również korzyści związane z czytelnością podczas tworzenia statycznej mapy wartości.
private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Użyj DefaultedMap Commons, jeśli nie masz ochoty wymyślać koła na nowo, np.
Map<String, String> map = new DefaultedMap<>("[NO ENTRY FOUND]");
String surname = map.get("Surname");
// surname == "[NO ENTRY FOUND]"
Możesz również przekazać istniejącą mapę, jeśli nie jesteś odpowiedzialny za jej tworzenie.
Java 8 wprowadziła ładną domyślną metodę computeIfAbsent do Map
interfejsu, która przechowuje leniwą wartość obliczoną i dlatego nie przerywa kontraktu mapy:
Map<Key, Graph> map = new HashMap<>();
map.computeIfAbsent(aKey, key -> createExpensiveGraph(key));
Pochodzenie: http://blog.javabien.net/2014/02/20/loadingcache-in-java-8-without-guava/
Disclamer: Ta odpowiedź nie pasuje dokładnie do tego, co zadał OP, ale może być przydatna w niektórych przypadkach dopasowanie tytułu pytania, gdy liczba kluczy jest ograniczona, a buforowanie różnych wartości byłoby opłacalne. Nie należy go używać w przeciwnym przypadku z dużą ilością kluczy i tą samą wartością domyślną, ponieważ niepotrzebnie marnuje to pamięć.
Nie możesz po prostu utworzyć statycznej metody, która robi dokładnie to?
private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
return map.containsKey(key) ? map.get(key) : defaultValue;
}
Możesz po prostu utworzyć nową klasę, która dziedziczy HashMap i dodać metodę getDefault. Oto przykładowy kod:
public class DefaultHashMap<K,V> extends HashMap<K,V> {
public V getDefault(K key, V defaultValue) {
if (containsKey(key)) {
return get(key);
}
return defaultValue;
}
}
Myślę, że nie powinieneś nadpisywać metody get (klawisz K) w swojej implementacji, z powodów określonych przez Eda Stauba w swoim komentarzu oraz z powodu zerwania kontraktu interfejsu Map (może to potencjalnie prowadzić do trudnych do znalezienia robaki).
get
metody. Z drugiej strony - Twoje rozwiązanie nie pozwala na używanie klasy przez interfejs, co często może mieć miejsce.
Posługiwać się:
myHashMap.getOrDefault(key, defaultValue);
Robi to domyślnie. Wraca null
.
HashMap
tylko dla tej funkcji, kiedynull
jest w porządku.
NullObject
wzorca lub nie chcesz rozpraszać kontroli null w całym kodzie - pragnienie, które całkowicie rozumiem.
znalazłem LazyMap jest całkiem pomocny.
Gdy metoda get (Object) jest wywoływana z kluczem, którego nie ma na mapie, do utworzenia obiektu używana jest fabryka. Utworzony obiekt zostanie dodany do mapy przy użyciu żądanego klucza.
To pozwala zrobić coś takiego:
Map<String, AtomicInteger> map = LazyMap.lazyMap(new HashMap<>(), ()->new AtomicInteger(0));
map.get(notExistingKey).incrementAndGet();
Wywołanie do get
tworzy wartość domyślną dla danego klucza. Możesz określić, jak utworzyć wartość domyślną z argumentem fabryki na LazyMap.lazyMap(map, factory)
. W powyższym przykładzie mapa jest inicjalizowana jako nowa AtomicInteger
z wartością 0.
List<String>
- używając zaakceptowanej odpowiedzi zaryzykowałbym użycie tej samej listy dla każdego nowego klucza, zamiast (powiedzmy) a new ArrayList<String>()
z fabryki.
Nie bezpośrednio, ale możesz rozszerzyć klasę, aby zmodyfikować jej metodę get. Oto gotowy do użycia przykład: 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);
}
Przykładowe zastosowanie:
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));
Musiałem odczytać wyniki zwrócone z serwera w formacie JSON, gdzie nie mogłem zagwarantować, że pola będą obecne. Używam klasy org.json.simple.JSONObject, która jest pochodną HashMap. Oto kilka funkcji pomocniczych, których użyłem:
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; }
}
W mieszanych projektach Java / Kotlin rozważ także Map.withDefault Kotlina .