사용자가 스스로 정의한 Class를 자료형으로 하는 Set 자료구조를 사용할 때에는 중복을 체크해주기 위한 equals, hashcode 등을 올바르게 재정의 해줄 필요가 있다. (Override)
이 부분에서 주로 실수하는 부분은 바로 equals를 재정의 할 때 '재정의(Override)'가 아니라 '다중정의(Overloading)'를 하는 것이다.
먼저 아래와 같은 코드는 a부터 z까지 for-loop을 도는 것을 10번 수행하면서 Set 자료구조에 추가한다.
Set은 중복을 허용하지 않기 때문에, 나름대로 equals를 재정의했고 빼먹지 않고, 아래 성질을 지키기 위해 hashCode 또한 재정의 했다.
주로 Set, HashMap 등의 자료구조에서는 hashCode를 이용해서 버킷을 정하고, equals를 이용해서 값을 비교해서 삽입하려는 원소가 이미 존재하는지 여부를 체크한다.
위 성질을 지켰다면 아래 코드는 26을 출력해야 한다. 그러나 아래 코드는 실제로 260의 결과를 출력한다.
package com.example.Bigram;
import java.util.HashSet;
import java.util.Set;
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set<Bigram> s = new HashSet<>();
for (int i = 0; i < 10; i++) {
for (char ch = 'a'; ch <= 'z'; ch++) {
s.add(new Bigram(ch, ch));
}
}
System.out.println("크기는 26으로 예상됩니다. 실제 크기 : " + s.size());
}
}
그 이유는 바로 equals 메서드의 인자가 상위 클래스에서는 Object로 되어 있기 때문이다.
위 사진은 Set 클래스에서 equals와 hashCode가 선언되어 있는 부분이다.
제대로 수정한 코드는 아래와 같다.
package com.example.Bigram;
import java.util.HashSet;
import java.util.Set;
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
@Override
public boolean equals(Object o) {
if(!(o instanceof Bigram))
return false;
Bigram b = (Bigram) o;
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set<Bigram> s = new HashSet<>();
for (int i = 0; i < 10; i++) {
for (char ch = 'a'; ch <= 'z'; ch++) {
s.add(new Bigram(ch, ch));
}
}
System.out.println("크기는 26으로 예상됩니다. 실제 크기 : " + s.size());
}
}
@Override 애너테이션을 붙여줬고, 인자를 Object로 수정했다.
@Override를 붙이더라도 인자를 수정하지 않으면 컴파일조차 안될 것이다.
@Override를 붙이지 않아도 동작은 하지만 코드의 일관성 그리고 재정의한 사실을 명시적으로 남겨주는 것이 좋다.
핵심 정리
재정의한 모든 메서드에 @Override 애너테이션을 의식적으로 달면 여러분이 실수했을 때 컴파일러가 바로 알려줄 것이다. 예외는 한 가지뿐이다. 구체 클래스에서 상위 클래스의 추상 메서드를 재정의한 경우엔 이 애너테이션을 달지 않아도 된다(단다고 해서 해로울 것도 없다).