아이템 40. @Override 어노테이션을 일관되게 사용해라.
- 상위 클래스 / 추상 클래스 / 인터페이스 중 재정의 하는 메서드가 있다면 반드시 @Override 어노테이션을 사용하라.
- 재정의 (Overriding)와 다중정의 (OverLoading)은 다른것임.
- @Override 어노테이션은 '재정의' 한다는 의도를 나타냄.
- @Override 어노테이션이 붙은 메서드가 '재정의'의 요건에 맞지 않을 경우 컴파일 에러 발생시킴.
- 상위 클래스 equals(Object o) → 재정의 메서드 equals(String o)
- 이 경우 함수 시그니쳐가 달라 다중정의(오버로딩)이 되면서 @Override 어노테이션이 붙은 경우 컴파일 에러 발생함.
@Override 어노테이션
@Override 어노테이션은 다음 용도로 사용되고 있는 어노테이션이다.
- 메서드에만 사용 가능한 어노테이션임.
- 상위 클래스 / 인터페이스의 메서드를 재정의 (override) 했음을 의미함.
특정 메서드를 재정의 할 때, 이 어노테이션을 습관적으로 달아주면 몇 몇 버그를 방지할 수 있다.
@Override를 달지 않으면 다중정의 해버릴 수 있음.
@Override 어노테이션을 달면 대상 메서드가 상위 클래스 / 인터페이스의 메서드를 재정의 한다는 것을 컴파일러에게 알려준다. 컴파일러는 메서드가 '재정의' 요건에 맞지 않을 경우 컴파일러 에러를 발생시킨다. 아래 이미지를 보면 바로 알 수 있다.
equals() 메서드를 재정의 하려고 했는데, equals()는 매개변수로 Object 타입을 받는다. 그런데 현재 작성된 메서드의 매개변수는 HelloTest 타입을 받고 있다. 개발자는 재정의(Override)를 하려고 했으나, 다중정의(OverLoading)이 되어버린 상황이다. 이럴 때 @Override 어노테이션을 달아주면 컴파일 에러를 발생시켜 의도치 않은 '다중 정의'가 되는 것을 막아준다.
public class HelloTest {
private final char first;
private final char second;
public HelloTest(char first, char second) {
this.first = first;
this.second = second;
}
//@Override
public boolean equals(HelloTest b) {return b.first == first && b.second == second;}
public int hashCode() {return 31 * first + second;}
public static void main(String[] args) {
Set<HelloTest> s = new HashSet<>();
for (int i = 0; i < 10; i++) {
for (char ch = 'a'; ch <= 'z'; ch++) {
s.add(new HelloTest(ch, ch));
}
}
System.out.println(s.size());
System.out.println(s.size() == 26);
}
}
위 코드에서 @Override를 제거하면 컴파일 에러는 발생하지 않는다. 그리고 equals()는 다중정의(Overloading)된 채로 사용된다. 그 결과 main()에 있는 코드의 결과는 26이 나와야 하는데, 실제로는 260이 나오게 된다.
hashSet의 size()는 내부적으로 equals(Object o) 메서드를 이용해서 해시 값을 비교해서 사이즈를 계산한다. 위 코드는 다중 정의된 equals() 메서드를 호출하기를 기대했었지만, 상위 클래스의 equals()를 호출하게 되었다. 따라서 이 HashSet의 사이즈는 26이 아니라 260이 나오게 되었다.
위 잘못된 코드 수정 : @Overriding을 이용해 재정의 함.
위의 코드는 다중정의(오버로딩) 된 것이 호출되기를 바랬지만, 실제로는 상위 클래스의 메서드가 호출된 것이 문제다. 이런 문제를 해결하기 위해서 '재정의' 하는 메서드는 다음 목표를 달성하기 위해 반드시 @Override를 표시하자.
- 재정의 함을 명시함.
- 필요한 경우 컴파일 에러를 발생시킴.
아래에서는 재정의하는 equals(), hashCode() 메서드에 @Override 어노테이션을 붙여서 재정의 시켰다. 그리고 코드를 실행해보면 기대한대로 HashSet에는 '26'개의 원소만 있는 것을 확인할 수 있다.
public class HelloGood {
private final char first;
private final char second;
public HelloGood(char first, char second) {
this.first = first;
this.second = second;
}
@Override
public boolean equals(Object b) {
if (!(b instanceof HelloGood))
return false;
HelloGood bb = (HelloGood) b;
return (bb.first == first) && (bb.second == second);
}
@Override
public int hashCode() {return 31 * first + second;}
public static void main(String[] args) {
Set<HelloGood> s = new HashSet<>();
for (int i = 0; i < 10; i++) {
for (char ch = 'a'; ch <= 'z'; ch++) {
s.add(new HelloGood(ch, ch));
}
}
System.out.println(s.size());
System.out.println(s.size() == 26);
}
}
@Override 재정의 정리
최근 인터페이스에서 default 메서드까지 지원하면서 상위 클래스 / 인터페이스의 메서드를 재정의 하는 일이 꽤 많아졌다. @Override를 붙이게 되면, 위처럼 의도치 않게 '다중정의'가 되는 것을 컴파일 레벨에서 막을 수 있기 때문에 좀 더 단단한 코드를 작성할 수 있다.
책에서는 추상 클래스의 추상 메서드를 재정의 하는 경우에 @Override를 붙이지 않아도 된다고 한다. 하지만 통일성을 위해 나는 클래스 / 추상 클래스 / 인터페이스에 대해서 재정의를 한다면 반드시 @Override를 붙이는게 헷갈리지 않고 좋을 것 같다.
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
Effective Java : 아이템 45. 스트림은 주의해서 사용하라. (0) | 2023.08.12 |
---|---|
Effective Java : 아이템 39. 명명 패턴보다 어노테이션을 사용하라 (0) | 2023.08.09 |
Effective Java : 아이템 44. 표준 함수형 인터페이스를 사용하라 (0) | 2023.08.08 |
Effective Java : 아이템 43. 람다보다는 메서드 참조를 사용하라. (0) | 2023.08.08 |
Effective Java : 아이템 42. 익명 클래스보다는 람다를 사용하라. (0) | 2023.08.06 |