티스토리 뷰
다중 정의 (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 |
- Total
- Today
- Yesterday
- HTTP 완벽가이드
- JS 딥다이브
- 드림코딩
- BOJ
- js api
- Spring Security
- 프로그래머스 SQL
- 김영한 http
- HTTP 완벽 가이드
- js promise
- GCP
- 이펙티브자바 아이템60
- 킹수빈닷컴
- java
- 이펙티브자바 아이템59
- 이펙티브자바 스터디
- 이펙티브자바
- 백준
- REST API
- JPA 연관관계 매핑
- dreamcoding
- 집 구하기
- http
- 가상 면접 사례로 배우는 대규모 시스템 설계 기초
- 모던자바스크립트
- js array
- 프로그래머스
- 백기선 스터디
- 패스트캠퍼스 컴퓨터공학 완주반
- 김영한 JPA
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |