ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] Object 클래스의 equals()와 hashCode() 재정의
    프로그래밍 언어/JAVA 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했을 때 오버라이딩을 통해 하나만 저장되는 걸 확인할 수 있다.

     

     

     

    반응형

    댓글

Designed by Tistory.