[java] Java에서 equals 및 hashCode를 재정의 할 때 고려해야 할 사항은 무엇입니까?
재정의 할 때 고려해야 할 사항 equals
과 함정은 무엇입니까 hashCode
?
답변
이론 (언어 변호사와 수학적으로 기울어 진) :
equals()
( javadoc )는 동등성 관계를 정의해야합니다 ( 반사적 , 대칭 적 , 전 이적 이어야 함 ). 또한 일관성 이 있어야합니다 (개체를 수정하지 않은 경우 계속 같은 값을 반환해야 함). 또한 o.equals(null)
항상 false를 반환해야합니다.
hashCode()
( javadoc )도 일관성이 있어야합니다 (객체가로 수정되지 않은 경우 equals()
계속 동일한 값을 반환해야 함).
두 방법 의 관계 는 다음과 같습니다.
때마다
a.equals(b)
, 다음a.hashCode()
과 같은 동일해야합니다b.hashCode()
.
실제로:
하나를 재정의하면 다른 것을 재정의해야합니다.
당신이 계산에 사용하는 필드의 동일한 세트를 사용하여 equals()
계산에 hashCode()
.
Apache Commons Lang 라이브러리 의 탁월한 헬퍼 클래스 EqualsBuilder 및 HashCodeBuilder 를 사용하십시오 . 예를 들면 :
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
또한 기억하십시오 :
HashSet , LinkedHashSet , HashMap , Hashtable 또는 WeakHashMap 과 같은 해시 기반 Collection 또는 Map을 사용 하는 경우, 오브젝트가 콜렉션에있는 동안 콜렉션에 넣은 키 오브젝트의 hashCode ()가 절대 변경되지 않도록하십시오. 이를 보장하는 방탄 방법은 키를 불변으로 만드는 것인데 다른 이점도 있습니다.
답변
Hibernate와 같은 ORM (Object-Relationship Mapper)을 사용하여 지속되는 클래스를 다루는 경우 주목할 가치가있는 몇 가지 문제가 있습니다.
지연로드 된 객체는 서브 클래스입니다.
ORM을 사용하여 객체를 유지하는 경우 대부분의 경우 데이터 프록시에서 객체를 너무 일찍로드하지 않도록 동적 프록시를 처리하게됩니다. 이 프록시는 자신의 클래스의 서브 클래스로 구현됩니다. 이것은 this.getClass() == o.getClass()
을 반환 한다는 의미입니다 false
. 예를 들면 다음과 같습니다.
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
ORM을 다루는 경우 o instanceof Person
올바르게 사용하는 것이 유일한 방법입니다.
게으른로드 된 오브젝트에는 널 필드가 있습니다.
ORM은 일반적으로 게터를 사용하여 지연로드 된 객체를 강제로로드합니다. 이 수단 person.name
이 될 것입니다 null
경우 person
,로드 게으른 경우에도 person.getName()
강제로로드 및 반환 “홍길동”. 내 경험에 의하면,이 더 자주 작물 hashCode()
및 equals()
.
ORM 나왔습니다 거래 당신이 경우, 항상에서 게터, 결코 필드 참조를 사용할 수 있는지 확인 hashCode()
하고 equals()
.
객체를 저장하면 상태가 변경됩니다
영구 객체는 종종 id
필드를 사용하여 객체 의 키를 보유합니다. 이 필드는 객체가 처음 저장 될 때 자동으로 업데이트됩니다. 에서 id 필드를 사용하지 마십시오 hashCode()
. 그러나에서 사용할 수 있습니다 equals()
.
내가 자주 사용하는 패턴은
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
그러나 : 당신은 포함 할 수 없습니다 getId()
에서 hashCode()
. 그렇게하면 개체가 지속될 때 개체가 hashCode
변경됩니다. 객체가에 HashSet
있으면 다시 찾지 않습니다.
내에서 Person
예를 들어, 나는 아마 사용합니다 getName()
위해 hashCode
및 getId()
플러스 getName()
에 대한 (단지 편집증에 대한) equals()
. 에 대한 “충돌”의 위험이있는 경우에는 hashCode()
괜찮지 만 결코 괜찮습니다 equals()
.
hashCode()
속성의 변경되지 않는 하위 집합을 사용해야합니다. equals()
답변
에 대한 설명 obj.getClass() != getClass()
입니다.
이 진술은 equals()
상속이 비우호적 인 결과입니다 . JLS (Java 언어 사양)는 그렇다면 A.equals(B) == true
을 B.equals(A)
반환해야 한다고 지정합니다 true
. 이 명령문을 생략하면 클래스를 상속 equals()
하고 (동작을 변경하는) 상속하는 클래스 가이 스펙을 위반합니다.
명령문이 생략 될 때 발생하는 다음 예를 고려하십시오.
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
이렇게 new A(1).equals(new A(1))
또한, new B(1,1).equals(new B(1,1))
예상대로, 사실 알려주지 발생합니다.
이것은 매우 좋아 보이지만 두 클래스를 모두 사용하려고하면 어떻게됩니까?
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
분명히 이것은 잘못된 것입니다.
대칭 조건을 보장하려는 경우. b = a 인 경우 a = b이고 Liskov 대체 원리는 예를 들어서 super.equals(other)
뿐만 아니라 예를 B
들어 확인해야 A
합니다.
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
어느 것이 출력 될까요?
a.equals(b) == true;
b.equals(a) == true;
a
의 참조가 아닌 경우 B
클래스의 참조가 될 수 있습니다 A
(확장하기 때문에).이 경우 super.equals()
에도 호출 합니다.
답변
상속 친화적 인 구현을 위해서는 Tal Cohen의 솔루션, equals () 메소드를 올바르게 구현하는 방법을 확인하십시오.
요약:
Joshua Bloch는 자신의 저서 Effective Java Programming Language Guide (Addison-Wesley, 2001)에서 “인스턴스 계약을 유지하면서 인스턴스화 가능한 클래스를 확장하고 측면을 추가 할 수있는 방법이 없다”고 주장합니다. 탈이 동의하지 않습니다.
그의 해결책은 두 가지 방법으로 다른 비대칭 blindlyEquals ()를 호출하여 equals ()를 구현하는 것입니다. blindlyEquals ()는 서브 클래스로 대체되고 equals ()는 상속되며 절대 재정의되지 않습니다.
예:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
Liskov 대체 원칙 을 만족 하려면 equals ()가 상속 계층 구조에서 작동해야합니다 .
답변
아무도 구아바 도서관을 추천하지 않았다는 것에 여전히 놀랐습니다.
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
답변
수퍼 클래스에는 java.lang.Object라는 두 가지 메소드가 있습니다. 우리는 그것들을 커스텀 객체로 재정의해야합니다.
public boolean equals(Object obj)
public int hashCode()
동일한 객체는 동일한 해시 코드를 생성해야하지만 동일하지 않은 객체는 고유 한 해시 코드를 생성 할 필요는 없습니다.
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
더 자세한 정보를 원하시면 http://www.javaranch.com/journal/2002/10/equalhash.html 로이 링크를 확인 하십시오.
이것은 또 다른 예입니다.
http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
즐기세요! @. @
답변
멤버 평등을 확인하기 전에 클래스 평등을 확인하는 몇 가지 방법이 있으며 올바른 환경에서 두 가지 모두 유용하다고 생각합니다.
instanceof
연산자를 사용하십시오 .- 사용하십시오
this.getClass().equals(that.getClass())
.
final
등호 구현 에서 # 1을 사용 하거나 등호 알고리즘을 규정하는 인터페이스를 구현할 때 ( java.util
컬렉션 인터페이스 등 (obj instanceof Set)
구현중인 인터페이스 를 확인하는 올바른 방법 ). 대칭 특성을 손상시키기 때문에 equals를 무시할 수있는 경우 일반적으로 나쁜 선택입니다.
옵션 # 2를 사용하면 등호를 무시하거나 대칭을 위반하지 않고 클래스를 안전하게 확장 할 수 있습니다.
클래스가 또한 Comparable
인 경우 equals
and compareTo
메소드도 일관성이 있어야합니다. Comparable
클래스 의 equals 메소드에 대한 템플릿은 다음과 같습니다 .
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}