다음 코드의 출력 결과는 무엇일까?
import java.util.*;
class Car {
String model;
public Car(String model) {
this.model = model;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Car)) return false;
Car other = (Car) o;
return model.equals(other.model);
}
// hashCode 생략
}
public class Main {
public static void main(String[] args) {
Map<Car, Integer> map = new HashMap<>();
Car c1 = new Car("Avante");
Car c2 = new Car("Avante");
map.put(c1, 100);
System.out.println(map.get(c2));
}
}
c1, c2는 서로 다른 인스턴스지만 model은 같음equals()는 true가 나올 수 있음hashCode()가 오버라이딩되지 않았음nullhashCode()를 오버라이딩하지 않으면 기본적으로 Object의 주소 기반 해시 사용c1, c2는 다른 해시값을 가지므로 다른 버킷에 저장/탐색equals()가 true여도 버킷 자체가 다르기 때문에 못 찾음import java.util.*;
class User {
String id;
public User(String id) {
this.id = id;
}
}
public class Main {
public static void main(String[] args) {
Map<User, String> map = new HashMap<>();
User u1 = new User("hong");
User u2 = new User("hong");
map.put(u1, "관리자");
System.out.println(map.get(u2)); // 예상: 관리자?
}
}
null
u1과 u2는 값은 같지만 서로 다른 객체hashCode() → 다름equals() → 생략했으니 기본 동작 (주소 비교) → false→ HashMap은 hashCode() 다르면 아예 다른 버킷으로 봄 → u2로 찾을 수 없음
import java.util.*;
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Set<Person> set = new HashSet<>();
set.add(new Person("철수"));
set.add(new Person("철수"));
System.out.println(set.size()); // 몇 개?
}
}
2
HashSet은 내부적으로 HashMap<Object, Object> 사용equals()와 hashCode()가 없으면, 객체 주소 비교로만 판단new Person("철수") 두 개는 서로 다른 객체 → 해시값도 다르고 equals도 false→ 결과적으로 다른 객체로 인식 → 두 번 저장됨
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person p = (Person) o;
return this.name.equals(p.name);
}
@Override
public int hashCode() {
return name.hashCode(); // name이 같으면 같은 해시값
}
}
→ 이렇게 수정하면 set.size()는 1이 나옴
| 상황 | 증상 | 원인 | 해결책 |
|---|---|---|---|
| HashMap에서 key로 찾으면 null | hashCode() 불일치 | equals만 있고 hashCode 누락 | |
| HashMap에서 다른 인스턴스로 get 불가 | equals() 오버라이딩 안 됨 | 둘 다 override 해야 함 | |
| HashSet에서 중복 저장됨 | equals/hashCode 없음 | 같은 값도 다른 객체로 판단 |
// 항상 둘 다 override
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
좋아!
이번엔 equals()와 hashCode()를 둘 다 오버라이딩해서 HashMap과 HashSet에서 정확하게 작동하는 예제를 줄게.
아래 코드는 디버깅하듯 단계별로 어떤 일이 일어나는지 전부 해설해줄게.
import java.util.*;
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// equals 오버라이딩
@Override
public boolean equals(Object o) {
if (this == o) return true; // 같은 객체면 true
if (!(o instanceof Person)) return false;
Person other = (Person) o;
return this.name.equals(other.name) && this.age == other.age;
}
// hashCode 오버라이딩
@Override
public int hashCode() {
return Objects.hash(name, age); // 이름과 나이를 조합해서 해시값 생성
}
}
public class Main {
public static void main(String[] args) {
Map<Person, String> map = new HashMap<>();
Person p1 = new Person("철수", 20);
Person p2 = new Person("철수", 20);
map.put(p1, "개발자");
System.out.println("p1 == p2 ? " + (p1 == p2)); // false
System.out.println("p1.equals(p2) ? " + p1.equals(p2)); // true
System.out.println("p1.hashCode() == p2.hashCode() ? " +
(p1.hashCode() == p2.hashCode())); // true
System.out.println("map.get(p2) → " + map.get(p2)); // ✅ "개발자"
}
}
p1 == p2 ? false
p1.equals(p2) ? true
p1.hashCode() == p2.hashCode() ? true
map.get(p2) → 개발자
p1, p2는 서로 다른 객체이므로 == 비교는 falseequals() 호출p1.equals(p2) → true
hashCode() 호출Objects.hash(name, age)
"철수"의 해시 + 20을 조합해서 하나의 정수값으로 만듦p1, p2는 같은 값을 가지므로 동일한 해시값 → 같은 버킷| 비교 대상 | 결과 | 이유 |
|---|---|---|
p1 == p2 | false | 서로 다른 인스턴스 |
p1.equals(p2) | true | name과 age가 같음 |
p1.hashCode() == p2.hashCode() | true | 같은 필드 값 기반 해시 |
map.get(p2) | "개발자" | p1과 같은 key로 인식됨 |
Set<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println("set size: " + set.size()); // ✅ 1
p2는 p1과 equals/hashCode가 동일 → 중복 저장 안 됨| 상황 | equals() | hashCode() |
|---|---|---|
HashSet.contains() | O | O |
HashMap.get(key) | O | O |
List.contains() | O | X (해시 아님) |
hashCode()만 있고 equals() 없으면: 잘못된 key로 인식하거나 충돌 오작동equals()만 있고 hashCode() 없으면: 동일한 key인데도 map에서 못 찾음이해 잘 됐다면,
원할 경우 직접 구현하는 실전형 객체 해시 문제도 줄 수 있어!
👉 “객체 해시 실전 문제 줘” 라고 말해줘!
방금 배운 걸 100% 내 것으로 만들 수 있어.
아주 좋아!
자바에서 (!(o instanceof Person))은 equals 메서드 안에서 자주 등장하는 핵심적인 문법이야.
초보자들이 처음 보면 헷갈리기 쉬운 부분인데, 지금 완벽하게 이해하고 넘어가자.
!(o instanceof Person) 뜻 해석if (!(o instanceof Person)) return false;
"만약
o가Person클래스의 인스턴스가 아니라면,false를 리턴해라"
즉:
instanceof → 타입 검사 연산자!(...) → 부정 (not)Person p = new Person("철수", 20);
String s = "나는 문자열이야";
p.equals(s); // ??? 문자열인데 사람과 비교한다고?
→ 이런 일이 실제로 일어나면 ClassCastException이나 NullPointerException이 발생할 수 있어.
@Override
public boolean equals(Object o) {
if (this == o) return true; // 자기 자신이면 true
if (!(o instanceof Person)) return false; // Person 타입 아니면 false
Person other = (Person) o; // 타입 확인 후 안전하게 다운캐스팅
return this.name.equals(other.name) && this.age == other.age;
}
==로 주소가 같은지 먼저 확인 → 빠르게 true 반환
instanceof로 타입 체크
o instanceof Person이 아니면 false 반환Person other = (Person) o;name, age 값 비교
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Person)) return false;
Person other = (Person) o;
return this.name.equals(other.name);
}
}
public class Main {
public static void main(String[] args) {
Person p = new Person("철수");
String s = "문자열";
System.out.println(p.equals(s)); // false → 안전하게 작동
}
}
| 구문 | 의미 |
|---|---|
o instanceof Person | o가 Person 타입인지 확인 |
!(o instanceof Person) | o가 Person 타입이 아니면 |
if (!(o instanceof Person)) return false; | 비교 대상이 Person 아니면 더 비교 안 하고 false 반환 |
정확한 질문이야! 아주 핵심을 찔렀어.
결론부터 말하자면:
맞아!
해시 기반 자료구조(
HashMap,HashSet등)를 제대로 사용하려면
반드시equals()와hashCode()를 오버라이딩해야 해.
그리고 그걸 코드상으로 확인하는 방법은:
equals()와 hashCode() 메서드가 클래스 내부에 @Override 되어 있느냐로 판단할 수 있어.| 항목 | 이유 |
|---|---|
hashCode() | → 버킷(배열 인덱스)을 결정하는 해시값 계산 |
equals() | → 해시값이 같을 경우, 정말 같은 객체인지 최종 확인 |
둘 다 없으면 → HashMap에서 키를 못 찾고, Set에서 중복 제거도 안 됨
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
→ 이러면 정상적으로 해시 기반 자료구조에서 작동함
// equals(), hashCode() 없음 → 기본 Object 기능 사용
→ 이러면 문제 생김:
equals()는 주소 비교hashCode()는 메모리 주소 기반 정수즉, 값이 같아도 다른 객체로 취급됨
class User {
String id;
public User(String id) { this.id = id; }
}
User u1 = new User("abc");
User u2 = new User("abc");
System.out.println(u1.equals(u2)); // false
System.out.println(u1.hashCode() == u2.hashCode()); // false (거의 대부분)
class User {
String id;
public User(String id) { this.id = id; }
@Override
public boolean equals(Object o) {
if (!(o instanceof User)) return false;
return this.id.equals(((User) o).id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
User u1 = new User("abc");
User u2 = new User("abc");
System.out.println(u1.equals(u2)); // true
System.out.println(u1.hashCode() == u2.hashCode()); // true
equals()/hashCode()를 오버라이드하면 자동으로 표시됨Ctrl + O 또는 Ctrl + F12 로 메서드 목록 확인 가능| 질문 | 답변 |
|---|---|
| 해시 기반 자료구조에서 equals/hashCode 꼭 필요해? | ✅ 예 (둘 다 필수) |
| 둘 중 하나만 오버라이드 해도 돼? | ❌ 안 됨 (둘 다 같이 해야 정상 작동) |
| 코드에서 오버라이드 여부 확인은 어떻게 해? | @Override 어노테이션 / 메서드 존재 여부로 판단 |