프로그래밍 언어/JAVA

[JAVA] Object 클래스의 equals()와 hashCode() 재정의

s워니얌 2022. 10. 15. 01:15

 

자바 면접 질문에 equals와 hashcode의 차이점에 대한 문항이 있었다. 그때 hashCode에 대해 글로만 읽어봤었는데 자바 책 공부를 하다 equals와 hashCode를 목적에 맞게 같이 오버라이딩하는 것을 보고 다시 정확히 정리하기 위해 포스팅을 작성한다.

 

 

 

📑 Object 클래스의 메서드 - equals()

 

매개변수로 객체의 참조변수를 받아서 비교하여 그 결과를 boolean 값으로 알려주는 역할을 한다.

 

public boolean equals(Object obj) {
  return (this==obj);
}

 

위의 코드는 Object 클래스의 정의되어 있는 equals 메서드의 실제 내용이다. 위 코드에서 알 수 있듯이 두 객체의 같고 다름을 참조변수의 값으로 판단한다. 그렇기 때문에 서로 다른 두 객체를 equals 메서드로 비교하면 항상 false를 얻게 된다. 

 

 


 

 

참고로) ==와 equals()의 비교에선 ( ==는 call by reference로 주소의 값을 비교 즉 실제 값이 아닌 자료의 위치값을 비교하는 것, equals()는 call by value로 값을 비교 즉) 문자열 구성이 같은지 확인 ) 이렇게 알고있었기에 착각했다. 여기선 Object 클래스의 메서드 equals()이니 개념 잡기! 

 

★ 아래에서 사용한 str.equals(str3)는 string 클래스의 equals이다. 

 

package part4.collection;

import java.sql.SQLOutput;

public class Equals {

    public static void main(String[] args) {

        String str = "JAVA";
        String str2 = "JAVA";
        String str3 = new String("JAVA");

        System.out.println(str==str2); //true
        System.out.println(str==str3); //false
        System.out.println(str.equals(str3)); //true


    }


}

 

 

 

 


 

 

package part_9;

import com.sun.jdi.Value;

public class Ex9_1_equals {
    public static void main(String[] args) {
        //서로 다른 객체
        Value1 v1 = new Value1(10);
        Value1 v2 = new Value1(10);

        if(v1.equals(v2))
            System.out.println("v1과 v2는 같다.");
        else
            System.out.println("v1과 v2는 다르다.");

        String s = "ss";
        String s1 = "ss";

        System.out.println(s==s1);
        System.out.println(s.equals(s1));

        String s3 = new String("DD");
        String s4 = new String("DD");

        System.out.println(s3==s4);
        System.out.println(s3.equals(s4));

    }
}

class Value1{
    int value;

    Value1(int value){
        this.value = value;
    }
}

Ex9_1_equals 의 출력 값

 

value라는 멤버변수를 갖는 Value 클래스를 정의하고, 두 개의 Value 클래스 인스턴스를 생성한 다음 equals 메서드를 이용해 두 인스턴스를 비교했다. equals() 메서드는 주소값을 비교하기 때문에 값이 10으로 같을지라도 참조하고 있는 주소값이 다르기에 false, v1과 v2는 다르다는 결과가 출력된다. 

 

 

🔨 그럼 어떻게 해야 Object 클래스의 equals() 메서드를 통해 같은 값으로 나타낼 수 있을까??

 

 

 

📑 equals()의 오버라이딩

 

바로 equals()의 오버라이딩이 필요하다.  Object 클래스로부터 상속받은 equals 메서드는 결국 두 개의 참조변수가 같은 객체를 참조하고 있는지, 두 참조변수에 저장된 값(주소값)이 같은지를 판단하는 기능밖에 할 수 없다는 걸 알 수 있다. 그럼 equals 메서드로 Value 인스턴스가 가지고 있는 value 값을 비교하기 위해선 메서드 오버라이딩을 통해 주소가 아닌 객체에 저장된 내용을 비교하게하면 된다.

 

 

