해시 맵의 해시 맵을 초기화하는 코드 반복을 피하려면 어떻게해야합니까?


27

모든 고객은 날짜별로 인보이스의 해시 맵으로 ID와 고객의 해시 맵으로 저장된 날짜와 함께 많은 송장을 가지고 있습니다.

HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.get(id);

if(allInvoices!=null){
    allInvoices.put(date, invoice);      //<---REPEATED CODE
}else{
    allInvoices = new HashMap<>();
    allInvoices.put(date, invoice);      //<---REPEATED CODE
    allInvoicesAllClients.put(id, allInvoices);
}

Java 솔루션은 다음을 사용하는 것 같습니다 getOrDefault.

HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.getOrDefault(
    id,
    new HashMap<LocalDateTime, Invoice> (){{  put(date, invoice); }}
);

그러나 get이 null이 아닌 경우 여전히 put (날짜, 송장)을 실행하고 "allInvoicesAllClients"에 데이터를 추가해야합니다. 그래서별로 도움이되지 않는 것 같습니다.


키의 고유성을 보장 할 수없는 경우 보조 맵에 ​​송장 대신 List <Invoice> 값이있는 것이 가장 좋습니다.
라이언

답변:


39

이것은 훌륭한 유스 케이스입니다 Map#computeIfAbsent. 스 니펫은 기본적으로 다음과 같습니다.

allInvoicesAllClients.computeIfAbsent(id, key -> new HashMap<>()).put(date, invoice);

경우 id에 키와 존재하지 않는 allInvoicesAllClients, 그것은에서 매핑을 만듭니다 id새에 HashMap새를 반환합니다 HashMap. 이 id키로 존재하면 기존을 반환합니다 HashMap.


1
computeIfAbsent는 get (id) (또는 get (id)에 의해 지정된 풋)을 수행하므로 다음 put은 올바른 put (date) 항목을 수정하기 위해 수행됩니다.
Hernán Eche

allInvoicesAllClients.computeIfAbsent(id, key -> Map.of(date, invoice))
알렉산더-복원 모니카

1
@ Alexander-ReinstateMonica Map.of는 수정할 수없는을 생성 Map하므로 OP가 원하는지 확실하지 않습니다.
Jacob G.

이 코드가 OP가 원래 가지고 있던 것보다 덜 효율적입니까? Java가 람다 함수를 처리하는 방법에 익숙하지 않기 때문에 이것을 묻습니다.
Zecong Hu

16

computeIfAbsent이 특별한 경우에 대한 훌륭한 솔루션입니다. 일반적으로 아무도 언급하지 않았으므로 다음 사항에 유의하고 싶습니다.

"outer"해시 맵 은 "inner"해시 맵에 대한 참조 만 저장 하므로 코드 중복을 피하기 위해 작업 순서를 다시 지정할 수 있습니다.

HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.get(id);

if (allInvoices == null) {           
    allInvoices = new HashMap<>();
    allInvoicesAllClients.put(id, allInvoices);
}

allInvoices.put(date, invoice);      // <--- no longer repeated

Java 8이 멋진 computeIfAbsent()메소드 와 함께 등장하기 전에 수십 년 동안 이렇게했습니다 !
닐 바틀렛

1
나는 여전히지도 구현이 단일 get-or-put-and-return-if-absent 메소드를 제공하지 않는 언어로 오늘날이 접근법을 사용합니다. 이 질문에 Java 8 태그가 지정
Quinn Mortimer

11

"이중 괄호"맵 초기화는 거의 사용하지 않아야합니다.

{{  put(date, invoice); }}

이 경우에는 computeIfAbsent

allInvoicesAllClients.computeIfAbsent(id, (k) -> new HashMap<>())
                     .put(date, allInvoices);

이 ID에 대한지도가 없으면지도를 삽입합니다. 결과는 기존 또는 계산 된 맵이됩니다. 그런 다음 put해당 맵의 항목이 null이되지 않도록 보장 할 수 있습니다.


1
나는, 내가 아니라, 당신이 대신 일의 ID를 사용하기 때문에 아마도 단 한 줄의 코드가 혼란 allInvoicesAllClients은 넣어, 내가 그것을 편집 할 수 있습니다 downvote 사람 모른다
에르난 Eche

1
@ HernánEche 아. 내 실수. 감사. 그렇습니다 id. 원한다면 computeIfAbsent조건부로 생각할 수 있습니다 . 그리고 그것은 또한 값을 반환합니다
Michael

" "이중 괄호 "맵 초기화를 사용해서는 안됩니다. "왜? (나는 당신이 옳다는 것을 의심하지 않습니다; 나는 진짜 호기심을 요구하고 있습니다.)
Heinzi

1
@Heinzi 익명의 내부 클래스를 생성하기 때문입니다. 이것은 클래스를 선언 한 클래스에 대한 참조를 보유하며, 맵을 노출하면 (예 : 게터를 통해) 둘러싼 클래스가 가비지 수집되지 않도록합니다. 또한 Java에 익숙하지 않은 사람들에게는 혼란 스러울 수 있습니다. 이니셜 라이저 블록은 거의 사용 {{ }}되지 않으며, 이렇게 작성하면 특별한 의미가있는 것처럼 보이지만 그렇지 않습니다.
Michael

1
@ 마이클 : 감사합니다. 익명의 내부 클래스는 항상 정적이 아닌 것을 잊어 버렸습니다 (필요하지 않아도).
Heinzi

5

이것은 다른 답변보다 길지만 훨씬 더 읽기 쉽습니다.

if(!allInvoicesAllClients.containsKey(id))
    allInvoicesAllClients.put(id, new HashMap<LocalDateTime, Invoice>());

allInvoicesAllClients.get(id).put(date, invoice);

3
이것은 HashMap에서 작동 할 수 있지만 일반적인 접근법은 최적이 아닙니다. 이것이 ConcurrentHashMaps 인 경우 이러한 작업은 원자 적이 지 않습니다. 이 경우, 체크-앤-액트는 경쟁 조건으로 이어질 것입니다. 어쨌든 증오에 찬성했다.
Michael

0

여기서 두 가지 별도의 작업을 수행합니다. HashMap 존재 여부를 하고 새 항목을 추가합니다.

기존 코드는 해시 맵을 등록하기 전에 먼저 새 요소를 삽입해야하지만 필요하지는 않습니다. HashMap 여기서 순서를 신경 쓰지 하지 않습니다. 두 가지 변형 모두 스레드 안전하지 않으므로 아무것도 잃지 않습니다.

@Heinzi가 제안한 것처럼이 두 단계를 나눌 수 있습니다.

내가 또 할 것입니다 것은 창조 오프로드입니다 HashMap받는 allInvoicesAllClients소위, 객체 get메소드는 반환 할 수 없습니다null .

또한 null포인터를 가져 get오고 단일 항목 put으로 새 HashMap항목 을 결정할 수 있는 별도의 스레드 간 경쟁 가능성을 put줄입니다 . 두 번째 항목 은 첫 번째 항목을 버리고 Invoice개체를 잃을 수 있습니다.

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