자바 소스 코드 분석:Guava 의 불가 변 집합 ImmutableMap 의 소스 코드 분석

11024 단어 ImmutableMapGuava
사례 장면
이러한 장면 을 만난 적 이 있 습 니 다.static 수식 맵 을 정의 할 때 대량의 put()방법 으로 값 을 부 여 했 습 니 다.이와 같 습 니 다.

public static final  Map<String,String> dayMap= new HashMap<>();
static {
    dayMap.put("Monday","      ");
    dayMap.put("Tuesday","      ");
    dayMap.put("Wednesday","      ");
    dayMap.put("Thursday","      ");
    dayMap.put("Sunday","      ");
    ......
}
그 당시 에 저 는 코드 를 더욱 최적화 시 켜 서 더욱 우아 하 게 만 들 수 있 는 지 생각 했 습 니 다.그 다음 에 Google Guava 에 ImmutableMap 이 있 는 것 을 발 견 했 습 니 다.이런 유형 을 통 해 건축 자 모델 과 유사 한 체인 프로 그래 밍 을 실현 할 수 있 고 최 적 화 된 효 과 는 다음 과 같 습 니 다.

public static final  Map<String,String> dayMap = ImmutableMap.<String, String>builder()
    .put("Monday","      ")
    .put("Tuesday","      ")
    .put("Wednesday","      ")
    .put("Thursday","      ")
    .put("Sunday","      ")
    .build();
2.ImmutableMap 소스 코드 분석
그렇다면 이 ImmutableMap 은 어떻게 이런 기능 을 실현 할 수 있 을 까?
Google Guava 공식 튜 토리 얼 에서 Immutable 접두사 의 집합 은 Immutable Set,Immutable Map 등 가 변 집합 으로 정의 되 었 습 니 다.가 변 집합 은 무엇 입 니까?집합 이 만들어 진 후 집합 안의 모든 상 태 는 생명주기 내 에 더 이상 수정 할 수 없고 읽 을 수 밖 에 없다 는 것 이다.
그러면 재 수정 이 가능 한 것 은 무엇 입 니까?Jdk 의 map,list 등 을 만 든 후에 put()또는 add()를 통 해 반복 적 으로 추가 하거나 수정 할 수 있 습 니 다.이것 이 바로 재 수정 이 가능 한 집합 입 니 다.집합 을 수정 할 수 없 는 이상 더 이상 수정 할 수 없 지 않 습 니까?아니,사실 반 사 를 통 해 수정 할 수 있 지만 이 는 가 변 집합 이 존재 하 는 취지 가 아니다.
한 마디 로 가 변 집합 은 스 레 드 가 안전 하고 상수 로 사용 할 수 있다 는 것 이다.
그 다음 에 ImmutableMap 내부 에 들 어가 면 Map 인 터 페 이 스 를 실현 한 것 을 볼 수 있다.HashMap 과 비슷 한 점 은 Map 인 터 페 이 스 는 모두 그들의 기본 클래스 라 고 할 수 있 고 부모 류 인용 이 가리 키 는 하위 대상,즉 위로 전환 할 수 있다 는 것 이다.

public abstract class ImmutableMap<K, V> implements Map<K, V>, Serializable {}
이것 은 추상 적 인 클래스 입 니 다.이렇게 ImmutableMap.builder()를 호출 하려 면 겉 으로 는builder()가 반드시 static 에 의 해 정 의 된 정적 방법 이 라 고 추측 할 수 있 습 니 다.소스 코드 에 들 어가 면 확실히 이와 같 습 니 다.

/**
 * Returns a new builder. The generated builder is equivalent to the builder
 * created by the {@link Builder} constructor.
 */
public static <K, V> Builder<K, V> builder() {
  return new Builder<K, V>();
}
이 방법의 정 의 는 일부 초급 프로그래머 들 에 게 있어 서 매우 이상 하 다 고 느 낄 수 있 지만,사실 이 방법 형식의 본질은 이렇다.

public <T> T method(T t)
이것 은 범 형의 약정 규범 으로 첫 번 째 로 범 형 을 정의 하 는데 현재 방법 에 범 형 변수 유형 이 있 음 을 나타 내 고 T 로 표시 한다.두 번 째 T 는 method 의 반환 유형 을 T 로 표시 합 니 다.
돌 이 켜 보면 이 builder()방법 은 이해 하기 쉽 습 니 다.는 현재 방법 을 나타 내 는 범 형 변 수 를 정의 합 니 다.Builder는 범 형 변 수 를로 되 돌려 주 는 대상 을 표시 합 니 다.
앞에서 정의 한 ImmutableMap.builder()는 이 builder()방법 에서 new Builder()의 대상 을 되 돌려 줍 니 다.이 대상 은 구조 기 를 통 해 ImmutableCollection.Builder.DEPAULT 크기 를 초기 화 합 니 다.INITIAL_CAPACITY 의 배열 entries,이 DEFAULTINITIAL_CAPACITY 의 기본 값 은 4 입 니 다.

