
String을 배우다 보면 “불변이다”, “hashCode를 캐싱한다”, “HashMap의 key로 안전하다” 같은 말들을 연속으로 듣게 된다.
문제는 이 말들이 각각 따로 설명되면 전혀 와닿지 않는다는 것이다.
나 역시
이 글은 String의 불변성 → hashCode 캐싱 → HashMap 설계가 하나의 논리로 어떻게 연결되는지를 정리한 글이다.
먼저 가장 큰 오해부터 정리해야 한다.
hashCode는 HashMap의 기능이 아니다.
hashCode()는 Object 클래스에 정의된 메서드다.
즉, 모든 객체가 태어날 때부터 가지고 있는 성질이다.
class Object {
public int hashCode() { ... }
}
HashMap은:
이 관점을 놓치면 “Map을 쓰니까 hashCode가 필요한 거 아닌가?”라는 오해가 생긴다.
Map에서 hashCode가 사용되는 대상은 항상 key다.
map.put(key, value);
내부 흐름은 단순하다.
key.hashCode() 호출equals()로 최종 비교❗ value의 hashCode는 이 과정에 전혀 관여하지 않는다.
그래서 다음 코드는:
map.get(1).hashCode();
"one"이라는 String 객체의 hashCode를 출력한 것일 뿐이다.이 지점에서 많이 붕 뜬다.
보고 있는 숫자가 Map이 쓰는 값이 아니기 때문이다.
String이나 Integer의 hashCode를 여러 번 출력해보면 항상 같은 값이 나온다.
"apple".hashCode(); // 항상 동일
1.hashCode(); // 항상 동일
이 때문에 “이미 정해진 값 같다”는 느낌을 받기 쉽다.
하지만 정확한 표현은 이거다.
hashCode는 고정된 공식으로 계산되며, 입력이 같으면 결과가 항상 같도록 설계된 값이다.
String의 hashCode() 구현을 보면, 계산 결과를 내부 필드에 저장해 두고 재사용한다.
의미는 단순하다.
한 번 계산한 hashCode를 다시 계산하지 않는다.
왜 이런 최적화가 가능할까?
그래서:
String이 Map의 key로 자주 쓰이기 때문에 이 캐싱은 성능상 필수에 가깝다.
이제 핵심 질문이다.
만약 String이 가변 객체였다면, hashCode 캐싱은 왜 위험해질까?
가정해보자.
String s = "abc";
int h1 = s.hashCode(); // "abc" 기준으로 계산 후 캐싱
이후 문자열이 바뀐다면?
s.setCharAt(0, 'x'); // "xbc" (가변이라고 가정)
이 상태에서 다시 hashCode()를 호출하면:
"abc" 기준 hashCode즉,
Map<String, String> map = new HashMap<>();
map.put(s, "value");
"abc" 기준 hashCode로 버킷 결정결과는?
이건 단순한 버그가 아니라 자료구조의 전제를 깨뜨리는 상태다.
그래서 자바에는 이런 강한 규칙이 있다.
equals가 true면 hashCode도 반드시 같아야 한다.
그리고 이 규칙을 지키기 위해 String은 불변 객체로 설계되었다.
String은:
즉,