복잡한뇌구조마냥

[JAVA] Effective Java - 다중정의는 신중히 사용하라 (52) 본문

BE/JAVA

[JAVA] Effective Java - 다중정의는 신중히 사용하라 (52)

지금해냥 2025. 8. 24. 21:38

1. 재정의 vs 다중정의

  • 재정의(Overriding)
    • 상위 클래스의 메서드와 시그니처가 동일할 때 하위 클래스에서 다시 정의하는 것.
    • 호출 대상은 런타임 시점 객체의 실제 타입으로 결정 → 동적 바인딩.
  • 다중정의(Overloading)
    • 메서드 이름은 같지만, 매개변수 시그니처가 다른 여러 메서드를 정의하는 것.
    • 호출 대상은 컴파일 타임에 매개변수의 정적 타입으로 결정 → 정적 바인딩.
 
class Parent {
    void hello(Object o) { System.out.println("Object"); }
    void hello(String s) { System.out.println("String"); }
}

Object obj = "text";
Parent p = new Parent();
p.hello(obj); // "Object" — 런타임 타입은 String이지만, 정적 타입 Object 기준으로 선택됨

👉 오버로드는 다형성처럼 동작하지 않으며, 호출 결과가 직관과 달라질 수 있다.


2. 다중정의가 위험한 이유

  • 매개변수의 정적 타입이 기준이므로, 런타임 타입은 전혀 고려되지 않는다.
  • null 인수는 모호성을 유발한다. (m(String), m(Integer) m(null) 컴파일 에러)
  • 오토박싱, 제네릭, varargs가 얽히면 더 복잡해지고 예측하기 힘들다.
  • 자바 8부터 람다/메서드 참조가 추가되면서 혼동이 심화.
    • 서로 다른 함수형 인터페이스를 받는 오버로드는 람다식으로 호출 시 모호하거나 의도치 않은 버전이 선택될 수 있다.
  • List.remove() 사례
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
list.remove(1);                   // index 1 제거
list.remove(Integer.valueOf(1));  // 값 1 제거

remove(int) vs remove(Object) 오버로드 혼란.

 

3. “근본적으로 다른 타입” 조건

다중정의가 안전하려면 매개변수 타입이 근본적으로 달라야 한다.

  • 기본 타입 vs 참조 타입
  • 클래스 타입 vs 배열 타입
  • 인터페이스 타입 vs 배열 타입
  • 상하위 관계가 없는 두 클래스(unrelated)

⚠️ 하지만, 자바 5부터 오토박싱제네릭이 도입되면서 이 경계가 흐려졌다.

  • int vs Integer는 사실상 혼동 가능.
  • List<Object> vs List<String>도 제네릭 소거 때문에 근본적으로 다르지 않음.

4. 다중정의 사용 가이드

  • 매개변수 개수가 다른 경우는 비교적 안전하다.
  • 같은 개수라면, 혼동 가능성이 있다면 메서드 이름을 다르게 짓는 것이 바람직하다.
    • write(String)writeText
    • write(byte[])writeBytes
  • 생성자는 이름을 바꿀 수 없으므로 필연적으로 오버로드된다.
    • 이 경우 정적 팩터리 메서드(of(), from(), valueOf() 등)를 고려하라.
  • 애매하다면, instanceof 검사 + 명시적 형변환으로 하나의 메서드로 통합하는 것도 방법.
  • 람다/메서드 참조를 인수로 받는 경우, 같은 위치에서 서로 다른 함수형 인터페이스를 인자로 받는 오버로드는 만들지 말 것.

5. 실무 팁

  • 컴파일러 옵션: -Xlint:overloads를 켜면 혼동 가능성이 있는 다중정의를 경고한다.
  • 테스트 필수: null, 경계 값, 람다 등 애매한 케이스를 반드시 단위 테스트에 포함.
  • API 안정성: 오버로드가 불려도 결과가 항상 같다면 안전하다.
  • 그렇지 않다면 다중정의를 억지로 쓰지 말고, 메서드명을 분리하거나 정적 팩터리를 제공하는 게 더 명확하다.

6. 결론

다중정의는 편리하지만, 호출 규칙은 정적이고 매우 복잡하다.

  • 혼란을 일으킬 수 있는 오버로드는 피하라.
  • 의미가 다르면 이름을 분리하라.
  • 생성자 오버로드는 정적 팩터리로 대체할 수 있다.
  • 함수형 인터페이스, 오토박싱, 제네릭과 섞이면 특히 조심하라.

언어가 다중정의를 허용한다고 해서 반드시 써야 하는 것은 아니다.
가장 중요한 원칙: 읽는 사람이 혼동하지 않는 API를 제공하라.

LIST