오버로딩과 오버라이딩은 개발자 면접 질문을 검색해보면 항상 들어있는 녀석들이다.
상속을 사용하며 오버라이딩은 자주 사용했지만,
오버로딩은 정의 정도만 달달 외우고 가끔씩 사용했기 때문에 주의점에 대해 깊게 생각해본 적이 있었나 싶다.
여러 우회할 수 있는 방법들이 있기 때문에 지나쳐왔지만 한번 짚어보고 가도록 하자.
오버로딩 문제의 예제코드
static class CollectionClassifier {
public static String classify(Set<?> s) {
return "집합";
}
public static String classify(List<?> s) {
return "리스트";
}
public static String classify(Collection<?> s) {
return "그 외 컬렉션";
}
}
@Test
public void collectionClassifierTest() {
Collection<?>[] collections = {
new HashSet<>(),
new ArrayList<>(),
new HashMap<>().values()
};
for (Collection<?> collection : collections) {
System.out.println(CollectionClassifier.classify(collection));
}
}
실행결과는 "그 외 컬렉션" 만 3번 출력된다.
오버로드된 메서드 중 어느 메서드를 실행할지는 컴파일 타입에 결정되기 때문에 캐스팅이 이루어지지 않는 컴파일 타임의 매개변수를 기준으로 항상 Collection<?>을 받는 메서드만 호출된다.
override 한 메서드는 동적으로 선택되고 overload 한 메서드는 정적으로 선택된다.
오버로딩 안쓰고 문제 해결하기
static class CollectionClassifier2 {
public static String classify(Collection<?> s) {
if (s instanceof Set) {
return "집합";
}
else if (s instanceof List) {
return "리스트";
}
return "그 외 컬렉션";
}
}
@Test
public void collectionClassifierTest2() {
Collection<?>[] collections = {
new HashSet<>(),
new ArrayList<>(),
new HashMap<>().values()
};
for (Collection<?> collection : collections) {
System.out.println(CollectionClassifier2.classify(collection));
}
}
instanceof 연산자를 이용해 문제를 해결할 수 있겠다.
다중 정의에서 주의할 점
1. 다중 정의가 혼동을 일으키는 상황을 피하자.
2. 매개변수가 같은 다중 정의는 만들지 말자.
3. 이러한 상황에서는 다중 정의 대신 메서드 이름을 다르게 짓자.
생성자의 경우에는 1개 이상이라면 무조건 다중정의가 되어버린다. 이 경우에는 정적 팩터리를 대안으로 사용할 수 있겠다.
다중 정의의 함정 1 : 오토박싱
@Test
public void boxingTest() {
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("set = " + set);
System.out.println("list = " + list);
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove(i);
}
System.out.println("set = " + set);
System.out.println("list = " + list);
}
set = [-3, -2, -1, 0, 1, 2]
list = [-3, -2, -1, 0, 1, 2]
set = [-3, -2, -1]
list = [-2, 0, 2]
set의 결과는 의도와 같지만 list의 결과는 의도와 다르다.
list 는 remove(Object o), remove(int i) 두가지 메서드가 다중 정의되어 있기 때문이다.
의도한 것은 첫번째 메서드인데, 두번째 메서드가 적용되었다.
set에는 remove(Obejct o) 만이 존재한다.
이럴 때에는 정상작동하도록 캐스팅 해주도록 하자.
@Test
public void boxingTest2() {
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("set = " + set);
System.out.println("list = " + list);
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove((Integer) i);
}
System.out.println("set = " + set);
System.out.println("list = " + list);
}
다중 정의의 함정 2 : 람다와 메서드 참조
람다와 메서드 참조는 가독성을 높여줄 수 있는 부분이지만 참 여러 부분에서 신경을 쓰이게 한다.
맛있게 코드를 짜기 위해선 알아야 할 게 많다..ㅠㅠ
@Test
public void test1() {
new Thread(System.out::println).start();
}
@Test
public void test2() {
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(System.out::println);
}
test1 에선 Thread() 에서 함수형 인터페이스인 Runnable 을 받기 때문에, 위와 같이 메서드 참조를 인수로 주었다.
test2 의 경우 exec.submit() 에서도 Runnable을 받지만 또 다른 함수형 인터페이스인 Callable 도 받기 때문에
컴파일러는 어떤 것을 선택해야 할 지 알 수 없어 진다.
이러한 모호성 때문에 컴파일 에러를 발생시킬 수 있다.
캐스팅을 이용하여 사용하던가 람다 표현싣을 사용하여 명확하게 표현해주는 방법을 사용하여 해결해 줄 수 있겠다.
exec.submit((Runnable) System.out::println);
exec.submit(() -> System.out.println());
다중 정의를 허용한다고 해서 남발하지 말자.
또한 매개변수 수가 같은 다중 정의는 만들지 않도록 주의하자.
'JAVA > 이펙티브 자바' 카테고리의 다른 글
Java. 예외처리 주의점 (1) | 2024.03.07 |
---|---|
Java. 명명규칙은 통용되는 것으로 지키자. (1) | 2024.03.06 |
Java. 불변 사용 시 적시에 방어적 복사본을 만들자 (0) | 2024.03.06 |
Java. 스트림이란? (2) | 2024.01.02 |
43. 람다보다는 메서드 참조를 사용하라 ( 람다 표현식과 메서드 참조 ) (0) | 2023.08.02 |