40. @Override 애너테이션을 일관되게 사용하라
📝 아이템 40: @Override 애너테이션을 일관되게 사용하라
🔹 핵심 요약
✅ @Override 애너테이션은 상위 클래스의 메서드를 재정의했음을 명확히 표시 ✅ 컴파일러에게 메서드 재정의 여부를 검증하도록 도움 ✅ 실수로 잘못된 메서드 재정의를 방지하는 중요한 도구 ✅ 가능한 모든 재정의 메서드에 @Override를 사용해야 함
📚 필수 개념 정리
💡 애너테이션
메타데이터로서 코드에 대한 추가 정보를 제공
컴파일 과정이나 실행 시점에 특정 동작을 안내
@ + 애너테이션 명
의 형식으로 사용
🔑 빌트인 애너테이션
@Override
: 상위 클래스 또는 인터페이스의 메서드를 재정의할 때 사용@Deprecated
: 더 이상 사용되지 않거나 권장되지 않는 메서드, 클래스에 사용@SuppressWarnings
: 특정 컴파일 경고를 무시하도록 설정@SafeVarargs
: 제네릭 가변 인자에 대한 경고를 억제@FunctionalInterface
: 해당 인터페이스가 함수형 인터페이스임을 명시
💡 메소드 재정의
상속 관계에 있는 클래스에서 부모 클래스의 메소드를 자식 클래스가 새로운 방식으로 구현하는 것
또는, 자식 클래스가 부모 클래스의 메서드를 오버라이드(재정의) 하는 것
equals()
메서드를 재정의할 때는반사성
,대칭성
,추이성
,일관성
,null-아님
을 잘 지켜야 함(Item 10)equals()
메서드를 재정의할 때는hashCode()
까지 함께 재정의해야 함(Item 11)
💡 @Override란?
메서드가 상위 클래스나 인터페이스의 메서드를 정확히 재정의했는지 검증하는 애너테이션
컴파일 시점에 오버라이딩 오류를 잡아낼 수 있는 안전장치
🧐 @Override을 사용하지 않으면?
의도와 다르게 오버로딩이 될 가능성이 있음
컴파일러가 재정의 여부를 확인할 수 없어 런타임 오류로 이어질 수도 있음
❓ 이 코드의 결과는?
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;
}
// hashCode 메서드 구현 (equals와 함께 재정의)
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(s.size()); // 26(알파벳 개수)?
}
}
→ 결과는 260
👉 hashset인데 왜 중복이 되는걸까..?
🔍 HashSet의 중복 판단 과정
1️⃣ 해시 코드 비교
hashCode() 메서드로 객체의 해시값 계산
해시값이 다르면 다른 객체로 판단
2️⃣ equals() 메서드 비교
해시값이 같은 경우 equals() 메서드로 최종 동등성 판단
Object의 기본 equals() 메서드는 == 연산자와 동일 (참조 동일성)
📌 왜 260개의 객체가 생성되었을까?
equals(Bigram b)
는 Object의equals(Object o)
를 재정의하지 않음HashSet은
Object.equals()
로 중복을 판단Object.equals()는 참조 동일성만 비교 → 모든 객체가 다른 객체로 인식
// Object.equals(): 참조 동일성만 비교 public boolean equals(Object obj) { return (this == obj); }
✨ equals() 구현 시 고려사항
매개변수는 Object로 받아야 함
instanceof로 타입 검사
안전한 타입 캐스팅
객체의 내부 상태 비교
💡 코드 예제 및 설명
✅ 메서드 재정의
// 문제가 있던 코드
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
// equals 메서드 오버로딩: 원래 Object 클래스의 equals 메서드를 재정의하려 했으나,
// 파라미터가 Bigram 타입이기 때문에 오버로딩이 아닌 별도의 메서드가 됨
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
// 해시 코드 계산: 첫 번째 문자와 두 번째 문자를 이용해 고유한 해시 값을 계산
public int hashCode() {
return 31 * first + second;
}
// main 메서드: Bigram 객체를 HashSet에 추가하고 크기를 출력
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(s.size()); // Set에 저장된 고유한 Bigram의 개수 출력
}
}
// @Override 애너테이션 추가 - 컴파일 에러 발생
@Override
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
// 오류 수정
// 올바른 equals 재정의 방법
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
// equals 메서드 재정의: Object 클래스의 equals를 올바르게 오버라이드
@Override
public boolean equals(Object o) {
// 인스턴스 타입 확인
if (!(o instanceof Bigram))
return false;
// 안전한 타입 변환
Bigram b = (Bigram) o;
// 동등성 비교
return b.first == first && b.second == second;
}
// hashCode 메서드 재정의: equals 메서드와 함께 동작하도록 hashCode도 재정의
@Override
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(s.size()); // Set에 들어있는 고유한 Bigram 객체의 개수 출력: 26
}
}
🎯 결론
📍 모든 재정의 메서드에 @Override 애너테이션 사용
📍 IDE의 자동 생성 기능 적극 활용
💭 느낀 점
💡 @Override는 단순한 애너테이션이 아니라 코드 품질을 높이는 강력한 도구
💡 메서드 재정의 시 항상 일관성과 정확성을 고려해야 한다
💡 작은 실수가 큰 버그로 이어질 수 있으므로 주의 깊은 접근 필요
Last updated