티스토리 뷰
//컬렉션 분류기 - 오류 ! 이 프로그램은 무엇을 출력할까?
public class CollectionClassifier {
public static String classify(Set<?> s) {
return "집합";
}
public static String classify(List<?> lst) {
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)된 세 메서드 중 어느 메서드를 호출할지가 컴파일타임에 정해지기 때문이다. (컴파일타임에는 for문 안의 c가 항상 Collection<?> 타입)
재정의(overriding)한 메서드는 동적으로 선택되고(런타임 타입 기준), 다중정의(overloading)한 메서드는 정적으로 선택됨 (컴파일타임 타입 기준)
재정의(overriding)
class Wine {
String name() { return "포도주"; }
}
class SparklingWine extends Wine {
@Override String name() { return "발포성 포도주"; }
}
class Champagne extends Wine {
@Override String name() { return "샴페인"; }
}
public class Overridings {
public static void main(String[] args) {
List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne());
for (Wine wine : wineList) {
System.out.println(wine.name());
}
}
}
- 메서드 재정의란 상위 클래스가 정의한 것과 똑같은 시그니처의 메서드를 하위 클래스에서 다시 정의한 것
- 하위 클래스에서 재정의한 메서드를 호출하면 그 메서드가 실행됨
- 위 코드는 “포도주”, “발포성 포도주", “샴페인" 을 출력 → 가장 하위에서 정의한 재정의 메서드가 실행됨
다중정의(overloading)
다중정의는 재정의와 다르게 컴파일타임 시의 타입에 의해 호출되기 때문에 instanceof로 명시적으로 검사하면 해결된다.
public static String classify(Collection<?> c) {
return c instanceof Set ? "집합" :
c instanceof List ? "리스트" : "그 외";
}
- API 사용자가 매개변수를 넘기면서 어떤 다중정의 메서드가 호출될지를 모른다면 프로그램이 오작동하기 쉬움 → 다중정의가 혼돈을 주는 상황을 피해야함
- 안전하고 보수적으로 가려면 매개변수 수가 같은 다중정의는 만들지 말자.
- 가변인수를 사용하는 메서드라면 다중정의를 아예 하지 말아야 한다.
- 다중정의 대신 메서드명을 다르게 지어주는 것도 고려해보자. ex) ObjectOutputStream 클래스의 writeBoolean(boolean), writeInt(int), writeLong(long)
- 생성자는 이름을 다르게 지을 수 없으니 두번째 생성자부터는 무조건 다중정의 → 대안 : 정적 팩터리
- 매개변수 수가 같은 다중정의 메서드가 많더라도, 각자 어떤 매개변수 집합을 처리할지가 명확하다면 헷갈릴 일이 없을 것
- 명확하다 = 근본적으로 다르다 = 두 타입의 값을 서로 어느쪽으로든 형변환 불가
- 이 조건이 충족하면 매개변수들의 런타임 타입만으로 메서드 결정 가능
- ex) ArrayList 의 int를 받는 생성자와 Collection을 받는 생성자 (헷갈릴 일 없음)
오토박싱의 도입으로 혼란 증가
자바5부터 오토박싱이 도입되면서 혼란이 증가하였다.
List의 다중정의된 메서드 remove(Object)와 remove(int)가 원래는 Object와 int가 근본적으로 달라서 문제가 없었다.
그런데, 제네릭과 오토박싱의 등장으로 두 매개변수 타입이 더는 근본적으로 다르지 않게 됨 (int를 Integer로 자동변환 해주기 때문)
다행히 같은 피해를 입은 API는 거의 없다.
두 메서드를 완전히 구분하기 위해서 remove(Object)에는 박싱타입으로 변환하여 넘겨야 한다.
for (int i = 0; i < 3; i++ ){
set.remove(i);
list.remove((Integer) i); // remove(Object o) 가 호출됨
}
람다와 메서드 참조 도입으로 혼란 증가
자바8부터 람다와 메서드 참조가 도입되면서 혼란이 증가하였다.
new Thread(System.out::println).start(); //컴파일 성공
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(System.out::println); //컴파일 에러
submit 다중정의 메서드 중에 Callabel 를 받는 메서드가 있어 컴파일 에러가 남
println 메서드들은 모두 void만 반환하는데 왜 헷갈려할까?
→ submit 메서드와 println 메서드는 둘다 다중정의되어, 다중정의 해소 알고리즘이 우리 기대처럼 동작하지 않음
다중정의된 메서드나 생성자들이 함수형 인터페이스를 인수로 받을 때, 비록 서로 다른 함수형 인터페이스라도 인수 위치가 같으면 혼란이 생김
메서드를 다중정의할 때, 서로 다른 함수형 인터페이스라도 같은 위치의 인수를 받으며 안됨
결론
일반적으로 매개변수 수가 같을 때는 다중정의를 피하는게 좋다.
그러나 생성자라면 마냥 피하기가 어려울 수 있다.
이 때는 헷갈릴만한 매개변수는 형변환하여 정확한 다중정의 메서드가 선택되도록 해야한다.
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] 54.null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2022.04.04 |
---|---|
[Effective Java] 53.가변인수는 신중히 사용하라 (0) | 2022.04.04 |
[Effective Java] 51.메서드 시그니처를 신중히 설계하라 (0) | 2022.04.04 |
[Effective Java] 50.적시에 방어적 복사본을 만들라 (0) | 2022.04.04 |
[Effective Java] 49.매개변수가 유효한지 검사하라 (0) | 2022.04.04 |