map.get ()을 사용할 때 java Map.containsKey () 중복을 사용하고 있습니다.


93

나는 containsKey()방법을 사용하는 것을 자제 java.util.Map하고 대신 .NET의 결과에 대해 null 검사를 수행하는 것이 모범 사례 내에서 허용 가능한지 한동안 궁금해했습니다 get().

내 근거는 값을 두 번 조회하는 것이 중복 된 것처럼 보인다는 것 containsKey()입니다 get().

반면에 대부분의 표준 Map캐시 구현은 마지막 조회 를 캐시하거나 컴파일러가 중복성을 제거 할 수 있으며 코드의 가독성을 위해 containsKey()부분 을 유지하는 것이 바람직 할 수 있습니다.

귀하의 의견에 감사드립니다.

답변:


112

일부 Map 구현은 HashMap과 같은 null 값을 가질 수 있습니다.이 경우 get(key)반환 null하면이 키와 관련된 맵에 항목이 없음을 보장하지 않습니다.

따라서지도에 키가 포함되어 있는지 알고 싶다면 Map.containsKey. 키에 매핑 된 값이 필요한 경우 Map.get(key). 이 맵이 null 값을 허용하는 경우 null 반환 값이 반드시 맵에 키에 대한 매핑이 포함되어 있지 않음을 나타내는 것은 아닙니다. 이 경우 Map.containsKey쓸모가 없으며 성능에 영향을 미칩니다. 또한지도에 대한 동시 액세스 (예 ConcurrentHashMap:)의 Map.containsKey(key)경우 테스트 한 후 를 호출하기 전에 다른 스레드에서 항목을 제거 할 가능성이 있습니다 Map.get(key).


8
값이로 설정되어 있어도 설정 null되지 않은 키 / 값과 다르게 처리 하시겠습니까? 특별히 다르게 취급 할 필요가없는 경우 다음을 사용할 수 있습니다.get()
Peter Lawrey 2013 년

1
귀하의 경우 Map입니다 private, 당신의 클래스는 보장 할 수있을 null지도에 삽입되지 않습니다. 이 경우 get()대신을 사용할 수 있습니다 containsKey(). 그렇게하는 것이 더 명확하고 어떤 경우에는 좀 더 효율적일 수 있습니다.
Raedwald

44

나는 작성하는 것이 상당히 표준이라고 생각합니다.

Object value = map.get(key);
if (value != null) {
    //do something with value
}

대신에

if (map.containsKey(key)) {
    Object value = map.get(key);
    //do something with value
}

가독성이 떨어지지 않고 약간 더 효율적이므로 그렇게하지 않을 이유가 없습니다. 분명히 지도에 null이 포함될 수 있다면 두 옵션의 의미가 동일하지 않습니다 .


8

assylias가 지적했듯이 이것은 의미 론적 질문입니다. 일반적으로 Map.get (x) == null은 원하는 것이지만 containsKey를 사용하는 것이 중요한 경우가 있습니다.

그러한 경우 중 하나가 캐시입니다. 나는 존재하지 않는 엔티티를 자주 찾는 데이터베이스를 쿼리하는 웹 앱에서 성능 문제를 해결 한 적이 있습니다. 해당 구성 요소에 대한 캐싱 코드를 연구했을 때 cache.get (key) == null 인 경우 데이터베이스를 쿼리하고 있다는 것을 깨달았습니다. 데이터베이스가 null (엔티티를 찾을 수 없음)을 반환하면 해당 키-> null 매핑을 캐시합니다.

containsKey로 전환하면 null 값에 대한 매핑이 실제로 의미가 있기 때문에 문제가 해결되었습니다. null에 대한 키 매핑은 존재하지 않는 키와 다른 의미를 가졌습니다.


흥미 롭군. 값을 캐싱하기 전에 단순히 null 검사를 추가하지 않은 이유는 무엇입니까?
Saket

그것은 아무것도 바꾸지 않을 것입니다. 핵심은 null에 대한 키 매핑이 "이미 수행했습니다. 캐시되었습니다. 값 null입니다"라는 의미입니다. 주어진 키를 전혀 포함하지 않는 경우와 비교하면 "모르겠습니다. 캐시에 없습니다. DB를 확인해야 할 수도 있습니다."
Brandon

5
  • containsKey다음에 오는 a get는 null 값이 절대 허용되지 않는다는 선제적인 경우에만 중복됩니다. null 값이 유효하지 않으면의 호출에 containsKey사소한 성능 저하가 발생하며 아래 벤치 마크에 표시된 것처럼 오버 헤드 일뿐입니다.

  • Java 8 Optional관용구- Optional.ofNullable(map.get(key)).ifPresent또는 Optional.ofNullable(map.get(key)).ifPresent-는 바닐라 널 검사에 비해 사소한 오버 헤드가 발생합니다.

  • A HashMapO(1)상수 테이블 조회를 TreeMap사용하는 반면 a 는 O(log(n))조회를 사용합니다 . containsKeya로 다음 getA의 호출 할 때 관용구가 훨씬 느립니다 TreeMap.

벤치 마크

참조 https://github.com/vkarun/enum-reverse-lookup-table-jmh를

// t1
static Type lookupTreeMapNotContainsKeyThrowGet(int t) {
  if (!lookupT.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupT.get(t);
}
// t2
static Type lookupTreeMapGetThrowIfNull(int t) {
  Type type = lookupT.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// t3
static Type lookupTreeMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupT.get(t)).orElseThrow(() -> new 
      IllegalStateException("Unknown Multihash type: " + t));
}
// h1
static Type lookupHashMapNotContainsKeyThrowGet(int t) {
  if (!lookupH.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupH.get(t);
}
// h2
static Type lookupHashMapGetThrowIfNull(int t) {
  Type type = lookupH.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// h3
static Type lookupHashMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupH.get(t)).orElseThrow(() -> new 
    IllegalStateException("Unknown Multihash type: " + t));
}
벤치 마크 (반복) (lookupApproach) 모드 Cnt 점수 오류 단위

MultihashTypeLookupBenchmark.testLookup 1000 t1 평균 9 33.438 ± 4.514 us / op
MultihashTypeLookupBenchmark.testLookup 1000 t2 평균 9 26.986 ± 0.405 us / op
MultihashTypeLookupBenchmark.testLookup 1000 t3 평균 9 39.259 ± 1.306 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h1 평균 9 18.954 ± 0.414 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h2 평균 9 15.486 ± 0.395 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h3 평균 9 16.780 ± 0.719 us / op

TreeMap 소스 참조

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java

HashMap 소스 참조

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java


3

Java8 Optional을 사용하면 @assylias가 더 읽기 쉽게 대답 할 수 있습니다.

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)

2

Java에서 구현을 확인하는 경우

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

둘 다 getNode를 사용하여 일치 항목을 검색합니다. 여기서 주요 작업이 완료됩니다.

중복성은 예를 들어 해시 맵에 저장된 사전이있는 경우 상황에 따라 다릅니다. 단어의 의미를 검색하고 싶을 때

하기...

if(dictionary.containsKey(word)) {
   return dictionary.get(word);
}

중복됩니다.

그러나 단어가 유효한지 사전에 따라 확인하려는 경우. 하기...

 return dictionary.get(word) != null;

위에...

 return dictionary.containsKey(word);

중복됩니다.

내부적으로 HashMap을 사용하는 HashSet 구현 을 확인하면 'contains'메소드에서 'containsKey'를 사용합니다.

    public boolean contains(Object o) {
        return map.containsKey(o);
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.