Search

아이템52 : 다중정의는 신중히 사용하라

다중정의(overloading, 오버로딩, 다중정의한 메서드)란? 같은 이름의 메서드로 매개변수 유형과 개수가 다르게 메소드 정의한 것을 의미함. 이 글에서는 다중정의라고 명칭하려고 한다. 재정의(override, 오버라이드, 재정의한 메서드)란? 상위 클래스가 정의한 것과 똑같은 시그니처의 메서드를 하위 클래스에서 다시 정의하는 것을 의미함. 이 글에서는 재정의라고 명칭하려고 한다.

 다중정의하면 런타임 때, 원하는 메서드 호출이 가능할까?

public class CollectionClassifier { //다중정의 메서드 1 public static String classify(Set<?> s) { return "Set"; } //다중정의 메서드 2 public static String classify(List<?> lst) { return "List"; } //다중정의 메서드 3 public static String classify(Collection<?> c) { return "Unknown Collection"; } public static void main(String[] args) { Collection<?>[] collections = { new HashSet<>(), new ArrayList<>(), new HashMap<String, String>().values() }; for (Collection<?> c : collections) //c는 항상 Collection<?> 타입 System.out.println(classify(c)); //매번 3번째 함수가 호출된다. } }
Java
불가능하다. 런타임에 타입이 매번 달라지지만, 호출할 메서드를 선택하는 데는 영향을 주지 못한다.
다중정의된 메서드는 컴파일 타임에 정해지기 때문이다. 따라서 런타임 타입은 전혀 중요치 않다.
즉 재정의한 메서드는 동적으로 선택되고, 다중정의한 메서드는 정적으로 선택된다.
재정의한 메서드 : 오버라이드
다중정의한 메서드 : 오버로딩

 instanceof 활용하기

public static String classify(Collection<?> c) { return c instanceof Set ? "집합" : c instanceof List ? "리스트" : "그 외"; }
Java
instanceof를 활용하면 의도한 대로 런타임 때, 원하는 메서드 호출이 가능하다.
1.
모든 classify 메서드를 하나로 만들기
2.
instanceof로 명시적으로 검사하기

 다중정의 실패한 사례

 제네릭과 오토박싱으로 인한 실패 사례

List 인터페이스는 remove(Object) & remove(int)를 다중정의 했다.
매개변수 집합을 명확했지만 실패한 사례이다.
제네릭과 오토박싱이 등장하면서 List 인터페이스는 취약해졌다.
아래 코드처럼 의도한대로 동작하지 않을 수 있다.
List<Integer> numbers = new ArrayList<>(); for (int i = -3; i < 3; i++) numbers.add(i); for (int i = 0; i < 3; i++) numbers.remove(i); //remove(Integer) -> 리스트에서 해당 원소 삭제 //remove(int) -> 리스트에서 해당 인덱스에 위치하는 원소 삭제
Java

 자바8의 람다와 메서드 참조로 인한 실패 사례

ExecutorService exec = Executors.newCachedThreadPool(); exec.submit(System.out::println); //submic 메서드는 Callable<T>를 받는 메서드도 존재함
Java
메서드를 다중정의할 때, 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서 안된다.
서로 다른 함수형 인터페이스라도 근본적으로 다르지 않다.
기대와 다르게 동작할 수 있다.

 다중정의 ‘잘’ 사용하기

 다중정의 대신 이름 다르게 하기

다중정의하는 대신 메서드 이름을 다르게 지어주는 방법도 있다.
ex) readBoolean(), readInt(), readLong()

 생성자는 정적 팩터리로

생성자는 이름을 다르게 할 수 없다. 따라서 두 번째 생성자부터 무조건 다중정의가 되는 셈이다.
이를 해결하기 위해 정적 팩터리를 활용할 수 있다.

 매개변수가 같은 다중정의는 매개변수 집합을 명확하게

어느 매개변수 집합을 처리할지 명확히 구분한다면 헷갈릴 일은 없다.
null이 아닌 두 타입의 값을 서로 어느 쪽으로든 형변환할 수 없어야 한다. (근본적으로 다름)
ex) ArrayList → int를 받는 생성자 & Collection을 받는 생성자는 헷갈릴 일이 없다.

 형변환하여 정확한 다중정의 메서드 호출하기

아래 코드처럼 인수를 Integer로 형변환하여 올바른 다중정의 메서드를 호출할 수도 있다.
for (int i = 0; i < 3; i++) numbers.remove((Integer) i); //0, 1, 2 원소 제거
Java

 기능이 똑같다면 Ok

어떤 다중정의 메서드가 불리는지 몰라도 기능이 똑같다면 신경 쓸 게 없다.
일반적인 방법은 아래와 같이 상대적으로 특수한 다중정의 메서드에서 덜 특수한 다중정의 메서드로 일을 넘겨버리는(forward) 것이다.
public boolean contentEquals(StringBuffer sb) { return contentEquals((CharSequence) sb); //forward하여 동일한 일을 하도록 함 }
Java

 핵심 요약

매개변수 개수가 같다면 다중정의는 되도록 피하자. → 혼동을 일으키는 상황을 피하자.
안전하게 가려면 매개변수 개수가 같은 다중정의는 만들지 말자.
가변인수(varargs)를 사용하는 메서드라면 더욱 만들지 말자.
다중정의를 해야 한다면 형변환하여 정확한 다중정의 메서드가 선택되도록 하자.
위가 불가능하다면 다중정의 메서드들이 모두 동일한 동작을 하도록 하자.