다중정의는 신중히 사용하라1️⃣ 아래의 코드는 어떤 값을 출력할까요?2️⃣ 아래의 코드는 어떤 값을 출력할까요?3️⃣ 예시1의 코드를 수정하려면?4️⃣ 오버로딩을 사용하려면 다음을 항상 생각하자같은 개수의 파라미터를 갖는 오버로딩 메서드 2개 이상을 갖지 마라매개변수 개수가 같은 오버로딩을 피할 수 없다면?매개변수가 근본적으로 달라도 항상 안전하지는 않다.오버로딩할때 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안된다.5️⃣ 정리
다중정의는 신중히 사용하라
1️⃣ 아래의 코드는 어떤 값을 출력할까요?
public class CollectionClassifier { public static String classify(Set<?> set) { return "집합"; } public static String classify(List<?> set) { return "리스트"; } public static String classify(Collection<?> set) { return "그 외"; } public static void main(String[] args) { Collection<?>[] collections = { new HashSet<String>(), new ArrayList<BigInteger>(), new HashMap<String,String>().values() }; for (Collection<?> c : collections) { System.out.println(classify(c)); } } }
결과보기

- 위의 코드는 “집합”, “리스트", “그 외"를 차례대로 출력할 것 같지만, 실제로 수행해보면 “그 외"만 세 번 연달아 출력한다. 그 이유는 무엇일까?
- 다중정의(오버로딩)된 메서드 중에서 어떤 메서드를 호출할지에 대한 선택은 컴파일 시점에 결정되기 때문이다.
- 따라서
Collection<?>
타입의 배열을 순회한다면Collection<?>
타입으로 모두 인지하게 되는 것이다. - 왜냐하면, 컴파일러는
Collections<?>[]
안에 있는 항목이HashSet
인지ArrayList
인지는 런타임 시점에 알 수 없기 때문이다.
- 메서드 오버라이드의 메서드 선택은 오버로딩과 다르게 런타임 시점에 결정된다.
2️⃣ 아래의 코드는 어떤 값을 출력할까요?
public class Wine { String name() { return "포도주"; } } class SparklingWine extends Wine { @Override String name() { return "발포성 포도주"; } } class Chapagne extends Wine { @Override String name() { return "샴페인"; } } class Overriding { public static void main(String[] args) { List<Wine> wineList = List.of( new Wine(), new SparklingWine(), new Chapagne() ); for (Wine wine : wineList) { System.out.println(wine.name()); } } }
결과보기

- Wine 클래스에 정의된 name 메서드는 하위 클래스인 SparklingWine과 Champagne에서 재정의 된다. 예상한 것처럼 이 프로그램은 “포도주”, “발포성 포도주", “샴페인"을 차례로 출력한다.
- for 문에서의 컴파일타임 타입이 모두 Wine인 것에 무관하게 항상 가장 하위에서 재정의한 메서드가 실행되는 것이다.
- 한편, 다중정의된 메서드 사이에서는 객체의 런타임 타입은 전혀 중요하지 않다. 선택은 컴파일 타임에, 오직 매개변수의 컴파일타임 타입에 의해 이뤄진다.
3️⃣ 예시1의 코드를 수정하려면?
- 예시1의 코드에서 프로그램의 원래 의도는 매개변수의 런타임 타입에 기초해 적절한 다중정의 메서드로 분배하는게 목적이였다. (예시 2와 동일하게!)
- 하지만 다중정의는 이렇게 동작하지 않는다. 이 문제는 (정적 메서드를 사용해도 좋다면)
CollectionClassifier
의 모든classify
메서드를 하나로 합친 후instanceof
로 명시적으로 검사하면 말끔히 해결된다.
// 예시3 public static String classify(Collection<?> c) { return c instanceof Set ? "집합" : c instanceof List ? "리스트" : "그 외"; }
4️⃣ 오버로딩을 사용하려면 다음을 항상 생각하자
같은 개수의 파라미터를 갖는 오버로딩 메서드 2개 이상을 갖지 마라
- 이러한 보수적 정책의 가장 좋은 예로
ObjectOutputStream
이 있다.
🤔 아래의 코드는 어떤 결과값을 출력할까요?
public static void main(String[] args) { 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); }
- Set과 List 각각 양수만 제거된 결과가 도출될 것이라고 혹시 예상하셨나요? 하지만 그렇지 않습니다.
[-3, -2, -1] [-3, -2, -1]
결과보기

- set.remove(i) 는 오버라이딩 메서드인 remove(E)를 호출하게 된다. 여기서 E는 제네릭 타입이므로 오토박싱이 발생한다.
- 하지만 list.remove(i)는 인덱스를 지정하여 삭제하는 메서드로 흐름을 따라가보면 아래와 같다.
// 초기값 [-3, -2, -1, 0, 1, 2] // i = 0 [-2, -1, 0, 1, 2] // i = 1 [-2, 0, 1, 2] // i = 2 [-2, 0, 2]
👀 원래 처음에 생각한 결과값처럼 동작하게 하려면 어떻게 해야할까요?
- remove() 메서드 호출할 때, 파라미터를 int를 프리미티브 타입이 아닌 박싱 타입인 Integer로 넘겨야 한다.
for (int i = 0; i < 3; i++ ){ set.remove(i); list.remove((Integer) i); // remove(Object o) 가 호출됨 }
매개변수 개수가 같은 오버로딩을 피할 수 없다면?
- 매개변수 주 하나 이상이 근본적으로 다르면(서로 어느쪽으로 형변환이 불가능 하다면) 그나마 괜찮다.
ArrayList
의 인자가 1개인 생성자는ArrayList(int initalCapacity)
와ArrayList(Collection<? extends E> c)
가 있지만int
와Collection
은 근본적으로 다르기 때문에 괜찮다.
매개변수가 근본적으로 달라도 항상 안전하지는 않다.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); list.remove(3); //3을 지우라는 것일까? index 3번째 원소를 지우라는 것일까?
- 위의 코드가 혼란스러운 이유는 List<E> 인터페이스가 remove(int index)와 remove(Object o)를 오버로딩 했기 때문이다.
- 제네릭과 오토박싱이 등장하면서 int를 Integer로 자동 변환해주니까 근본적으로 달라지지 않게 되었다.
오버로딩할때 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안된다.
new Thread(System.out::println).start(); //컴파일 성공 ExecutorService exec = Executors.newCachedThreadPool(); exec.submit(System.out::println); //컴파일 에러
- 넘겨진 인수는 모두 System.out::println 으로 똑같고 둘 다 Runnable을 받는 형제 메서드를 오버로딩 하고 있다.
- 하지만 컴파일 에러가 발생하는데, 정확한 이유는 모르겠지만 서로 다른 함수형 인터페이스라도 인수 위치가 같으면 혼란이 발생한다(사실 함수형 인터페이스들은 근본적으로 같다.)
5️⃣ 정리
- 오버로딩 메서드를 안전하게 사용하는 가장 최적의 방법은 다수 개수의 파라미터에만 오버로딩을 정의하는 것입니다.
- 오버로딩 대신, 메서드 이름을 달리하여 다중정의를 흉내내어 사용하도록 하자.