-
아이템 52. 다중정의는 신중히 사용하라책/이펙티브자바 2021. 9. 16. 15:33
다중 정의 (Overroading)
// 52-1. BAD - 컬렉션 분류기 public 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 "Collection"; } 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 // Collection
Set, List, Collection 을 출력하길 기대하고 작성했지만 실제로는 Collection만 3번 출력한다.
왜냐면 다중정의 (overloading)된 세 classify 중 어느 메서드를 호출할지는 컴파일 타임에 정해지기 때문이다.
컴파일 타임에는 for 문 안의 c 는 항상 Collection<?> 타입이다.
런타임에는 매번 달라지지만, 호출할 메서드를 선택할때는 영향을 주지 못한다.
따라서 컴파일타임 기준으로 세 번째 메서드임 classify(Collection<?>) 만 호출하는 것이다.
오버로딩 메서드는 정적으로 선택한다.
// GOOD ? public static String classify(Collection<?> c) { return c instanceof Set ? "Set" : c instanceof List ? "List" : "Collection"; }
52-1 의 코드를 사용하려면 이런식으로 명시적으로 검사해서 사용해야 한다.
재정의 (Override)
// 52-2. 재정의는 동적 선택 public class Item52 { static class Wine { String name() { return "포도주"; } } static class SparklingWine extends Wine { @Override String name() { return "발포성 포도주"; } } static class Champagne extends Wine { @Override String name() { return "샴페인"; } } 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()); } } } // 포도주 // 발포성 포도주 // 샴페인
가장 하위에서 정의한 재정의 메서드가 실행된다.
오버라이딩 메서드는 동적으로 선택한다.
다중정의를 조심해서 사용하자.
- 다중정의가 혼동을 일으키는 상황을 피하자.
- 안전하고 보수적으로 가려면 매개변수 수가 같은 다중정의는 만들지 말자.
- 가변인수(varargs)를 사용하는 메서드라면 다중정의를 아예 하지말자. (Item53)
- 다중정의 대신 메서드 이름을 다르게 지어주는 길도 항상 열려 있음을 잊지말자.
메서드에 다른 이름을 주는 방식
java.io.ObjectOutputStream 클래스를 보면 write 메서드를 다중 정의가 아닌 메서드에 다른 이름 주어주는 방법을 택했다.
// ObjectOutputStream public void writeBoolean(boolean val) throws IOException { bout.writeBoolean(val); } public void writeByte(int val) throws IOException { bout.writeByte(val); } public void writeShort(int val) throws IOException { bout.writeShort(val); } public void writeChar(int val) throws IOException { bout.writeChar(val); } ...
이런식으로 이름을 다르게 지어줬는데 이 방식이 다중정의보다 나은점은 read 메서드의 이름과 짝을 맞추기 좋다는 것이다. 예를 들어 readBoolean(), readInt(), readLong() 같은 식이다.
생성자 다중정의
한편, 생성자는 이름을 다르게 지을 수 없으니 두 번째 생성자부터는 무조건 다중정의가 된다.
하지만 정적 팩터리라는 대안을 사용할 수 있다.
그래도 어쩔수 없이 같은 수의 매개변수를 받아야 하는 경우가 있을 수 있는데 그 때를 대비해서 안전대책을 배워보자.
다중정의 메서드가 많더라도, 그중 어느 것이 주어진 매개변수 집합을 처리할지가 명확히 구분된다면 헷갈릴 일은 없을 것이다. 즉, 매개변수 중 하나 이상이 "근본적으로 다르다"면 헷갈릴 일이 없다.
여기서 근본적으로 다르다라는 말은 두 타입의 값을 서로 어느 쪽으로든 형변환 할 수 없다는 뜻이다.
이 조건만 충족하면 어느 다중정의 메서드를 호출할지가 매개변수의 런타임 타입만으로 결정된다.
근본적으로 다르다의 예시) ArrayList의 인자가 1개인 생성자는 ArrayList(int initialCapacity)와 ArrayList(Collection<? extends E> c)가 있지만 int와 Collection은 근본적으로 다르므로 괜찮다
List 인터페이스에서의 다중정의
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); } // [-3, -2, -1], [-2, 0, 2]
나는 -3 ~ 2 까지 정수를 넣고, 0 ~ 2를 제거해 [-3, -2, -1] 가 출력되길 기대했지만 다른 값이 나온다.
Set 에서는 실제 정수 [0, 1, 2] 를 제거했고 List 에서는 인덱스를 제거 했다.
set.remove(i) 는 remove(Object) 이다.
다중정의된 다른 메서드가 없으니 기대한 대로 동작하여 [0, 1, 2] 제거했다.
한편, list.remove(i)는 다중정의된 remove(Object o), remove(int index) 중에서 remove(int index)를 선택해서 이런 결과가 나온 것이다.
list.remove(Integer.valueOf(i));
list.remove 부분에서의 인자를 Integer 로 바꿔서 remove(Object o)가 호출되게끔 하면 기대한대로 동작한다.
제네릭이 도입되기 전인 자바 4 까지는 List에서 Obejct와 int 가 근본적으로 달라서 문제가 없었지만 제네릭과 오토박싱이 등장하면서 매개변수 타입이 더는 근본적으로 다르지 않게 되었다.
정리하자면, 제네릭과 오토박싱을 더한 결과 List 인터페이스가 취약해졌다.
결론
- 다중정의 사용을 조심하자.
- 매개변수 수가 같을때는 다중정의를 피하자.
- 헷갈릴 만한 매개변수는 형변환하여 정확한 다중정의 메서드가 선택되도록 하자.
'책 > 이펙티브자바' 카테고리의 다른 글
아이템 60. 정확한 답이 필요하다면 float와 double은 피하라 (0) 2021.09.16 아이템 59. 라이브러리를 익히고 사용하라 (0) 2021.09.16 아이템 51. 메서드 시그니처를 신중히 설계하라 (0) 2021.09.16 아이템 60. 정확한 답이 필요하다면 float와 double은 피하라 (0) 2021.09.15 아이템 59. 라이브러리를 익히고 사용하라 (0) 2021.09.15