본문으로 바로가기
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


아래 프로그램은 컬렉션을 종류별로(집합, 리스트, 컬렉션) 분류하는 것이다.


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
28
29
30
public final class CollectionClassifier {
    
    public static String classify(Set<?> s) {
        
        return "Set";
    }
    
    public static String classify(List<?> l) {
        
        return "List";
    }
    
    public static String classify(Collection<?> c) {
        
        return "Unknown Collection";
    }
    
    public static void main(String[] args) {
        Collection<?>[] collection = {
            new HashSet<String>(),
            new ArrayList<BigInteger>(),
            new HashMap<StringString>().values()
        };
        
        for(Collection<?> c : collection) {
            System.out.println(classify(c));
        }
        
    }
}



이 프로그램이 Set, List, Unknown Collection을 출력하는 것을 기대하겠지만 Unknown Collection을 3번 출력한다. 왜 그럴까.?

classify 메서드가 오버로딩 되어 있으며 오버로딩된 메서드 가운데 어떤 것이 호출되는지 컴파일 시점에 결정되기 때문이다. 루프를 돌 때 인자의 컴파일 시점 자료형은 전부 Collection<?>으로 동일하다.

각 인자의 실행시점 자료형은 전부 다르지만, 선택 과정에는 영향을 끼치지 못한다. 인자의 컴파일 시점 자료형이 Collection<?> 이므로 호출되는 것은 항상 classify(Collection<?>) 메서드다.


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
class Wine {
    public String name() { return "Wine"; }
}
 
class SparklingWine extends Wine {
    @Override
    public String name() { return "SparklingWine"; }
}
 
class Champagne extends SparklingWine {
    @Override
    public String name() { return "Champagne"; }
}
 
public class Test {
    
    public static void main(String[] args) {
        Wine[] wines = {
            new Wine(), new SparklingWine(), new Champagne()    
        };
        
        for(Wine wine : wines) {
            System.out.println(wine.name());
        }
    }
}



위 프로그램은 반대로 동작한다 오버로딩된 메서드는 정적으로 선택되었지만, 재정의된 메서드는 동적으로 선택되기 때문이다.

재정의된 메서드의 경우, 선택 기준은 메서드 호출 대상 객체의 자료형이다. 객체 자료형에 따라 실행 도중에 결정되는 것이다. 하위 클래스에서 재정의한 메서드를 하위 클래스 객체에 대해 호출하면, 해당 객체의 컴파일 시점 ㅅ자료형과는 상관없이, 항상 하위 클래스의 재정의 메서드가 호출된다.


위의 예제를 좀더 자세히 살펴보면 똑같은 Wine 자료형을 가지고 있지만 Wine, SparklingWine, Chanpagne을 순서대로 출력한다.  재정의 메서드 가운데 하나를 선택할 때 객체의 컴파일 시점 자료형은 영향을 주지 못한다. 오버로딩에서는 반대로 실행시점 자료형이 아무 영향도 주지 못한다. 실행될 메서드는 컴파일 시에, 인자의 컴파일 시점 자료형만을 근거로 결정한다.


위의 CollectionClassifier 에서 이 문제를 고치기 위해서는 세 메서드를 합친 후 instanceof를 사용하여 자료형을 검사하는 것이다.


1
2
3
4
public static String classify(Collection<?> c) {
    
    return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection";
}



오버라이드(재정의)가 일반적 규범이라면 오버로딩은 예외에 해당한다.  위의 CollectionClassifier 같이 의도하지 않은 결과가 호출될 수 있다. 그래서 오버로딩을 사용할 때 혼란스럽지 않게 사용할 수 있도록 주의해야 한다.


혼란스러운 오버로딩은 무엇일까 ? 논쟁의 여지가 있지만 혼란을 피하는 전략은 같은 수의 인자를 갖는 두 개의 오버로딩 메서드를 API에 포함시키지 않는다는 것이다.


메서드를 오버로딩할 수 있다고 해서 반드시그래야 하는 것은 아니다. 인자 개수가 같은 오버로딩 메서드를 추가하는 것은 일반적으로 피해야 한다. 하지만 생성자에 대해서라면 따를수 업을 수도 있다. 그럴 때는 형변환만 추가하면 같은 인자 집합으로 여러 오버로딩 메서드를 호출하는 상황은 피하는 것이 좋다.




이 부분은 생각보다 깊게 봐야하는데 책을 보고 정리하면서 완벽하고 정리하는데

시간이 걸릴 것 같다. 추후에 다시 읽어봐야 할 부분