public static class Builder<K, V> {
    Comparator<? super V> valueComparator;
    ImmutableMapEntry<K, V>[] entries;
    int size;
    boolean entriesUsed;
   public Builder() {
      this(ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY);
    }
    Builder(int initialCapacity) {
      this.entries = new ImmutableMapEntry[initialCapacity];
      this.size = 0;
      this.entriesUsed = false;
    }
    ......
}
그럼 문제 가 생 겼 습 니 다.이 Immutable MapEntry[]는 어떤 유형의 배열 입 니까?
이 Immutable MapEntry클래스 는 Immutable Entry클래스 를 계승 하 는 것 입 니 다.

class ImmutableMapEntry<K, V> extends ImmutableEntry<K, V> {
  static <K, V> ImmutableMapEntry<K, V>[] createEntryArray(int size) {
    return new ImmutableMapEntry[size];
  }
  ImmutableMapEntry(K key, V value) {
    super(key, value);
    checkEntryNotNull(key, value);
  }
}
한 가지 주의 하 세 요.checkEntry NotNull(key,value)이 검 사 를 했 습 니 다.이 는 저 장 된 key 와 value 값 이 비어 있 지 않 음 을 의미 합 니 다.

static void checkEntryNotNull(Object key, Object value) {
  if (key == null) {
    throw new NullPointerException("null key in entry: null=" + value);
  } else if (value == null) {
    throw new NullPointerException("null value in entry: " + key + "=null");
  }
}
부모 클래스 Immutable Entry클래스 에서 key 와 value 두 개의 일반적인 변 수 를 정의 합 니 다.외부 에서 builder().put(key,value)를 호출 하여 key-value 데 이 터 를 저장 할 때 사실은 key-value 데 이 터 를 Immutable Entry 대상 의 key 와 value 에 저장 합 니 다.

class ImmutableEntry<K, V> extends AbstractMapEntry<K, V> implements Serializable {
  final K key;
  final V value;
  ......
}
Immutable Entry배열 을 언급 하여 key-value 데 이 터 를 저장 하려 면 HashMap 을 언급 해 야 합 니 다.
JDK 1.8 에서 HashMap 은 배열+링크+빨 간 검 은 나무 로 구성 되 어 있 으 며,그 내부 의 배열 은 Node로 정의 되 어 있 으 며,이 Node는 Map.Entry로 이 루어 져 있다.

Immutable MapEntry상단 역시 Entry를 실현 했다.

이 를 통 해 알 수 있 듯 이 ImmutableMap 은 HashMap 과 마찬가지 로 key-value 를 저장 하 는 대상 이 속 한 클래스 는 Entry인 터 페 이 스 를 직접적 이거 나 간접 적 으로 실현 했다.
여기까지 분석 하고 Builder류 소스 코드 를 다시 보면 쉽게 알 수 있 습 니 다.이 Immutable MapEntry[]entries 는 HashMap 의 배열 과 유사 하여 모두 key-value 의 데 이 터 를 저장 하 는 데 사 용 됩 니 다.
다음은 put 의 논리 원 리 를 분석 하 는 것 이다.
앞에서 분석 한 Builder 클래스 는 추상 적 인 ImmutableMap의 내부 정적 클래스 에 속 합 니 다.이 는 ImmutableMap.builder().put("Monday","오늘 영어 수업")을 수행 하 는 본질 이 고 사실은 ImmutableMap.new Builder().put("Monday","오늘 영어 수업")을 수행 하 는 것 과 같 습 니 다.
put 방법의 원본 코드 는 다음 과 같 습 니 다.

public Builder<K, V> put(K key, V value) {
  ensureCapacity(size + 1); 
  ImmutableMapEntry<K, V> entry = entryOf(key, value);
  // don't inline this: we want to fail atomically if key or value is null
  entries[size++] = entry;
  return this;
}
1.첫 번 째 줄 코드 호출 방법 을 살 펴 보면 키-value 대상 이 배열 에 저장 되 었 을 때 넘 칠 가능성 이 있 는 지 판단 하고 넘 칠 경우 배열 을 확대 하 는 역할 을 합 니 다.

private void ensureCapacity(int minCapacity) {
  if (minCapacity > entries.length) {
    entries =
        Arrays.copyOf(
            entries, ImmutableCollection.Builder.expandedCapacity(entries.length, minCapacity));
    entriesUsed = false;
  }
}
두 번 째 줄 Immutable MapEntryentry=entry Of(key,value)는 새로운 Immutable MapEntry 대상 을 만 들 고 구조 기 를 통 해 대상 에 게 할당 하 는 key 와 value 를 초기 화 하 는 것 입 니 다.

static <K, V> ImmutableMapEntry<K, V> entryOf(K key, V value) {
    return new ImmutableMapEntry<K, V>(key, value);
  }