package part_9;

public class Ex9_2_equals2 {

    public static void main(String[] args) {
        Person p1 = new Person(8011L);
        Person p2 = new Person(8011L);
        
        if(p1.equals(p2))
            System.out.println("p1과 p2 같음");
        else
            System.out.println("p1과 p2 다름");
    }

}

class Person{
    long id;

    public boolean equals(Object obj){
        if(obj instanceof Person)
            //obj가 Object 타입이므로 id값 참조 위해 Person 타입으로 형변환.
            //주소가 아닌 값을 비교해서 같으면 true
            return id==((Person)obj).id;
        else
            //타입이 Person이 아님 비교할 필요도 없다.
            return false;
    }

    public Person(long id) {
        this.id = id;
    }
}

 

 

이렇게 오버라이딩을 했을 때 결과는 p1과 p2가 같다라는 값이 출력된다.  

 

 

그렇다면 다음 예시를 살펴보기 위해 위의 Ex9_2_equals 클래스에서 메인 메서드에 아래와 같은 코드를 추가해보자. 

 

List<Person> persons = new ArrayList<>();
persons.add(new Person(8011L));
persons.add(new Person(8011L));
System.out.println(persons.size());

 

list는 중복을 허용하니까 값이 2개가 나올 것이다. 그렇다면 Collection에 중복을 허용하지 않는 Set에 값을 저장해보자. 그렇다면 우리가 equals() 메서드를 오버라이딩 했으니 1개만 나오겠다고 예상할 수 있다.

 

그런데!!!! 값이 2가 출력되었다. 여기서 hashCode를 생각해봐야한다.

 

 

 

📑 hashCode()의 오버라이딩

 

Object 클래스의 hashCode()는 해싱 기법에 사용되는 '해시함수'를 구현한 것이다. 해싱은 데이터관리 기법 중 하나로 다량의 데이터를 저장하고 검색하는데 유용하다. 해시함수는 찾고자 하는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드(hash code)를 반환한다.

 

일반적으로 해시코드가 같은 두 객체가 존재하는 것이 가능하지만, Object 클래스에 정의된 hashCode 메서드는 객체의 주소값을 이용해 해시코드를 반환하기 때문에 다른 두 객체는 결코 같은 해시코드를 가질 수 없다. 

 

 

그런데 hash 값을 사용하는 Collection(HashMap, HashSet, HashTable ,, )은 객체가 논리적으로 같은지 비교할 때 위의 과정을 거친다. 일단 hashCode()의 리턴값에 따라 equals()를 호출할지 다른 객체라고 반환해버릴지 판단하는데, hashCode값이 일치해야 우리가 재정의한 equals()로 값을 비교할 수 있는 것이다. 따라서 hashCode도 오버라이딩해줘야 한다. 

 

 

 

class Person{
    long id;

    @Override
    public boolean equals(Object obj){
        if(obj instanceof Person)
            //obj가 Object 타입이므로 id값 참조 위해 Person 타입으로 형변환.
            //주소가 아닌 값을 비교해서 같으면 true
            return id==((Person)obj).id;
        else
            //타입이 Person이 아님 비교할 필요도 없다.
            return false;
    }

    @Override
    public int hashCode(){
        //Objects.hash() : 매개 값으로 주어진 값들을 이용해서 해시 코드를 생성하는 역할
        //동일한 필드값을 가지는 객체는 동일한 해시코드를 가질 수 있다.
        return Objects.hash(id);
    }

    public Person(long id) {
        this.id = id;
    }
}

 

 

따라서 hashCode()에 매개변수, 즉) 인자로 들어오는 값이 같으면 같은 hashCode를 만들도록 재정의 했더니 set에 두개의 다른 객체를 add했을 때 오버라이딩을 통해 하나만 저장되는 걸 확인할 수 있다.

 

 

 

반응형