동일성과 동등성 (feat. equals와 hashCode)

심야·2022년 9월 24일
0

웹 개발

목록 보기
16/47

동일성과 동등성

모든 스펙이 동일한 노트북 두 개가 있다고 가정하면 두 개의 노트북은 같은 노트북일까? 같다고 할 수 있고 다르다고도 할 수 있다.

두 개의 노트북은 동일하지 않다.

가격, 출시 날짜, 상세 스펙 등 모든 것이 동일하지만 두 개의 노트북은 전자기기를 고유하게 식별하는 MAC주소가 다르다. 따라서 두 노트북은 동일하지 않다.

두 개의 노트북은 동등하다.

가격, 출시 날짜, 상세 스펙과 모델명이 같기 때문에 두 노트북은 같다 즉, 동등하다고 부를 수 있다.

동일성(Identity)

동일성은 ==비교로 객체의 메모리 내 주소값이 같은지 식별한다.
위에서 설명한 MAC 주소는 전자기기 고유의 값으로 두 기기가 완전히 동일한 것인지 비교한다.

동등성(Equality)

동등성은 equals 메소드로 논리적으로 같은 지위를 지녔는지 확인한다.
두 개의 객체가 같은 정보를 갖고 있다면 논리적으로 같은 지위를 지녔으므로 두 객체는 동등하다고 표현한다.

CODE

아래 코드를 보면 두 객체는 inmo라는 같은 값을 가지지만 개별적으로 객체가 생성되어 두 객체는 주소값이 다르다. ==비교로 두 객체가 동일한지 검사하면 member and member2 is different가 출력된다. 따라서 두 객체는 동일하지 않다.

public void equalMethod() {
        Member member = new Member("inmo");
        Member member2 = new Member("inmo");

        if (member == member2) {
            System.out.println("member and member2 is same");
        } else {
            System.out.println("member and member2 is different");
        }
    }

equals

두 객체가 동등한지 검사하려면 equals메소드를 사용해야 한다. equals 메소드는 Object 클래스의 메소드이다. 하지만 overriding하지 않은 equals 메소드는 ==비교로 비교하기 때문에 member and member2 is different가 출력된다.

public void equalMethod2() {
        Member member = new Member("inmo");
        Member member2 = new Member("inmo");

        if (member.equals(member2)) {
            System.out.println("member and member2 is same");
        } else {
            System.out.println("member and member2 is different");
        }
    }
  • Object의 equals method
    public boolean equals(Object obj) {
            return (this == obj);
        }

equals methodoverriding 해 다시 비교하겠다.

@Override
public boolean equals(Object obj) {
        if (this == obj)
            return true; // 주소가 같으므로 true
        if (obj == null)
            return false; // obj가 null이므로 talse
        if (getClass() != obj.getClass())
            return false; // class 종류가 다르므로 false
        Member other = (Member) obj; // 같은 클래스이므로 형 변환 실패
        if (name == null) { // name이 null 이면
            if (other.name != null) // 비교 대상의 name이 null이 아니면 false
                return false;
        } else if (!name.equals(other.name)) // 두 개의 anme이 다르면 false
            return false;
        return true;
        // 모든 조건을 거쳐서 false를 리턴하지 않은 객체는 값은 값 즉, 동등성을 가지고 있다 판단해 true를 .
    }

member and member2 is same이 출력된다. equals method를 재정의 했기 때문에 같은 name을 가진 두 객체는 동등하다고 표현할 수 있다.

hashCode()

hashCode() 메소드는 기본적으로 객체의 메모리 주소를 16진수로 리턴하며 두 객체가 같은 객체인지 확인한다. 만약 어떤 두 개의 객체가 서로 동일하다면, hashCode() 값은 무조건 동일해야만 한다. 따라서 equals() 메소드를 override하면, hashCode() 메소드도 override 해서 동일한 결과가 나오도록 만들어야만 한다.

자바 API 문서에서는 이 메소드를 Overriding할 때에 다음 조건을 따라야 한다고 명시하고 있다.

  • 자바 애플리케이션이 수행되는 동안에 어떤 객체에 대해서 이 메소드가 호출될 때에는 항상 동일한 int 값을 리턴해 주어야 한다. 하지만 자바를 실행할 때마다 같은 값이어야 할 필요는 전혀 없다.

  • 어떤 두 개의 객체에 대하여 equals() 메소드를 사용해 비교한 결과가 true일 경우에, 두 객체의 hashCode() 메소드를 호출하면 동일한 int값을 리턴해야만 한다.

  • 두 객체를 equals() 메소드를 사용해 비교한 결과 false를 리턴했다고 해서, hashCode() 메소드를 호출한 int 값이 무조건 달라야 할 필요는 없다. 하지만 이 경우에 서로 다른 int 값을 제공하면 hashtable 성능을 향상시키는 데 도움이 된다.

