해시 특강 1

류한선·2025년 7월 2일

실기연습-2

목록 보기
70/95

좋아, 그럼 지금부터 너가 요청한 3가지를 각각 자바 실전 코드 예제 + 왜 그런 결과가 나오는지 디버깅 분석 형식으로 해줄게.

✅ ① “equals와 hashCode 오버라이딩 문제 줘”

❓문제:

다음 코드의 출력 결과는 무엇일까?

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()가 오버라이딩되지 않았음

✅ 정답: null

🧠 이유:

  • hashCode()를 오버라이딩하지 않으면 기본적으로 Object의 주소 기반 해시 사용
  • c1, c2는 다른 해시값을 가지므로 다른 버킷에 저장/탐색
  • equals()가 true여도 버킷 자체가 다르기 때문에 못 찾음

✅ ② “HashMap에 넣고 get 안되는 코드 보여줘”

❗️문제 코드

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

✅ 왜 이런가?

  • u1u2는 값은 같지만 서로 다른 객체
  • hashCode() → 다름
  • equals() → 생략했으니 기본 동작 (주소 비교) → false

→ HashMap은 hashCode() 다르면 아예 다른 버킷으로 봄 → u2로 찾을 수 없음


✅ ③ “Set에 객체 넣었는데 중복 저장된 이유 알려줘”

❗️코드 예제:

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로 찾으면 nullhashCode() 불일치equals만 있고 hashCode 누락
HashMap에서 다른 인스턴스로 get 불가equals() 오버라이딩 안 됨둘 다 override 해야 함
HashSet에서 중복 저장됨equals/hashCode 없음같은 값도 다른 객체로 판단

📌 기억할 공식

// 항상 둘 다 override
@Override
public boolean equals(Object o) { ... }

@Override
public int hashCode() { ... }

좋아!
이번엔 equals()hashCode()를 둘 다 오버라이딩해서 HashMap과 HashSet에서 정확하게 작동하는 예제를 줄게.
아래 코드는 디버깅하듯 단계별로 어떤 일이 일어나는지 전부 해설해줄게.


✅ 예제 코드: Person 객체를 key로 사용하는 HashMap

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) → 개발자

🔍 코드 흐름 해설

💡 new Person("철수", 20)

  • p1, p2는 서로 다른 객체이므로 == 비교는 false

🔹 equals() 호출

p1.equals(p2)true
  • name이 같고
  • age도 같으므로 → true

🔹 hashCode() 호출

Objects.hash(name, age)
  • 문자열 "철수"의 해시 + 20을 조합해서 하나의 정수값으로 만듦
  • p1, p2는 같은 값을 가지므로 동일한 해시값 → 같은 버킷

🎯 핵심 요약

비교 대상결과이유
p1 == p2false서로 다른 인스턴스
p1.equals(p2)truename과 age가 같음
p1.hashCode() == p2.hashCode()true같은 필드 값 기반 해시
map.get(p2)"개발자"p1과 같은 key로 인식됨

💡 추가: HashSet도 제대로 작동함

Set<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println("set size: " + set.size()); // ✅ 1
  • p2p1과 equals/hashCode가 동일 → 중복 저장 안 됨

✅ 결론: 언제 둘 다 필요한가?

상황equals()hashCode()
HashSet.contains()OO
HashMap.get(key)OO
List.contains()OX (해시 아님)

❗️잠깐! 이거 틀리면 생기는 문제는?

  • hashCode()만 있고 equals() 없으면: 잘못된 key로 인식하거나 충돌 오작동
  • equals()만 있고 hashCode() 없으면: 동일한 key인데도 map에서 못 찾음
  • 둘 다 없으면: 기본 주소 비교 → 절대로 중복 제거나 비교 불가

이해 잘 됐다면,
원할 경우 직접 구현하는 실전형 객체 해시 문제도 줄 수 있어!

👉 “객체 해시 실전 문제 줘” 라고 말해줘!
방금 배운 걸 100% 내 것으로 만들 수 있어.

아주 좋아!
자바에서 (!(o instanceof Person))equals 메서드 안에서 자주 등장하는 핵심적인 문법이야.
초보자들이 처음 보면 헷갈리기 쉬운 부분인데, 지금 완벽하게 이해하고 넘어가자.


!(o instanceof Person) 뜻 해석

📌 전체 구조:

if (!(o instanceof Person)) return false;

🔍 이건 무슨 뜻이냐면?

"만약 oPerson 클래스의 인스턴스가 아니라면, false를 리턴해라"

즉:

  • instanceof → 타입 검사 연산자
  • !(...) → 부정 (not)
  • 전체 의미는: 비교 대상이 Person 타입이 아니면, 더 비교할 필요도 없다

🧠 왜 equals에서 이걸 체크해야 할까?

예제:

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;
}

✅ 작동 순서 정리

  1. ==로 주소가 같은지 먼저 확인 → 빠르게 true 반환

  2. instanceof로 타입 체크

    • o instanceof Person이 아니면 false 반환
    • 안전하게 다운캐스팅 가능할 때만 Person other = (Person) o;
  3. 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 PersonoPerson 타입인지 확인
!(o instanceof Person)oPerson 타입이 아니면
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

🔍 IDE로 빠르게 확인하는 팁

  • 이클립스, 인텔리제이 등에서는 클래스 내부에 equals()/hashCode()를 오버라이드하면 자동으로 표시됨
  • Ctrl + O 또는 Ctrl + F12 로 메서드 목록 확인 가능

✅ 정리 공식

질문답변
해시 기반 자료구조에서 equals/hashCode 꼭 필요해?✅ 예 (둘 다 필수)
둘 중 하나만 오버라이드 해도 돼?❌ 안 됨 (둘 다 같이 해야 정상 작동)
코드에서 오버라이드 여부 확인은 어떻게 해?@Override 어노테이션 / 메서드 존재 여부로 판단

0개의 댓글