사용자 도구

사이트 도구


java:equals_hashcode

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

양쪽 이전 판 이전 판
다음 판
이전 판
java:equals_hashcode [2017/06/27 21:30]
kwon37xi
java:equals_hashcode [2017/07/04 01:06] (현재)
kwon37xi [동치 문제의 최종 해결책 canEqual]
줄 1: 줄 1:
 ====== Java equals & hashCode ====== ====== Java equals & hashCode ======
-  * [[https://www.artima.com/lejava/articles/equality.html|How to Write an Equality Method in Java]] +  * [[java:equals_verifier|Equals Verifier]] ''equals'', ''hashCode'' 테스트 자동화
-  * [[java:equals_verifier|Equals Verifier]]+
   * [[java:hibernate:equalsandhashcode|Hibernate and equals & hashCode]]   * [[java:hibernate:equalsandhashcode|Hibernate and equals & hashCode]]
  
-===== 자주하는 equals & hashCode 실수 ===== +===== 자주하는 equals & hashCode 실수 및 올바른 구현 ===== 
-  * 잘못된 ''equals'' 메소드 시그너쳐<code java>+  * [[https://www.artima.com/lejava/articles/equality.html|How to Write an Equality Method in Java]] 
 + 
 +==== 잘못된 ''equals'' 메소드 시그너쳐 ==== 
 +<code java>
 public boolean equals(ClassName other) { public boolean equals(ClassName other) {
   // 잘못된 equals!!   // 잘못된 equals!!
 } }
  
-// 다음이 올바르다+// 다음이 올바르다. 무조건 다음과 같이한다.
 public boolean equals(Object other) { public boolean equals(Object other) {
 ... ...
줄 16: 줄 18:
 </code> </code>
  
 +==== equals가 변경되면 hashCode도 변경해야한다 ====
 +  * ''equals''가 사용하는 필드와 ''hashCode''가 사용하는 필드를 동일하게 맞춰야 한다.
 +  * 그렇지 않으면 ''HashSet.contains'' 등이 올바르게 작동하지 않게 된다. HashSet은 hashCode 기반으로 bucket을 결정한다. 
 +  * **hashCode 규약**
 +    * 두 개의 객체가 ''equals(Object)'' 호출시 ''true''라면 두 객체의 ''hashCode()''는 동일한 int 를 리턴해야 한다.
 +    * ''hashCode''는 ''equals''에서 사용한 필드들만을 사용해야한다.
  
 +==== Mutable(변경가능한) 필드에 대한 equals/hashCode 구현은 하면 안 된다 ====
 +  * 변경가능한 필드에 대해 ''equals'', ''hashCode''를 구현한 상태에서 해당 필드 값을 변경하면 동일 객체라도 hashCode가 바뀌게 된다.
 +  * 이 상태로 필드 변경전에 HashSet에 값을 넣었다면, 필드 값을 변경한 후에는 해당 객체를 HashSet에서 찾을 수 없게 된다. hashCode로 bucket을 결정하는데 hashCode가 바뀌었기 때문이다.
 +
 +==== 동치(equivalence)를 위반해서는 안된다 ====
 +''equals''는 ''null''이 아닌 객체간에는 항상 동치 관계를 유지하게 구현해야만 한다.
 +
 +  * 반사성(reflexive) : null이 아닌 x에 대해 ''x.equals(x)''는 항상 ''true''여야 한다.
 +  * 대칭성(symmetric) : null이 아닌 x, y에 대해, 오직 ''y.equals(x)''가 ''true''일 때만 ''x.equals(y)''도 ''true''여야 한다. [[https://github.com/kwon37xi/research-java8/blob/master/java-8-others/src/main/java/equality/no_equivalance_relation/NoSymmetricColoredPoint.java|대칭성 위반 예]]
 +  * 이행성(transitive) : null이 아닌 x,y,z에 대해, ''x.equals(y)''가 ''true''이고 ''y.equals(z)''가 ''true''이면 ''x.equals(z)''도 ''true''여야 한다. [[https://github.com/kwon37xi/research-java8/blob/master/java-8-others/src/main/java/equality/no_equivalance_relation/NoTransientColoredPoint.java|이행성 위반 예]]
 +  * 일관성(consistent) : null이 아닌 x, y에 대해, 객체의 동등성 비교에 사용된 정보에 변경이 없다면 ''x.equals(y)''를 여러번 호출해도 일관성있게 ''true''를 반환하거나 일관성있게 ''false''를 반환해야 한다.
 +  * null이 아닌 값 x 에 대해 ''x.equals(null)''은 항상 ''false''를 반환해야 한다.
 +  * [[https://github.com/kwon37xi/research-java8/blob/master/java-8-others/src/main/java/equality/no_equivalance_relation/WrongStrictPoint.java|상속관계에 지나치게 엄격 한 예]]
 +
 +상속 관계에서는 위 동치성을 쉽게 위반하게 된다. ''canEqual'' 메소드를 통해 이를 지켜내야 한다.
 +
 +==== 동치 문제의 최종 해결책 canEqual ====
 +''equals''와 ''hashCode''를 override 할 때마다 ''can equal''메소드도 함께 구현하고 오버라이드 해주면 된다.
 +이를 통해 현재 클래스의 상위클래스와 동등하게 평가되는 것을 막을 수 있다.
 +  * [[https://github.com/kwon37xi/research-java8/blob/master/java-8-others/src/main/java/equality/Point.java|Point.java]]
 +  * [[https://github.com/kwon37xi/research-java8/blob/master/java-8-others/src/main/java/equality/ColoredPoint.java|ColoredPoint.java]]
 +
 +<code java>
 +// Point 라는 클래스가 존재할 때
 +
 +public boolean canEqual(Object other) {
 +    return (other instanceof Point);
 +}
 +
 +@Override 
 +public boolean equals(Object other) {
 +    boolean result = false;
 +    if (other instanceof Point) {
 +        Point that = (Point) other;
 +        // that.canEqual(this) 가 핵심이다. 이를 거꾸로 this.canEqual(that) 으로 사용하면 안된다.
 +        result = (that.canEqual(this) && this.getX() == that.getX() && this.getY() == that.getY());
 +    }
 +    return result;
 +}
 +
 +// 오버라이드 되는 ColoredPoint 클래스에서는
 +@Override
 +public boolean equals(Object other) {
 +    boolean result = false;
 +    if (other instanceof ColoredPoint) {
 +        ColoredPoint that = (ColoredPoint) other;
 +        result = (that.canEqual(this) && this.color.equals(that.color) && super.equals(that));
 +    }
 +    return result;
 +}
 +
 +// that.canEqual(this) 호출을 통해서 상위클래스(Point)는 결코 ColoredPoint와 같을 수 없게 보장됨.
 +@Override 
 +public boolean canEqual(Object other) {
 +    return (other instanceof ColoredPoint);
 +}
 +</code>
 +  * 프로그래머는 상위클래스에 ''canEqual''이 구현되어 있는 경우 하위 클래스에서 ''canEqual''을 구현하거나 하지 않음으로써 상위클래스와의 동등성 비교를 허용하거나 안할 수 있다.
 ===== 다른 타입간의 equals 탐지 ===== ===== 다른 타입간의 equals 탐지 =====
   * 서로 다른 타입간의 equals는 항상 ''false''가 될 가능성이 높은 잘못된 코딩이다.   * 서로 다른 타입간의 equals는 항상 ''false''가 될 가능성이 높은 잘못된 코딩이다.
   * [[java:findbugs|Java FindBugs]]와 이를 사용하는 [[intellij_idea:qaplug|QAPlug]], [[java:sonarqube|SonarQube]] 등으로 탐지 가능하다.   * [[java:findbugs|Java FindBugs]]와 이를 사용하는 [[intellij_idea:qaplug|QAPlug]], [[java:sonarqube|SonarQube]] 등으로 탐지 가능하다.
   * [[http://findbugs.sourceforge.net/bugDescriptions.html#EC_UNRELATED_TYPES|EC: Call to equals() comparing different types (EC_UNRELATED_TYPES)]] 을 비롯한 ''EC'', ''EQ'' 계통을 살펴본다.   * [[http://findbugs.sourceforge.net/bugDescriptions.html#EC_UNRELATED_TYPES|EC: Call to equals() comparing different types (EC_UNRELATED_TYPES)]] 을 비롯한 ''EC'', ''EQ'' 계통을 살펴본다.
java/equals_hashcode.1498568403.txt.gz · 마지막으로 수정됨: 2017/06/27 21:30 저자 kwon37xi