💻 equals와 hashCode: JavaScript에서의 객체 비교와 해시
안녕하세요! 오늘은 Java의 equals와 hashCode 메서드와 비슷한 개념을 JavaScript 세계에서 어떻게 적용할 수 있는지 알아보겠습니다. 비록 JavaScript에는 이 메서드들이 명시적으로 존재하지 않지만, 같은 문제와 해결책이 존재합니다! 😊
🤔 문제 상황: 객체는 언제 '같다'고 할 수 있을까요?
JavaScript에서 객체를 비교할 때 흔히 겪는 문제를 살펴봅시다:
const user1 = { email: 'user@example.com', role: 'admin' };
const user2 = { email: 'user@example.com', role: 'admin' };
console.log(user1 === user2); // 결과: false 😱
두 객체는 같은 정보를 가지고 있지만, JavaScript는 이들을 다른 객체로 인식합니다! 이유는 === 연산자가 객체의 내용이 아닌 참조(메모리 주소)를 비교하기 때문입니다.
🔍 Java의 equals와 hashCode가 필요한 이유
자바에서는 두 메서드를 함께 재정의해야 하는 이유가 있습니다:
- equals: 두 객체의 논리적 동등성을 판단 (내용이 같은지)
- hashCode: 객체를 해시 기반 컬렉션(HashMap, HashSet 등)에서 효율적으로 찾기 위한 정수값
❗중요❗: 만약 두 객체가 equals로 같다고 판단되면, 반드시 hashCode도 같은 값을 반환해야 합니다!
🧩 JavaScript에서의 객체 비교와 해시 테이블
JavaScript도 해시 테이블을 사용합니다. Map과 Set이 바로 그것이죠!
하지만 Java와 달리 별도의 메서드를 재정의하는 방식이 아닙니다.
아래 예제를 봅시다
const subscribe1 = { email: 'team.maeilmail@gmail.com', category: 'backend' };
const subscribe2 = { email: 'team.maeilmail@gmail.com', category: 'backend' };
const subscribes = new Set([subscribe1, subscribe2]);
console.log(subscribes.size); // 결과: 2 (두 객체가 다르다고 판단! 😱)
위의 Java 예제와 같은 문제가 발생했습니다! 내용은 같지만 서로 다른 객체로 취급됩니다.
🛠 JavaScript에서의 해결책
1️⃣ 객체 동등성 비교 함수 만들기 (equals 역할)
function isEqual(obj1, obj2) {
return obj1.email === obj2.email && obj1.category === obj2.category;
}
console.log(isEqual(subscribe1, subscribe2)); // true
2️⃣ 객체의 해시 코드 생성 함수 (hashCode 역할)
function hashCode(obj) {
return `${obj.email}:${obj.category}`; // 고유한 문자열 생성
}
3️⃣ 커스텀 Map 구현하기
실제 해시 테이블처럼 작동하는 커스텀 컬렉션을 만들어 봅시다:
class CustomMap {
constructor() {
this.map = {};
}
set(key, value) {
const hash = hashCode(key); // hashCode 함수 사용
this.map[hash] = { key, value };
}
get(key) {
const hash = hashCode(key);
return this.map[hash]?.value;
}
has(key) {
const hash = hashCode(key);
return this.map[hash] !== undefined;
}
}
이제 사용해 봅시다
const subscribeMap = new CustomMap();
subscribeMap.set(subscribe1, "구독 정보 1");
console.log(subscribeMap.has(subscribe2)); // true! 🎉
console.log(subscribeMap.get(subscribe2)); // "구독 정보 1"
성공입니다! 내용이 같은 두 객체를 같은 것으로 처리했습니다.
💡 실제 상황에서의 해결책
프로덕션 환경에서는 더 견고한 솔루션이 필요합니다. 몇 가지 방법을 소개합니다:
1. 객체 대신 문자열 키 사용하기
const subscribeMap = new Map();
const key1 = `${subscribe1.email}:${subscribe1.category}`;
const key2 = `${subscribe2.email}:${subscribe2.category}`;
subscribeMap.set(key1, subscribe1);
console.log(subscribeMap.has(key2)); // true
2. JSON 문자열 변환 활용하기
const subscribeSet = new Set();
subscribeSet.add(JSON.stringify(subscribe1));
console.log(subscribeSet.has(JSON.stringify(subscribe2))); // true
3. 라이브러리 활용하기 (예: Lodash)
const _ = require('lodash');
console.log(_.isEqual(subscribe1, subscribe2)); // true
🧙♂️ 심화: Map과 WeakMap의 차이
JavaScript의 Map은 객체를 키로 사용할 때 참조 동등성을 사용합니다:
const map = new Map();
map.set(subscribe1, "값");
console.log(map.has(subscribe2)); // false (다른 객체로 인식)
WeakMap도 마찬가지지만, 가비지 컬렉션 처리 방식이 다릅니다:
- Map: 키로 사용된 객체에 대한 강한 참조 유지
- WeakMap: 키에 대한 약한 참조만 유지 (메모리 관리에 좋음)
🎯 결론
Java의 equals와 hashCode는 객체의 동등성과 해시 기반 컬렉션에서의 올바른 작동을 위해 필수적입니다. JavaScript에서는 이 메서드들이 명시적으로 존재하지 않지만, 같은 문제가 존재하고 비슷한 해결책이 필요합니다.
키 포인트
- ✅ 객체를 해시 테이블 키로 사용할 때는 내용 기반 해시 값이 필요합니다
- ✅ 내용 비교와 해시 생성이 일관되게 동작해야 합니다
- ✅ JavaScript에서는 커스텀 함수나 문자열 변환으로 이 문제를 해결할 수 있습니다
다음에는 JavaScript의 Symbol.toPrimitive와 같은 고급 기능을 활용한 객체 비교 방법에 대해 다뤄보겠습니다! 질문이나 의견은 댓글로 남겨주세요! 💬
equals와 hashCode는 왜 함께 재정의해야 할까요?
equals와 hashCode 메서드는 객체의 동등성 비교와 해시값 생성을 위해서 사용할 수 있습니다. 하지만, 함께 재정의하지 않는다면 예상치 못한 결과를 만들 수 있습니다. 가령, 해시값을 사용하는 자료구조(HashSet, HashMap..)을 사용할 때 문제가 발생할 수 있습니다.
class EqualsHashCodeTest {
@Test
@DisplayName("equals만 정의하면 HashSet이 제대로 동작하지 않는다.")
void test() {
// 아래 2개는 같은 구독자
Subscribe subscribe1 = new Subscribe("team.maeilmail@gmail.com", "backend");
Subscribe subscribe2 = new Subscribe("team.maeilmail@gmail.com", "backend");
HashSet<Subscribe> subscribes = new HashSet<>(List.of(subscribe1, subscribe2));
// 결과는 1개여야하는데..? 2개가 나온다.
System.out.println(subscribes.size());
}
class Subscribe {
private final String email;
private final String category;
public Subscribe(String email, String category) {
this.email = email;
this.category = category;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Subscribe subscribe = (Subscribe) o;
return Objects.equals(email, subscribe.email) && Objects.equals(category, subscribe.category);
}
}
}
왜 이런 현상이 발생하나요? 🤔
해시값을 사용하는 자료구조는 hashCode 메서드의 반환값을 사용하는데요. hashCode 메서드의 반환 값이 일치한 이후 equals 메서드의 반환값 참일 때만 논리적으로 같은 객체라고 판단합니다. 위 예제에서 Subscribe 클래스는 hashCode 메서드를 재정의하지 않았기 때문에 Object 클래스의 기본 hashCode 메서드를 사용합니다. Object 클래스의 기본 hashCode 메서드는 객체의 고유한 주소를 사용하기 때문에 객체마다 다른 값을 반환합니다. 따라서 2개의 Subscribe 객체는 다른 객체로 판단되었고 HashSet에서 중복 처리가 되지 않았습니다.
'1일 1CS(Computer Science)' 카테고리의 다른 글
동일성과 동등성에 대해 설명해주세요 (0) | 2025.04.09 |
---|---|
자바스크립트 호이스팅에 대해서 설명해주세요. (0) | 2025.04.09 |
리액트의 Render Phase와 Commit Phase (0) | 2025.04.08 |
리액트의 Strict Mode에 대해서 설명해주세요. (0) | 2025.04.07 |
인터넷 창에 www.google.com를 입력하면 무슨 일이 일어나는지 설명해주세요. (0) | 2025.04.07 |