Home [Effective Java] Item52. 다중정의는 신중히 사용하라!
Post
Cancel

[Effective Java] Item52. 다중정의는 신중히 사용하라!

Item52. 다중정의는 신중히 사용하라!

다중정의(overloading)

  • 메서드 명은 같지만 매개변수의 개수, 타입, 순서를 다르게 하여 선언하는 것을 말한다.

메서드 실행 시점

오버로딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CollectionClassifier {
	public static String classify(Set<?> s) {
		return "Set";
	}
	public static String classify(List<?> lst) {
		return "List";
	}
	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));
	}
}
  • 어느 메서드를 호출할지가 컴파일타임에 정해진다.
  • ‘그 외’만 연달아 세 번 출력한다.
    • for문 안의 c는 항상 Collection 타입이다. 따라서 컴파일타임의 매개변수 타입을 기준으로 항상 세 번째 메서드가 호출된다.
  • 이처럼 직관에 어긋나는 이유는 재정의한 메서드는 동적으로 선택되고, 다중정의한 메서드는 정적으로 선택되기 때문이다.
1
2
3
4
public static String classify(Collection<?> c) {
  return c instanceof Set ? "Set" :
  c instanceof List ? "List" : "Unknown Colleciton";
}
  • classify 메서드를 하나로 합치고, instanceof로 명시적 형검사를 통해 해결할 수 있다.

오버라이딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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());
	}
}
  • 어느 메서드를 호출할지가 런타임에 정해진다.
  • ‘와인’, ‘스파클링 와인’, ‘샴페인’이 차례대로 출력된다.

다중정의 시 주의할 점

  • 다중정의가 혼동을 일으키는 상황을 피해야 한다.

되도록 매개변수 개수가 같은 다중정의는 만들지 말자.

  • 가변인수(varargs)를 사용하는 메서드라면 다중정의를 아예 하지 말아야 한다.
    • 이 규칙을 따른다면 어떤 다중정의 메서드가 호출될지 헷갈릴 일은 없을 것이다.
  • 다중정의 대신 메서드 이름을 다르게 지어주는 길도 항상 열려있다.

매개변수 개수가 같은 다중정의를 피할 수 없는 경우

  • 생성자는 이름을 다르게 지을 수 없다.
    • 두 번째 생성자부터는 무조건 다중정의가 된다.
    • 정적 팩터리라는 대안을 활용할 수 있는 경우가 많다.
  • 여러 생성자가 같은 수의 매개변수를 받아야 하는 경우를 완전히 피해갈 수는 없다. 안전 대책을 세워야 한다.
    • 매개변수 수가 같은 다중정의 메서드가 많더라도, 그중 어느 것이 주어진 매개변수 집합을 처리할지가 명확히 구분된다면 헷갈릴 일은 없을 것이다.
    • 즉, 매개변수 중 하나 이상이 ‘근본적으로 다르다(radically different)’면 헷갈릴 일이 없을 것이다.
      • 근본적으로 다르다는 것? 두 타입의 (null이 아닌) 값을 서로 어느 쪽으로든 형변환할 수 없다는 뜻.

매개변수가 근본적으로 달라도 안전하지 않은 경우

1
2
List<Integer> list = Arrays.asList(1, 2, 3);
list.remove(1);
  • List 인터페이스가 remove(int index)와 remove(Object o)를 오버로딩 했다.

    • remove(1)은 1번 index의 값을 지우라는 것일까? 아님 1인 값을 지우라는 것일까? 애매하다.
  • 제네릭과 오토박싱이 등장하면서 Object와 int 타입이 근본적으로 다르지 않게 되었다.

  • 위 예제의 경우 remove(int index)를 선택하게 된다.

  • 만약 1을 지우고 싶다면 명시적으로 형변환하여 처리하면 된다.

    1
    
    list.remove((Integer) 1);
    
  • 다중정의된 메서드(혹은 생성자)들이 함수형 인터페이스를 인수로 받을 때, 비록 서로 다른 함수형 인터페이스라도 인수 위치가 같으면 혼란이 생긴다.

오버로딩 시, 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안 된다.

  • 서로 다른 함수형 인터페이스라도 서로 근본적으로 다르지 않다는 것이다.
1
2
3
4
new Thread(System.out::println).start(); // 컴파일 성공

ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(System.out::println); // 컴파일 에러
  • 넘겨진 인수는 System.out::println으로 서로 같다. 둘 다 Runnable을 받는 형제 메서드를 오버로딩 하고 있다.
  • 다중정의 해소가 되지 않아 컴파일 에러가 난다.
  • System.out::println는 부정확한 메서드 참조다.

핵심 정리

  • 프로그래밍 언어가 다중정의를 허용한다고 해서 다중정의를 꼭 활용하란 뜻은 아니다.
  • 일반적으로 매개변수 수가 같을 때는 다중정의를 피하는 게 좋다.
  • 상황에 따라, 특히 생성자라면 이러한 조언을 따르기 힘들 수 있는데, 그럴 때는 헷갈릴 만한 매개변수는 형변환하여 정확한 다중정의 메서드가 선택되도록 하자.
    • 이것이 불가능하다면, 예를 들어 기존 클래스를 수정해 새로운 인터페이스를 구현해야 하는 경우, 같은 객체를 입력받는 다중정의 메서드들이 모두 동일한 동작을 하도록 구성하자.
    • 그렇지 못하면 프로그래머는 다중정의 메서드 혹은 생성자를 효과적으로 쓰지 못할 것이고, 오동작의 이유도 이해하지 못할 것이다.
This post is licensed under younghwani by the author.

[Effective Java] Item51. 메서드 시그니처를 신중히 설계하라!

[Effective Java] Item53. 가변인수는 신중히 사용하라!

Comments powered by Disqus.