Backend/JAVA

동등성 과 동일성? == 와 equals?

mjeongriver 2025. 2. 5. 22:25

동등성(Equality) vs 동일성(Identity) 개념 정리

동일성 (Identity)

  • 동일성이란 두 객체가 같은 메모리 주소(참조값)를 가리키고 있는지를 의미합니다.
  • 즉, 동일한 객체인지 확인하는 것을 말합니다.
  • Java에서는 == 연산자를 사용하여 동일성을 비교합니다.

동등성 (Equality)

  • 동등성이란 두 객체의 "내용(값)"이 같은지를 비교하는 것을 의미합니다.
  • 즉, 값이 같다면 "같은 객체"로 간주할 수 있도록 하는 개념입니다.
  • Java에서는 equals() 메서드를 사용하여 동등성을 비교합니다.
    • Java에서 equals() 메서드는 기본적으로 ==과 동일한 방식으로 동작한다.하지만 equals()를 오버라이딩하면 "동등성(Equality)"을 비교하도록 변경할 수 있다.
    • 즉, 오버라이딩하지 않으면 equals()는 동일성(Identity) 비교를 수행하며, ==과 같은 결과를 반환한다.
  • equals()를 오버라이딩하면 "값"을 기준으로 객체 비교를 수행할 수 있습니다.

 

== vs equals() 차이점

비교 대상 == (동일성 비교) equals() (동등성 비교)

비교 기준 객체의 참조값(메모리 주소) 비교 객체의 내용(값) 비교
기본 동작 같은 주소를 가리키면 true Object의 기본 구현은 ==과 동일하지만, 오버라이딩하면 값 비교 가능
오버라이딩 가능 여부 ❌ 불가능 (연산자) ✅ 가능 (equals() 메서드 오버라이딩 가능)
사용 예 기본 자료형 비교 또는 객체가 같은 주소를 가리키는지 확인할 때 객체의 값이 같은지 확인할 때

 

동일성과 동등성 예제 코드

동일성 비교 (==)

public class Test {
    public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");

        System.out.println(s1 == s2);       // false (서로 다른 객체)
        System.out.println(s1.equals(s2)); // true (값이 같음)
    }
}

 

출력 결과

false
true

 

결론:

✔ == 연산자는 객체의 메모리 주소를 비교하기 때문에 false

✔ equals()는 객체의 "값"을 비교하기 때문에 true

 

동등성 비교 (equals() 오버라이딩)

📌 equals()를 오버라이딩하면 "값"이 같으면 같은 객체로 판단 가능!

import java.util.Objects;

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }
}

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("홍길동", 30);
        Person p2 = new Person("홍길동", 30);
        Person p3 = p1;  // p3는 p1과 같은 객체 (주소값 같음)

        System.out.println(p1 == p2);    // false (동일성 X)
        System.out.println(p1.equals(p2)); // true (동등성 O)
        System.out.println(p1 == p3);    // true (동일성 O)
    }
}

 

출력 결과

false
true
true

 

결론:

✔ p1 == p2 → 서로 다른 객체이므로 false

✔ p1.equals(p2) → 값이 같아서 true

✔ p1 == p3 → 같은 객체이므로 true

 

hashCode()란?

hashCode()는 객체를 식별하는 정수 값(해시 값)을 반환하는 메서드

Java의 Object 클래스에서 기본적으로 제공하는 메서드

객체를 HashSet, HashMap, HashTable 등에서 빠르게 검색하기 위해 사용됨

기본적으로 Object의 hashCode()는 객체의 메모리 주소를 기반으로 생성됨

📌 Java의 Object 클래스에서 hashCode() 기본 구현

public native int hashCode();  // 기본적으로 객체의 주소값 기반 해시코드 반환

 

hashCode()와의 관계

객체가 같다면 (equals()가 true라면) hashCode()도 같아야 한다.

하지만 hashCode()가 같다고 해서 equals()가 true라는 보장은 없다.

✔ 즉, 같은 객체로 판단(equals()가 true)되면, hashCode()도 같아야 한다.

✔ 그러나 서로 다른 객체라도 같은 hashCode()를 가질 수 있다. (해시 충돌 가능)

import java.util.Objects;

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {  // 해시코드 오버라이딩
        return Objects.hash(name, age);
    }
}

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("홍길동", 30);
        Person p2 = new Person("홍길동", 30);

        System.out.println(p1.equals(p2));  // true
        System.out.println(p1.hashCode());  // 같은 해시코드 반환
        System.out.println(p2.hashCode());
    }
}

 

출력 결과: equals()를 오버라이딩하면 hashCode()도 같아야 한다!

true
12345678  // (예제 해시코드)
12345678

 

hashCode()를 오버라이딩하는 이유

컬렉션(HashSet, HashMap)에서 객체를 효율적으로 저장하고 검색하기 위해 필요

equals()를 오버라이딩하면 hashCode()도 함께 오버라이딩해야 함

그렇지 않으면, HashSet에서 중복 객체가 허용되는 문제 발생 가능

 

📌 예제: equals()만 오버라이딩하면 발생하는 문제

import java.util.HashSet;

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {  // equals 오버라이딩
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }

    // hashCode() 미오버라이딩 → 기본적으로 Object의 hashCode() 사용
}

public class Test {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();

        Person p1 = new Person("홍길동", 30);
        Person p2 = new Person("홍길동", 30);

        set.add(p1);
        set.add(p2);  // 서로 다른 객체로 인식됨

        System.out.println(set.size());  // 2 (중복 저장됨)
    }
}

 

출력 결과: 값이 같아도 hashCode()가 다르면 HashSet에서 다른 객체로 인식됨!

2

 

hashCode()가 같아도 equals()가 다를 수 있음 (해시 충돌)

📌 서로 다른 객체가 같은 hashCode()를 가질 수 있음 (Hash Collision).

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {  // 단순한 해시코드 (충돌 발생 가능)
        return age;  // 나이만 기준으로 해시코드 생성
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }
}

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("홍길동", 30);
        Person p2 = new Person("이순신", 30);  // 다른 객체지만 해시코드 같음

        System.out.println(p1.hashCode());  // 30
        System.out.println(p2.hashCode());  // 30 (해시 충돌 발생)

        System.out.println(p1.equals(p2));  // false (값이 다름)
    }
}

 

출력 결과: hashCode()가 같아도 equals()가 false일 수 있다!

30
30
false