3.세 번 째 줄 코드 entries[size++]=entry 는 새로 추 가 된 Immutable MapEntry 대상 을 배열 의 빈 위치 에 저장 합 니 다.이렇게 put(key,value)캐 시 를 통 해 들 어 온 key-value 값 은 대상 의 형식 으로 배열 에 저 장 됩 니 다.
4.마지막 줄 은 this 를 되 돌려 주 는 것 입 니 다.ImmutableMap 이 체인 프로 그래 밍 을 실현 할 수 있 는 이 유 는 바로 이 this 에 있 습 니 다.
이 this 를 이해 하면 ImmutableMap 디자인 의 정교 함 을 이해 할 수 있 습 니 다.
우리 가 체인 프로 그래 밍 ImmutableMap.builder().put("key 1","value 1").put("key 2","value 2").put("key 2","value 3")를 사용 하여 값 을 부여 할 때 그 내 부 는 내부 정적 클래스 Builder 중의 put()방법 을 반복 적 으로 호출 한 것 입 니 다.그러면 문제 가 생 겼 습 니 다.왜 반복 적 으로 호출 할 수 있 습 니까?
정 답 은 바로 이것 이 되 돌아 오 는 this 입 니 다.그것 이 되 돌아 오 는 것 은 Builder 대상 자체 입 니 다.Builder 대상 은 당연히 put 방법 을 계속 호출 할 수 있 습 니 다.이 반복 적 으로 호출 되 는 과정 에서 entries[size++]만 계속 변화 하고 있 습 니 다.
이것 은 사실은 건축 자 디자인 모델 의 표현 이다.다만 평소에 만 나 는 건축 자 디자인 모델 은 대부분이 대상 의 각 속성 을 유연 하 게 조합 하여 맞 춤 형 대상 을 구성 하 는 것 이다.여 기 는 하나의 배열 저장 상황 을 유연 하 게 맞 추 는 것 이다.
마지막 으로'build()방법'을 실행 하 는 것 입 니 다.

ImmutableMap.<String, String>builder()
    .put("Monday","      ")
    ......
    .build();
이 build()소스 코드 는 매우 복잡 하 게 쓰 여 있 습 니 다.여 기 는 직접적 으로 간단하게 최적화 되 었 습 니 다.아마도 entries 배열 을 Map 인 터 페 이 스 를 실현 하 는 하위 대상 으로 포장 하여 되 돌려 주 는 것 입 니 다.

public ImmutableMap<K, V> build() {
  switch (size) {
    case 0:
      return of();
    case 1:
      return  new SingletonImmutableBiMap<K, V>(k1, v1);
    default:
      return  new RegularImmutableMap<K, V>(entries, table, mask);
  }
}
배열 의 길이 가 1 을 넘 으 면 Singleton Immutable BiMap 이나 Regular Immutable Map 으로 돌아 갈 수 있 습 니 다.둘 다 간접 적 으로 Map 인 터 페 이 스 를 실현 하고 각자 의 클래스 정 의 를 비교 해 볼 수 있 습 니 다.

final class SingletonImmutableBiMap<K, V> extends ImmutableBiMap<K, V> {
  final transient K singleKey;
  final transient V singleValue;
  ......
}

final class RegularImmutableMap<K, V> extends ImmutableMap<K, V> {
  // entries in insertion order
  private final transient Entry<K, V>[] entries;
  // array of linked lists of entries
  private final transient ImmutableMapEntry<K, V>[] table;
  // 'and' with an int to get a table index
  private final transient int mask;
  ......
}
클래스 와 클래스 의 속성 은 모두 final 수정자 로 정의 되 는 공 통 된 특징 이 있 음 을 발견 하 였 습 니 다.이 는 build()방법 을 사용 하여 초기 화 하면 더 이상 바 꿀 수 없다 는 것 을 의미 합 니 다.
이것 이 바로 ImmutableMap 집합 이 변 하지 않 는 진정한 원인 이다.
마지막 으로 또 하나의 문 제 는 ImmutableMap 을 통 해 맵 대상 을 만 든 다음 put 를 통 해 데 이 터 를 삽입 하려 고 할 때 어떤 상황 이 발생 하 느 냐 는 것 이다.
이때 put 방법 을 통 해 호출 할 때,예 를 들 어 위 에서 정 의 된 dayMap 을 예 로 들 면,어떤 방법 에서 dayMap.put("Monday","오늘 영어 수업")을 통 해 맵 데 이 터 를 수정 하거나 추가 하려 고 할 때,여기 서 호출 된 put 는 이미 내부 클래스 Builder()의 put 방법 이 아니 라 ImmutableMap 자체 의 put 방법 입 니 다.이 방법의 소스 코드 는 다음 과 같 습 니 다.

/**
 * Guaranteed to throw an exception and leave the map unmodified.
 *
 * @throws UnsupportedOperationException always
 * @deprecated Unsupported operation.
 */
@CanIgnoreReturnValue
@Deprecated
@Override
public final V put(K k, V v) {
  throw new UnsupportedOperationException();
}
설명 에 따 르 면 map unmodified 는 더 이상 수정 할 수 없습니다.put 를 호출 하여 실행 하면 이상 한 것 만 언급 할 수 있 습 니 다.
UnsupportedOperationException
총결산
이 글 은 여기까지 입 니 다.당신 에 게 도움 을 줄 수 있 기 를 바 랍 니 다.또한 당신 이 우리 의 더 많은 내용 에 관심 을 가 져 주 실 수 있 기 를 바 랍 니 다!

좋은 웹페이지 즐겨찾기