본문 바로가기

JAVA

[JAVA] LinkedHashMap의 방어적 복사

Map.copyOf() 와 Collections.unmodifiableMap()

  우아한 테크코스에서 블랙잭 미션을 하다가 입력된 Player들의 순서대로 결과를 보여주고 싶어서 LinkedHashMap으로 Player의 이름과 게임 결과를 묶어서 전달해주었다.

 

  게임 결과를 계산하고 저장하는 객체에서 외부에서 게임 결과를 요청받았을 때 LinkedHashMap을 넘겨주도록 했는데 내부에 저장된 요소를 수정하지 못하도록 방어적 복사를 해서 넘겨주려고 Map.copyOf()를 사용했다. 그런데 최종 결과를 출력할 때 Player들의 순서를 보장해주지 않는 문제가 발생했다. 아마도 Map.copyOf() 메서드가 LinkedHashMap()처럼 순서를 보장해주지 않는 것으로 보였다.

 

Map.copyOf는 내부 요소를 복사해서 넘겨줌과 동시에 수정불가하게 만든다.

 

입력된 순서와 출력된 순서가 다르다??

 

  이전 미션 때 '정말 간편한 기능이다~'라고 좋아하면서 배웠던 copyOf()가 바로 사용 불가능한 상황이 되버려 외부에서 수정이 불가능한 LinkedHashMap을 반환하려면 어떻게 해야할지 고민을 하다가 원래 사용했던 Collections.tounmodifiableMap()을 사용해보았다. 그런데 웬일? 이 방법은 5번을 시도해봐도 출력순서가 유지되는 것이다.

 

이건 됐다. (딜러 손해는 덤)

 

  내부 구현이 궁금해져서 하나하나 타고 까볼 수밖에 없는 순간이다.

 

Collections.unmodifiableMap()

  unModifiableMap의 구현을 살펴보면 내부에 들어온 Map의 메서드를 호출해주고 put, remove, putAll, clear의 호출은 예외를 발생시켜서 막아놓은 것을 볼 수 있다. 파라미터로 전달된 클래스의 메서드를 사용하게 되니 linkedHashMap의 entrySet(), keySet(), values()의 특성을 그대로 유지할 수 있게 되는 것이다. 일종의 래퍼 클래스(Wrapper Class) 라고 볼 수 있을 거 같다.

 

Map.CopyOf()

 

  첨부된 사진이 많지만 위에서부터 찬찬히 살펴보자.

  1. Map.copyOf()를 호출하면 Map.ofEntries()라는 메서드가 호출된다.
  2. ofEntires()로 전달된 배열의 순서대로 ImmutableCollection.MapN()이라는 구조에 저장된다.
  3. MapN의 iterator는 remaining으로 내부 요소의 개수를, 첫 인덱스인 idx는 특수한 알고리즘을 통해 결정된다.
  4. REVERSE(정/역방향 순회)에 따라 idx를 기준으로 +2나 -2를 해줘 순차적으로 조회하도록 한다.

  MapN에서 들어온 순서대로 저장을 하고 있기 때문에 만약 MapNIterator에서 첫 idx를 0, REVERSE = false라면 원래 LinkedHashMap에 담긴 순서대로 출력될 수 있을 것이다. 그럼 idx와 REVERSE는 어떻게 결정되는 것일까?

아... 랜덤

 

  그래서 Map.copyOf()에 LinkedHashMap을 담았을 때 반환된 Map은 담긴 순서를 기억한다는 LinkedHashMap의 특성을 유지하게 못하게 되는 것이다. 다만 출력할 때 완전히 섞이는 것이 아닌 무언가 순서대로 출력되는 것을 볼 수는 있다.

ImmutableCollections.MapN을 생성
idx = 20, REVERSE = true
idx = 8, REVERSE = false

 

😄 관련해서 찾아보니 이전 기수에서도 동일한 문제가 발생해서 글을 쓰신 분이 계셔서 첨부!!

https://velog.io/@ohzzi/collection-%EB%B3%B5%EC%82%AC-%EC%8B%9C-%EA%B5%AC%ED%98%84-%ED%98%95%ED%83%9C%EB%A5%BC-%EC%9C%A0%EC%A7%80%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%9C%BC%EB%A9%B4-copyOf%EB%A5%BC-%EC%93%B0%EC%A7%80-%EB%A7%90%EC%9E%90