자바 소스 코드 분석:Guava 의 불가 변 집합 ImmutableMap 의 소스 코드 분석
이러한 장면 을 만난 적 이 있 습 니 다.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.
/**
* 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()방법 은 이해 하기 쉽 습 니 다.
앞에서 정의 한 ImmutableMap.
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
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
class ImmutableEntry<K, V> extends AbstractMapEntry<K, V> implements Serializable {
final K key;
final V value;
......
}
Immutable EntryJDK 1.8 에서 HashMap 은 배열+링크+빨 간 검 은 나무 로 구성 되 어 있 으 며,그 내부 의 배열 은 Node
Immutable MapEntry
이 를 통 해 알 수 있 듯 이 ImmutableMap 은 HashMap 과 마찬가지 로 key-value 를 저장 하 는 대상 이 속 한 클래스 는 Entry
여기까지 분석 하고 Builder
다음은 put 의 논리 원 리 를 분석 하 는 것 이다.
앞에서 분석 한 Builder 클래스 는 추상 적 인 ImmutableMap
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 MapEntry
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.
정 답 은 바로 이것 이 되 돌아 오 는 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
/**
* 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
총결산
이 글 은 여기까지 입 니 다.당신 에 게 도움 을 줄 수 있 기 를 바 랍 니 다.또한 당신 이 우리 의 더 많은 내용 에 관심 을 가 져 주 실 수 있 기 를 바 랍 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
[ts] useState 선언시 제네릭+배열 타입은 어떻게 해야 할까?오늘도 하나 배운다. useState에 배열을 선언해서 쓰고 싶은데 보통 javascript에서는 []만 넣으면 알아서 만들어 주지만, 난 우아하게 코딩 하기 위해서 ts를 쓰기 때문에 문법 오류 나는 꼴을 무시할 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.