-
equals / hashCode☕️ java 2022. 12. 7. 18:06
equals ❓ hashCode ❓
equals와 hashCode는 모든 Java 객체의 부모 객체인 Object 클래스에 정의되어 있다.
- 따라서 Java의 모든 객체는 Object 클래스에 정의된 equals와 hashCode 함수를 상속받고 있다.
equals()
Object의 equals()
public class Object { @IntrinsicCandidate public Object() {} ... public boolean equals(Object obj) { return (this == obj); } ... }
- 기본적으로 equals() 메소드는 2개의 객체가 동일한지 검사하기 위해 사용한다.
- 2개의 객체가 참조하는 것이 동일한지를 확인 - 동일성(Equality)을 비교하는 것
즉, 2개의 객체가 가리키는 곳이 동일한 메모리 주소일 경우에만 동일한 객체가 된다.
프로그래밍을 하다보면 동일한 객체가 메모리 상에 여러개 띄워져있는 경우가 있다.
- 해당 객체는 서로 다른 메모리에 띄워져있으므로 동일한(Equality)객체가 아니다.
- 프로그래밍 상으로는 같은 값을 가지므로 같은 객체로 인식되어야 한다 - 동등성(Identity)
따라서, 우리는 값으로 객체를 비교하도록 equals 메소드를 오버라이딩해준다.
String s1 = new String("Test"); String s2 = new String("Test"); System.out.println(s1 == s2); // false System.out.println(s1.equals(s2)); // true; // equals, overridden in String Class public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
- 위 코드에서 s1.equals(s2) 의 결과는 true 이다.
- 이유 : String 클래스에서 equals() 메소드를 오버라이드해 객체가 같은 값을 갖는지 동등성(Identity)을 비교하도록 처리했기 때문이다.
equals 메서드 단계별 구현 방법
- == 연산자를 사용해 입력이 자기 자신의 참조인지 확인
- instanceof 연산자로 입력이 올바른 타입인지 확인
- 입력을 올바른 타입으로 형변환
- 입력 객체와 자기 자신의 대응되는 ‘핵심’ 필드들이 모두 일치하는지 하나씩 검사
hashCode()
public class Object { @IntrinsicCandidate public Object() {} ... @IntrinsicCandidate public native int hashCode(); ... }
hashCode() 메소드는 실행 중(Runtime) 객체의 유일한 integer 값을 반환한다.
Object 클래스에서는 heap에 저장된 객체의 메모리 주소를 반환한다.
- 항상 그런 것은 아니다.
hashCode는 HashTable 과 같은 자료구조를 사용할 때 데이터가 저장되는 위치를 결정하기 위해 사용된다.
equals 와 hashCode의 관계
동일한 객체는 동일한 메모리 주소를 갖는다는 것을 의미한다.
따라서, 동일한 객체는 동일한 해시코드를 가져야 한다.
⇒ equals() 메소드를 오버라이드 한다면, hashCode() 메소드도 함께 오버라이드 되어야 한다.
equals 와 hashCode의 관계
- 프로그램을 실행하는 동안 equals() 에 사용된 정보가 수정되지 않았다면, hashCode는 항상 동일한 정수값을 반환해야 한다.
- Java 프로그램을 실행할 때 마다 달라지는 것은 상관 ❌
- 두 객체가 equals() 에 의해 동일하다 → 두 객체의 hashCode() 값도 일치
- 두 객체가 equals() 에 의해 동일하지 않다 → 두 객체의 hashCode() 값은 일치하지 않아도 된다.
📌 equals, hashCode 의 override
equals() 를 오버라이드 해야 하는 이유
public class TestObject { private Integer id; }
public class EqualsTest { public static void main(String[] args) { TestObject t1 = new TestObject(); TestObject t2 = new TestObject(); t1.setId(100); t2.setId(100); System.out.println(t1.equals(t2)); //false } }
- 같은 id 값을 가지는 객체를 서로 다른 과정에 의해 생성했을 경우
- 2개의 객체는 같은 id를 갖기 때문에 equals 연산시 true를 반환해야 한다.
- 하지만 false 를 반환한다.
→ 이런 문제를 해결하기 위해서 객체에 대한 equals() 메소드를 오버라이드 해야 한다.
public boolean equals(Object o) { if(o == null) { return false; } if (o == this) { return true; } if (getClass() != o.getClass()) { return false; } TestObject t = (TestObject) o; return (this.getId() == t.getId()); }
equals 에 의한 문제는 해결된 것 처럼 보이지만,
⚠️ 해당 객체를 HashSet과 같은 자료구조에 저장하려고 하면 또 다른 문제가 발생한다.
hashCode의 오버라이드 필요성
HashTable 이나 HashSet, HashMap 같은 자료구조는 자료를 저장하기 위한 위치를 선택하기 위해 hashCode를 이용한다.
public class EqualsTest { public static void main(String[] args) { TestObject t1 = new TestObject(); TestObject t2 = new TestObject(); t1.setId(100); t2.setId(100); //Prints 'true' now! System.out.println(t1.equals(t2)); Set<TestObject> objects = new HashSet<TestObject>(); objects.add(t1); objects.add(t2); System.out.println(objects); //Prints two objects } }
Object 클래스의 hashCode() 메소드는 해당 메모리 주소값을 반환한다.
- 따라서, 위 코드의 t1, t2는 다른 해시값을 반환한다
- ⇒ HashSet에는 2개의 객체가 서로 다른 위치에 저장되게 된다.
이러한 문제를 해결하기 위해 hashCode 메소드도 객체 클래스에 오버라이드하여 수정해주어야 한다.
@Override public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + getId(); return result; }
⇒ hashSet 에도 1개의 데이터만 저장되게 된다.
외부 라이브러리를 통한 override
Apache Commons 의 라이브러리를 사용하면 HashCodeBuilder 와 EqualsBuilder를 사용할 수 있다.
- 더욱 간편하게 메소드들을 오버라이드할 수 있다.
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; public class TestObject { private Integer id; private String firstname; private String lastName; private String department; ... @Override public int hashCode() { final int PRIME = 31; return new HashCodeBuilder(getId()%2==0?getId()+1:getId(), PRIME).toHashCode(); } @Override public boolean equals(Object o) { if (o == null) return false; if (o == this) return true; if (o.getClass() != getClass()) return false; TestObject t = (TestObject) o; return new EqualsBuilder(). append(getId(), t.getId()). isEquals(); } }
equals(), hashCode() 오버라이드 가이드라인
- hashCode와 equals를 생성하기 위해서는 같은 속성(attribute)를 이용하라
- ex) id
- equals()는 일관성되어야 한다.
- a.equals(b) == true 라면 a.hashCode() == b.hashCode() 역시 true 여야 한다.
- 두 메소드는 함께 항상 오버라이드 되어야 한다.
ORM 에서의 hashCode, equals
ORM를 사용하고 있다면, hashCode와 equals를 오버라이드 메소드 내부에서 Getter를 사용하자.
- ORM에 의해 필드가 lazy loaded 되어 getter를 부르기 전에 사용할 수 없을 수도 있기 때문
// 클래스의 정보가 lazy loaded 되면 id 에 0이 할당 t1.id == t2.id //( 0 == 0 ) 으로 처리가 될 수 있다. // 이 것을 수정 t1.getId() == t2.getId() // ORM에 의해 id에 값이 할당된 후 getId()가 호출가능하므로 오작동을 멈출 수 있다.
참고자료
https://mangkyu.tistory.com/101
https://velog.io/@lovi0714/Equals와-Hashcode를-override-해야하는-이유
'☕️ java' 카테고리의 다른 글
Reflection API ? (0) 2023.02.09 커스텀 validation 만들기 (0) 2023.01.28 CheckedException 과 UncheckedException (0) 2022.10.10 가비지 컬렉션(GC) ❓ (0) 2022.10.10 Java version 별 특징 (0) 2022.10.09