Czy jest możliwe HashMapzwrócenie wartości domyślnej dla wszystkich kluczy, których nie ma w zestawie?
Czy jest możliwe HashMapzwró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 nullwartość. 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). containsValueRozwią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 vnadal 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.
getOrDefaultjest 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 Mapinterfejsu, 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).
getmetody. 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.
HashMaptylko 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 gettworzy 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 AtomicIntegerz 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 .