발견되지 않은 키의 기본값을 반환하는 HashMap?


151

HashMap세트에서 찾을 수없는 모든 키에 대해 기본값을 반환 할 수 있습니까?


키가 있는지 확인하고 기본값을 반환 할 수 있습니다. 또는 클래스를 확장하고 동작을 수정하십시오. 또는 심지어 null을 사용할 수 있으며 사용하려는 곳이면 어디든 확인하십시오.
SudhirJ

2
이것은 stackoverflow.com/questions/4833336/… 과 관련이 있거나 중복 됩니다. 다른 옵션은 여기에서 논의됩니다.
Mark Butler

3
지도 API에 대한 Java (8) 솔루션을 체크 아웃 getOrDefault() 링크
트레이 Jonn

답변:


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값입니다. 알다시피, 이것은 모퉁이의 경우에 해당하지만 저자는 문제가 발생할 수 있습니다.
Adam Paynter

@maerics : 나는 당신이 사용하는 것으로 나타났습니다 !this.containsValue(null). 이것은 미묘하게 다릅니다 !this.containsKey(k). containsValue어떤 경우 솔루션은 실패하게됩니다 다른 키가 명시 적으로 값을 할당하고있다 null. 예를 들어 map = new HashMap(); map.put(k1, null); V v = map.get(k2);,이 경우 v에도 여전히 null맞습니까?
Adam Paynter

21
일반적으로 , 나는 이것이 나쁜 생각이라고 생각합니다. 기본 동작을 클라이언트 또는 맵이라고 주장하지 않는 델리게이트로 푸시했습니다. 특히, 유효한 keySet () 또는 entrySet ()이 없으면 맵 계약이 준수 될 것으로 예상되는 모든 문제가 발생합니다. 그리고 Key ()를 포함하는 무한한 유효한 키 집합은 진단하기 어려운 성능 저하를 일으킬 수 있습니다. 그러나 특정 목적을 달성하지 못할 수도 있습니다.
Ed Staub

이 접근 방식의 한 가지 문제점은 값이 복잡한 개체 인 경우입니다. Map <String, List> #put이 예상대로 작동하지 않습니다.
Eyal

ConcurrentHashMap에서는 작동하지 않습니다. 거기에서 get의 결과가 null인지 확인해야합니다.
dveim

162

Java 8에서는 Map.getOrDefault를 사용 하십시오 . 일치하는 키가 없으면 키를 반환하고 값을 반환합니다.


14
getOrDefault매우 훌륭하지만 맵에 액세스 할 때마다 기본 정의가 필요합니다. 기본값을 한 번 정의하면 정적 값 맵을 작성할 때 가독성이 향상됩니다.
ach

3
이것은 자신을 구현하는 것이 쉽지 않습니다. private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
잭 Satriano

@JackSatriano 그래,하지만 기본값을 하드 코딩해야하거나 정적 변수가 있어야합니다.
Blrp

1
기본값이 비싸거나 매번 달라야하는 경우 computeIfAbsent를 사용하여 아래의 답변을 참조하십시오.
hectorpal

메모리가 나쁘지만 기본값이 구성 / 계산에 비싼 경우 계산 시간을 절약합니다. 값이 싸면 기본값을 반환하는 대신 맵에 삽입해야하기 때문에 성능이 저하 될 수 있습니다. 물론 다른 옵션입니다.
Spycho 2016 년

73

바퀴를 재발견하고 싶지 않다면 Commons의 DefaultedMap을 사용하십시오 .

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/

Disclamer : 이 답변은 OP가 요청한 것과 정확히 일치하지 않지만 키 번호가 제한되어 있고 다른 값의 캐싱이 수익성이있는 경우 질문 제목과 일치하는 경우에 유용 할 수 있습니다. 메모리가 불필요하게 낭비되므로 키가 많고 기본값이 같은 반대의 경우에는 사용하면 안됩니다.


OP가 요청한 내용이 아닙니다. 그는지도에 부작용을 원하지 않습니다. 또한, 부재 한 각 키의 기본값을 저장하면 쓸모없는 메모리 공간이 손실됩니다.
numéro6

@ numéro6, 예, OP가 요청한 것과 정확히 일치하지 않지만 일부 인터넷 사용자는 여전히이 사이드 답변을 유용하게 생각합니다. 다른 답변에서 언급했듯이지도 계약을 위반하지 않고 OP가 요청한 것을 정확하게 달성하는 것은 불가능합니다. 여기에 언급되지 않은 또 다른 해결 방법 은 Map 대신 다른 추상화사용하는 것입니다 .
Vadzim

지도 계약을 위반하지 않고 OP가 요청한 내용을 정확하게 달성 할 수 있습니다. 해결 방법이 필요하지 않습니다. 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;
}

정적 저장 위치는 어디입니까?
Pacerier

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

Ed Staub가 언급 한 이유와 Map 인터페이스 계약 을 위반하기 때문에 구현에서 get (K key) 메소드를 재정의해서는 안된다고 생각합니다 (이는 잠재적으로 찾기 어려운 결과를 초래할 수 있음) 버그).


4
get메서드를 재정의하지 않는 것이 중요합니다 . 반면에-귀하의 솔루션은 인터페이스를 통한 클래스 사용을 허용하지 않습니다.
Krzysztof Jabłoński


3

기본적으로이 작업을 수행합니다. 를 반환합니다 null.


@Larry, 완벽하게 괜찮을 HashMap때이 기능 을 위해 서브 클래스를 작성하는 것이 조금 어리석은 것처럼 보입니다 null.
mrkhrts

15
그러나 NullObject패턴을 사용하고 있거나 코드 전체에 null 검사를 분산시키지 않으려는 경우 좋지 않습니다. 완전히 이해하고 싶습니다.
Dave Newton


1

LazyMap이 매우 유용 하다는 것을 알았 습니다.

맵에 존재하지 않는 키로 get (Object) 메소드를 호출하면 팩토리가 오브젝트를 작성하는 데 사용됩니다. 생성 된 객체는 요청 된 키를 사용하여 맵에 추가됩니다.

이를 통해 다음과 같은 작업을 수행 할 수 있습니다.

    Map<String, AtomicInteger> map = LazyMap.lazyMap(new HashMap<>(), ()->new AtomicInteger(0));
    map.get(notExistingKey).incrementAndGet();

get주어진 키에 대한 기본값 을 생성하기위한 호출 factory 인수를 사용하여 기본값을 작성하는 방법을 지정합니다 LazyMap.lazyMap(map, factory). 위의 예에서 맵은 AtomicInteger값이 0 인 새 것으로 초기화됩니다 .


이것은 기본값이 공장에서 생성된다는 점에서 허용되는 답변보다 이점이 있습니다. 내 기본값 List<String>이 수락 된 대답을 사용하면 공장에서 (예를 들어)가 아닌 각각의 새 키에 대해 동일한 목록을 사용할 위험이 new ArrayList<String>()있습니다.

0

직접적이지는 않지만 get 메소드를 수정하기 위해 클래스를 확장 할 수 있습니다. 사용할 준비가 된 예제는 다음과 같습니다 .


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 서버에서 반환 된 결과를 읽어야했습니다. HashMap에서 파생 된 org.json.simple.JSONObject 클래스를 사용하고 있습니다. 내가 사용한 도우미 기능은 다음과 같습니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.