public native int hashCode();

hash

해싱은 해시 함수를 사용해 가변 크기 입력 값에서 고정된 크기 출력 값을 생성하는 과정을 의미한다. 해싱으로 얻어 온 값을 해시 코드라고 한다.

equals만 재정의할 경우

아래 코드에 따르면 people1과 people2 객체는 논리적으로 같은 객체로 판단된다.

public class Main {
    public static void main(String[] args) throws IOException {
        Person people1 = new Person("inmo");
        Person people2 = new Person("inmo");
        System.out.println(people1.equals(people2)); // true
    }
}

class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

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

}

이번에는 중복 값을 허용하지 않는 Set으로 로직을 바꿨다.

public static void main(String[] args) throws IOException {
        Set<Person> people = new HashSet<>();
        people.add(new Person("inmo"));
        people.add(new Person("inmo"));

        System.out.println(people.size()); // 2

    }

equals메소드를 오버라이딩 했고 두 Person 객체의 이름이 같아 논리적으로 같은 객체라 판단해 HastSet Size가 1이 나올 것 같지만 2가 출력된다.

hashCode()는 왜 함께 오버라이드 해야할까?

hashCode를 equals와 함께 재정의하지 않으면 두 객체는 논리적으로 같은 객체라 판단하지 않는다. 도대체 왜 그럴까? 정확히 표현하자면 hash값을 사용하는 Collection(HashSet, HashMap, HashTable)을 사용할 때 문제가 발생한다. hash 값을 사용하는 Collection(HashSet, HashMap, HashTable)은 객체가 논리적으로 같은지 비교할 때 아래 과정을 거친다.

hashCode 메소드 리턴 값이 우선 일치하고 equals 메소드 리턴 값이 true여야 논리적으로 같은 객체라 판단한다.

hashSet에 Person객체를 추가할 때도 hashCode 메소드로 중복 여부를 판단하고 hashSet에 추가되었다. 단지 hashCode 메소드가 재정의 되어있지 않아 Object 클래스의 hashCode 메소드가 사용되었을 뿐이다.

Object 클래스의 hashCode 메소드는 객체의 고유한 주소 값을 int값으로 변환하기 때문에 객체마다 다른 값을 리턴한다. 따라서 두 개의 Person 객체는 equals로 비교 하기 전에 hashCode 메소드의 리턴 값이 서로 달라 다른 객체로 판단되었다.

hashCode 재정의

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

앞서 말했듯이 자바 API 문서에서는 이 메소드를 Overriding할 때에 다음 조건을 따라야 한다고 명시하고 있다.

어떤 두 개의 객체에 대하여 equals() 메소드를 사용해 비교한 결과가 true일 경우에, 두 객체의 hashcode() 메소드를 호출하면 동일한 int값을 리턴해야만 한다.

이제는 주소값이 달라도 "두 객체가 같다"의 기준이 될 name 필드 값이 동일하면 hashCode 리턴값이 동일하도록 재정의 하였다.

이제 hashCode(), equals 메소드도 오버라이딩 했으니 결과를 확인해보자. 예상이 맞다면 두 객체의 hashCode는 동일하고 equals 리턴 값은 true이며 size는 1일 것이다.

hashCode, equals Overriding 결과

아래 코드를 보면 예상대로 equals 리턴 값은 true, size는 1, 두 객체의 hashCode는 동일하다.

    public static void main(String[] args) throws IOException {
        Set<Person> people = new HashSet<>();
        Person p1 = new Person("inmo");
        Person p2 = new Person("inmo");
        people.add(p1);
        people.add(p2);

        System.out.println(p1.equals(p2)); // true
        System.out.println(people.size()); // 1
        System.out.println(p1.hashCode()); // 3237286
        System.out.println(p2.hashCode()); // 3237286

    }

출처

  1. 자바의 신
  2. https://steady-coding.tistory.com/534
  3. https://hudi.blog/identity-vs-equality/
  4. https://steady-coding.tistory.com/604
  5. https://tecoble.techcourse.co.kr/post/2020-07-29-equals-and-hashCode/
  6. https://creampuffy.tistory.com/140#%F-%-F%A-%--%--hashCode--%--%EB%-F%--%--%ED%--%A-%EA%BB%--%--%EC%--%A-%EB%B-%--%EB%-D%BC%EC%-D%B-%EB%--%-C%--%ED%--%B-%EC%--%BC%--%ED%--%--%EB%-A%--%--%EC%-D%B-%EC%-C%A-%EB%-A%--%-F
profile
하루하루 성실하게, 인생 전체는 되는대로.

0개의 댓글