[ Item 52 ] 다중정의는 신중히 사용하라

둥그냥·2022년 5월 22일
0

Effective Java 독서

목록 보기
9/15

📚 [ Item 52 ] 다중정의는 신중히 사용하라

다중정의된 메서드 호출 메커니즘

public class CollectionClassifier {
    public static String classify(Set<?> s) {
        return "집합";
    }

    public static String classify(List<?> lst) {
        return "리스트";
    }

    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<?> 타입이다.
  • 재정의한 메서드는 동적으로 선택되고, 다중정의한 메서드는 정적으로 선택된다.

재정의된 메서드 호출 메커니즘

class Wine {
    String name() { return "포도주"; }
}

class SparklingWine extends Wine {
    @Override String name() { return "발포성 포도주"; }
}

class Champagne extends SparklingWine {
    @Override String name() { return "샴페인"; }
}

public class Overriding {
    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());
    }
}
  • 예상한 것처럼 "포도주", "발포성 포도주", "샴페인"을 차례로 출력함
  • 가장 하위에서 정의한 재정의 메서드가 실행됨

주의

  • 다중정의가 혼동을 일으키는 상황을 피해야 한다.
  • 안전하고 보수적인 방법
    • 매개변수 수가 같은 다중정의는 만들지 말자
    • 가변인수를 사용하는 메서드라면 다중정의를 아예 하지 말아야 한다.
    • 다중정의하는 대신 메서드 이름을 다르게 지어주는 길도 열려 있다.

ObjectOutputStream 클래스

ObjectOutputStream 클래스의 write 메서드는 모든 기본 타입과 일부 참조 타입용 변형을 가지고 있다.

  • 다중정의가 아닌 모든 메서드에 다른 이름을 지어주는 길을 택함
  • writeBoolean(boolean), writeInt(int), writeLong(long) 같은 식
  • 이 방식이 다중정의보다 나은 점
    • read 메서드의 이름과 짝을 맞추기 좋다 (readBoolean(), readInt() ... )

매개변수 수가 같은 다중정의 메서드

  • 그중 어느 것이 주어진 매개변수 집할을 처리할지가 명확히 구분된다면 헷갈릴 일이 없다
    • 즉 매개변수 중 하나 이상이 근본적으로 다르다면 헷갈릴 일이 없다
    • 근본적으로 다르다는 건 두 타입의 (null이 아닌) 값을 서로 어느 쪽으로든 형변환 할 수 없다.
  • 이 조건만 충족하면 어느 다중정의 메서드를 호출할지가 매개변수들의 런타임 타입만으로 결정된다.
  • 예컨대 ArrayList에는 int를 받는 생성자와 Collection을 받는 생성자가 있는데, 어떤 상황에서든 어느 것이 호출될지 헷갈릴 일이 없다.

오토박싱

public class SetList {

    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][-3. -2, -1]
    출력 결과 값
  • [-3,-2,-1][0,1,2]

왜 일까?

  • set.remove(i)의 시그니처는 remove(Object)다.
  • 한 편 list.remove(i)는 다중정의된 remove(int index)를 선택한다.
    • 이 remove는 '지정된 위치'의 원소를 제거하는 기능을 수행한다.

해결방안

list.remove의 인수를 Onteger로 형변환하여 사용하면 된다.
혹은 Integer.valueOf를 이용해 i를 Integer로 변환 후 전달해도 된다.

for (int i = 0; i < 3; i++) {
	set.remove(i);
    list.remove((Integer) i); // 혹은 remove(Ineger.valueOf(i))
}
  • 이렇게 혼란스러운 이유는 List<E> interface가 remove(Object)와 remove(int)를 다중정의 했기 때문이다.
  • 제네릭이 도입되기 전 자바 4까지에서는 Object와 int가 근본적으로 달라서 문제가 없었다.
  • 제네릭과 오토박싱이 등장하면서 더는 근본적으로 다르지 않게 되었다.

💡 핵심 정리

  • 프로그래밍 언어가 다중정의를 허용한다고 해서 다중정의를 꼭 활용하란 뜻은 아니다
  • 일반적으로 매개변수 수가 같을 때는 다중정의를 피하는 게 좋다.
  • 헷갈릴 만한 매개변수는 형변환하여 정확한 다중정의 메서드가 선택되도록 해야한다
  • 이것이 불가능하다면, 같은 객체를 입력받는 다중정의 메서드들이 모두 동일하게 동작하도록 만들어야 한다.

0개의 댓글