Java

[Java] equals()와 hascode() 메서드

수수한개발자 2022. 7. 9.
728x90

equals() 및 hashCode() 메서드는 Java 객체의 부모 객체인 Object 클래스에 정의되어 있습니다.

그렇기 때문에 모든  Java의 객체는 Object 클래스에 정의된 equlas() 와 hashCode 메서드를 상속받고 있습니다.

 

equals() 란?

boolean equals(Object obj)로 정의된 equals 메소드는 2개의 객체가 동일한지 검사하기 위해 사용된다.

equals() 가 구현된 방법은 2개의 객체가 참조한느 것이 동일한지를 확인하는 것이다.

2개의 객체가 가리키는 곳이 동일한 메모리 주소일 경우에만 동일한 객체가 된다.

public boolean equals(Object o) {
    if (this == o) return true;
}

 

 

hashCode() 란?

int hashCode()로 정의된 hashCode 메소드는 실행중에 객체의 유일한 integer값을 반환합니다.

Object 클래스에서는 heap에 저장된 객체의 메모리 주소를 반환하도록 되어있습니다.

 

public native int hashCode();

여기서 native 키워드는 메소드가 JNI(Java Native Interface)라는 native code를 이용해 구현되었음을 의미합니다.

native는 메소드에만 적용가능한 제어자로, C or C++ 등 Java가 아닌 언어로 구현된 부분을 JNI를 통해 Java에서 이용하고자 할 때 사용된다. 우리같은 일반 개발자는 어디에서도 사용할 수 없습니다.

hashCode는 HashTable과 같은 자료구조를 사용할 때 데이터가 저장되는 위치를 결정하기 위해 사용됩니다.

 

equals와 hashCode의 관계

동일한 객체는 동일한 메모리 주소를 갖는다는 것을 의미하므로, 동일한 객체는 동일한 해시코드를 가져야 한다. 그렇기 때문에 만약 우리가 equals() 메소드를 오버라이드 한다면, hashCode() 메소드도 오버라이드 되어야 한다.

이러한 equals와 hashCode의 관계를 정의하면 다음과 같다.

  • Java 프로그램을 실행하는 동안 equals에 사용된 정보가 수정되지 않았다면, hashCode는 항상 동일한 정수값을 반환해야 한다. (Java의 프로그램을 실행할 때 마다 달라지는 것은 상관이 없다.)
  • 두 객체가 equals()에 의해 동일하다면, 두 객체의 hashCode() 값도 일치해야 한다.
  • 두 객체가 equals()에 의해 동일하지 않다면, 두 객체의 hashCode() 값은 일치하지 않아도 된다.

즉, obj1.equals(obj2) == True 이면 hashCode(obj1) == hashCode(obj2) 이여야하지만 hashCode(obj1) == hashCode(obj2) 라고 obj1.equals(obj2) == True일 필요는 없다.

하지만 우리는 다른 객체에 대해 동일한 hashCode를 생성한다면 hashTable을 생성하는데 불이익을 받을 수 있음을 인지하고 있어야 한다.

 

equals() Override의 필요성

아래와 같은 Article 클래스가 있다. Article은 id를 고유값으로 가집니다.

public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Setter
    @Column(nullable = false)
    private String title;  // 제목

    @Setter
    @Column(nullable = false, length = 10000)
    private String content;  // 내용

    @Setter
    private String hashtag; // 해시태그

    @OrderBy("id")
    @OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
    @ToString.Exclude
    private final Set<ArticleComment> articleComments = new LinkedHashSet<>();//댓글
    
    }

 

만약 아래와 같이 동일한 id 값을 가지는 2개의 Article을 서로 다른 처리 과정에 의해 얻었다고 하면, 2개의 Article은 동일한 id를 갖기 때문에 equals 연산을 한다면 true를 반환해야 합니다. 하지만 아래의 예제는 false를 반환합니다. 

왜냐하면 재정의 하지 않은 equals() 메서드는 모든 필에 대해 검증하기 때문입니다.

public class ArticleTest {
    public static void main(String[] args) {
        Article a1 = new Article();
        Article a2 = new Article();
 
        a1.setId(100);
        a2.setId(100);
 
        System.out.println(a1.equals(a2));  //false
    }
}

 

이러한 문제를 해결하기 위해 Article 클래스에 다음과 같은 equals() 메서드를 오버라이드 해야합니다.

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Article article = (Article) o;
        return getId() != null && getId().equals(article.getId());
    }

여기서 필드에있는 id를 바로사용하지 않고 왜 getId를 하는지에 대해서는 뒤에서 설명하겠습니다.

이렇게하면 equals에 대한 문제는 해결된 것 처럼 보입니다. 하지만 Article을 Set 과 같은 자료구조 인터페이스에 저장하려고 하면 다른 문제가 생깁니다.

 

hashCode() Override의 필요성

앞에서 말한대로 HashTable이나 HashSet, HashMap과 같은 자료구조는 자료를 저장하기 위한 위치를 선택하기 위해 hashCode를 이용합니다. 그래서 우리가 만든 객체 Article을 HashSet에 저장하면 서로 다른 객체로 두개가 나옵니다.

public class EqualsTest {
    public static void main(String[] args) {
        Article a1 = new Article();
        Article a2 = new Article();
 
        a1.setId(100);
        a2.setId(100);
 
        System.out.println(a1.equals(a2));
 
        Set<Article> articles = new HashSet<Article>();
        articles.add(a1);
        articles.add(a2);
         
        System.out.println(articles);  
        //두개가 나옴
    }
}

 

Object 클래스의 hashCode() 메소드는 해당 메모리 주소값을 반환한다고 설명하였다. 그렇기 때문에 위의 a1과 a2는 다른 해시값을 반환할 것이고, HashSet에는 2개의 객체가 서로 다른 위치에 저장될 것이다.

우리는 이러한 문제를 해결하기 위해 hashCode 메소드도 Article 클래스에 오버라이드하여 수정해주어야 한다.

 @Override
    public int hashCode() {
        return Objects.hash(getId());
    }

결국엔 db를 연동한다고 하였을때 db에서가져온 id가 같으면 두개는 동일한 것으로 봐도 되기 때문입니다.

 

그리고 위에서 getId()를 사용하는데에서는 아래와 같은 이유가 있습니다.

ORM을 다루는 경우 항상 getter를 사용 하고  에서 필드 참조를 사용하지 마십시오hashCode()equals() . ORM에서는 때때로 필드가 지연 로드되어 getter 메서드를 호출할 때까지 사용할 수 없기 때문입니다.

예를 들어, 우리 Employee클래스에서 사용하는 경우 e1.id == e2.id. id필드가 지연 로드 될 가능성이 매우 높습니다 . 따라서 이 경우 메서드 내부의 id 필드는 0 또는 null, 따라서 잘못된 동작이 발생할 수 있습니다.

그러나 를 사용하는 e1.getId() == e2.getId()경우 필드가 지연 로드된 경우에도 필드 getter를 호출하면 필드가 먼저 채워집니다.

 

reference

https://howtodoinjava.com/java/basics/java-hashcode-equals-methods/

 

728x90

'Java' 카테고리의 다른 글

[ThreadLocal] 쓰레드 로컬 사용해보기  (0) 2023.04.22
[Java] Mac에서 여러개의 Java 버전 확인하기  (0) 2022.12.13
[java] stream  (0) 2022.07.27
[Java] RuntimeException  (0) 2022.07.06
[Java] String 클래스 메소드 정리  (0) 2022.05.10

댓글