Effective Java : 아이템 40. @Override 에너테이션을 일관되게 사용해라

    아이템 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를 표시하자.

    1. 재정의 함을 명시함. 
    2. 필요한 경우 컴파일 에러를 발생시킴. 

    아래에서는 재정의하는 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를 붙이는게 헷갈리지 않고 좋을 것 같다. 

    댓글

    Designed by JB FACTORY