메서드- 규칙 41 오버로딩할 때는 주의하라.
JAVA/Effective Java

메서드- 규칙 41 오버로딩할 때는 주의하라.

반응형

오버로딩을 조심하라는 이번 주제를 확인하기 전에
오러로딩이 무엇인지 정리해보자.

오버로딩이란
오버로딩은 같은 이름의 메소드를 파라미터를 달리하여 사용하는 메서드를 말한다.

아래에 보면 오버로딩이 된 printData를 int와 string 데이터들이 사용하는 것을 볼 수있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Effective41 {
    public static void main(String args []) {
        printData(10);
        printData("test");
    }
    
    public static void printData(int data) {
        System.out.println("test " + data);
    }
    
    public static void printData(String data) {
        System.out.println("String " + data);
    }
 
}
 
// 결과
integer 10
String test
cs





이렇게 편하게 사용할 수 있는 오버로딩을 제대로 사용하지 않을경우,
문제를 야기할 수 있다. 

다음 사례를 확인해보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Effective41 {
    public static void main(String args []) {
        Collection<?>[] collections = {
            new HashSet<String>(),
            new ArrayList<BigInteger>(),
            new HashMap<StringString>().values()
        };
        for (Collection<?> c : collections) {
            System.out.println(classify(c));
        }
    }
    
    /**
     * @return
     */
    public static String classify(Set<?> s) {
        return "Set";
    }
    
    public static String classify(List<?> s) {
        return "List";
    }
    
    public static String classify(Collection<?> s) {
        return "Unknown Collection";
    }
}
cs



위의 결과는 예상했을 때는 
"Set"
"List"
"Collection" 순서로 출력될 것 같으나  "Unknown Collection"만 3개가 출력된다.

이유가 무엇일지 혼란스러울 것이다.
나도 혼란스러웠다. 왜지??? 

그 이유는 오버로딩된 메서드 가운데 어떤 메서드를 호출할 것인지는 컴파일 시점에서 결정이 된다.
위의 Collection을 보면 모두 Collection<?>으로 동일하다. 

그렇기 때문에 컴파일 시점에 Collection<?> 타입이였던 객체 모두 Collection을 파라미터로 가지는 메서드가 실행된 것이다.

반대로 오버라이딩은 동적으로 선택이 되기 때문에, 자식클래스에서 재정의한 메서드가 실행시간에도 호출이 되는 이유이다.

위의 메서드의 문제를 해결하기 위해서는 굳이 오버로딩을 사용하지말고 instanceof를 사용하여 해결할 수 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Effective41 {
    public static void main(String args []) {
        Collection<?>[] collections = {
            new HashSet<String>(),
            new ArrayList<BigInteger>(),
            new HashMap<StringString>().values()
        };
        for (Collection<?> c : collections) {
            System.out.println(classify(c));
        }
    }
 
    public static String classify(Collection<?> c) {
        return c instanceof Set ? "SET" : c instanceof List ? "List" : "Unknown Collection";
    }
}
cs




위와 같은 문제가 발생될 수 있기때문에 같은 인자의 수를 갖는 메소드를 오버로딩을 하는 것을 피해야 한다.

대표적인 예로 ObjectOutputStream의 경우는 각 경우마다 write메소드를 오버로딩 하지 않고 각 인자마다 새로 만들었다.




또 다른 오버로딩의 문제를 살펴보자.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String args []) throws FileNotFoundException, IOException {
        Set<Integer> set = new TreeSet<>();
        List<Integer> list = new ArrayList<>();
    
        for (int i = -3; i < 3; i++) {
            set.add(i);
            list.add(i);
        }
        
        for (int i = 0; i < 3; i++) {
            set.remove(i);
            list.remove(i);
        }
        
        System.out.println(set + " " + list);
        
    }
cs



위의 코드를 살펴보면 set과 list에 -3, -2, -1, 0, 1, 2 가 들어가게 되고
0,1,2 세개의 데이터를 지우도록 코드가 작성되어있다.

하지만
[-3, -2, -1] [-2, 0, 2]  다음과 같은 결과가 출력된다.

왜 List의 데이터는 이상한 값이 출력되는 것인가.?
그 이유는 List<E> 인터페이스에 정의된 remove는 remove(E)와 remove(int) 두가지가 존재한다.

그렇기 때문에 Integer로 들어간 데이터가 자동객체화(autoboxing)을 통해서 int로 변경되었고
첫 번째, 두 번째, 세 번재 위치의 요소가 지원지게 된 것이다.

이와 같이 오버로딩에는 다양한 오류를 야기할 수 있고, 이 오버로딩에 대한 구체적인 규칙이 33쪽에 이를 정도로 양이 어마어마하다.

그렇기 때문에 이런 문제를 야기하지 않기 위해서 될 수 있으면 오버로딩을 피하고 메서드를 따로 만들거나 instanceof 를 통해 구분하여 사용하는 것이 조금 더 방어적으로 코딩하는 방법이 될 것 같다.



출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙41 인용.

반응형