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