public class CollectionClassifier {
public static String classify(Set<?> s) {
return "집합";
}
public static String classify(List<?> list) {
return "리스트";
}
public static String classify(Collection<?> c) {
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));
}
}
}
// 요렇게 출력되지 않을까?
집합
리스트
그 외
하지만 결과는! 두구두구
// 쨘~
그 외
그 외
그 외
왜 그럴까요?
다중정의(overloading, 오버로딩)된 세 classify 중 어느 메서드를 호출할지는 컴파일 타임에 정해지기 때문입니다.
for (Collection<?> c : collections) {
// 컴파일 타임에서의 c는 항상 Collection<?> 타입
// 그래서 세번째 메서드 public static String classify(Collection<?> c) 호출
System.out.println(classify(c));
}
public class SetList {
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);
}
System.out.println("add 결과: " + set + " " + list);
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove(i);
}
System.out.println("remove 결과: " + set + " " + list);
}
}
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove((Integer) i); // Integer로 형변환해서 remove(Object o) 사용
// 혹은 list.remove(Integer.valueOf(i)); // Integer로 형변환
}
java에 generic과 autoboxing이 더해지면서 List 인터페이스가 취약해졌습니다 ㅠ
또 람다와 메서드 참조도 도입된다면 어떨까!
public class LambdaOverriding {
public static void main(String[] args) {
// 1번. Thread 생성자 호출
new Thread(System.out::println).start();
// 2번. ExecutorService의 submit 메서드 호출
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(System.out::println); // 컴파일 오류
}
}
Thread 생성자와 ExecutorService.submit 둘다 Runnable를 받는 형제 메서드를 overload하는데, 왜 컴파일 오류가 발생할까요?
ExecutorService.submit의 overload된 메서드 중에는 Callable를 받는 메서드도 있기 때문입니다. (#44)
Error:(18, 21) java: reference to submit is ambiguous
both method submit(java.util.concurrent.Callable) in java.util.concurrent.ExecutorService and method submit(java.lang.Runnable) in java.util.concurrent.ExecutorService match
하지만 모든 println은 void 형이므로, 반환값이 있는 Callable와 헷갈릴 수가 있는건가요?
자바의 다중정의 해소(overloading resolution) 알고리즘이 기대처럼 동작하지 않은 상황입니다.
왜? 참조된 메서드(println)과 호출한 메서드(submit) 양쪽 다 오버로딩된 상태이기 때문!
컴파일러 제작자를 위한 설명입니다. 무슨 말인지 모르겠네요 ㅠ
System.out::println은 부정확한 메서드 참조(inexact method reference)이다. 암시적 타입 람다식(implicitly typed lambda expression)이나 부정확한 메서드 참조 같은 인수 표현식은 목표 타입이 선택되기 전에는 그 의미가 정해지지 않기 때문에 적용성 테스트(applicability test) 때 무시된다.
핵심은! 메서드를 오버로딩할 때, 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안됩니다!
즉, 서로 다른 함수형 인터페이스는 근본적으로 다르지(NOT radically different) 않습니다.
'근본적으로 다른' 것들
Object 외의 클래스 타입과 배열 타입
Serializable과 Cloneable외의 인터페이스 타입과 배열 타입
관련 없는 클래스들
'관련 없다(unrelated)' : String과 Throwable처럼 상/하위 관계가 아닌 두 클래스
어떤 객체도 관련 없는 두 클래스의 공통 인스턴스가 될 수 없습니다.
String.java에 있는 contentEquals는???
String.java에는 파라미터 개수마저 똑같은 오버로딩된 메소드가 있네요.
public boolean contentEquals(StringBuffer sb)
public boolean contentEquals(CharSequence cs)
하지만!!
// 내부적으로 호출합니다.
// 즉, 두 메소드는 동일한 역할을 합니다.
public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence)sb);
}
어떤 다중정의 메서드가 불릴진 몰라도, 기능이 똑같으면 괜찮습니다.
상대적으로 더 특수한 오버로딩 메서드에서 덜 특수한(더 일반적인) 오버로딩 메서드로 일을 forwarding하는 것입니다.
이 프로그램은 무엇을 출력할까?
classify
중 어느 메서드를 호출할지는 컴파일 타임에 정해지기 때문입니다.재정의(overriding) 메서드 vs 다중정의(overloading) 메서드
class SparkingWine extends Wine { @Override String name() {return "발포성 포도주"; } }
class Champagne extends SparkingWine { @Override String name() {return "샴페인"; } }
public class Overriding { public static void main(String[] args) { List wineList = List.of(new Wine(), new SparkingWine(), new Champagne());
}
overloading이 혼란을 일으키지 않도록 하자.
53 에 예외가 있습니다.
overloading 대신 메서드 이름을 다르게 짓는 건 어떨까?
ObjectOutputStream
의write~()
메서드는 이름을 모두 다르게 지었습니다.ObjectInputStream
의read~()
메서드는readInt(), readLong(), readFloat(), ...
입니다.생성자는 이름을 다르게 못 짓는데 어떡하지?
매개변수가 근본적으로 다르면(radically different) 된다.
하지만 오토박싱이 도입된다면 어떨까!
set.remove(i)
의 시그니처는remove(Object)
list.remove(i)
는 overload된remove(int index)
를 선택remove
는 지정한 위치의 원소를 제거합니다.[-2, 0, 2]
가 남았습니다.또 람다와 메서드 참조도 도입된다면 어떨까!
Thread 생성자
와ExecutorService.submit
둘다Runnable
를 받는 형제 메서드를 overload하는데, 왜 컴파일 오류가 발생할까요?ExecutorService.submit
의 overload된 메서드 중에는Callable
를 받는 메서드도 있기 때문입니다. (#44)println
은void
형이므로, 반환값이 있는Callable
와 헷갈릴 수가 있는건가요?println
)과 호출한 메서드(submit
) 양쪽 다 오버로딩된 상태이기 때문!'근본적으로 다른' 것들
Object
외의 클래스 타입과 배열 타입Serializable
과Cloneable
외의 인터페이스 타입과 배열 타입String
과Throwable
처럼 상/하위 관계가 아닌 두 클래스String.java
에 있는contentEquals
는???String.java
에는 파라미터 개수마저 똑같은 오버로딩된 메소드가 있네요.public boolean contentEquals(StringBuffer sb)
public boolean contentEquals(CharSequence cs)
String.valueOf
public static String valueOf(char data[]) { return new String(data); }