베어_
TechBear
베어_
전체 방문자
오늘
어제
  • 분류 전체보기 (336)
    • Spring (33)
      • 개념 (13)
      • Security (5)
      • 실습 (1)
      • 토비 스프링 (11)
    • JPA (6)
    • 프로젝트 기록 (24)
    • DB (13)
    • JAVA (18)
    • 알고리즘 (50)
      • 유형정리 (8)
      • Baekjoon (21)
      • LeetCode (18)
    • 디자인패턴 (0)
    • 개발서적 (79)
      • Effective Java (78)
      • 객체지향의 사실과 오해 (1)
    • 독후감 (4)
    • 보안 (2)
    • 운영체제(OS) (53)
      • 공룡책 (53)
    • 컴퓨터 네트워크 (28)
      • 컴퓨터 네트워크 하향식 접근 (23)
    • 자료구조 (1)
    • DevOps (2)
    • 앱 개발 (20)
      • 안드로이드 스튜디오 (20)

블로그 메뉴

    공지사항

    인기 글

    태그

    • 데이터베이스
    • 이펙티브자바
    • java
    • 자바8
    • 스프링
    • BFS
    • 자바
    • leetcode
    • jpa
    • 스프링시큐리티
    • dfs
    • 토비스프링
    • 백준
    • 코드업
    • 운영체제
    • 함수형인터페이스
    • 알고리즘
    • 스레드
    • Spring
    • C++

    최근 댓글

    최근 글

    티스토리

    hELLO · Designed By 정상우.
    베어_

    TechBear

    개발서적/Effective Java

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

    2023. 6. 2. 05:35

    다중정의를 잘못한 컬렉션 분류기

    다음 컬렉션 분류기는 "집합", "리스트", "그 외"를 차례로 출력할 것 같지만, "그 외"만 세번 연달아 출력한다.

    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));
        }
    }

    그 이유는 다중정의(오버로딩) 된 세 classify 중 어느 메서드를 호출할지가 컴파일타임에 정해지기 때문이다. 컴파일타임에는 for 문 안의 c는 항상 Collection<?>타입이다. 이처럼 직관과 어긋나는 이유는 재정의한 메서드는 동적으로 선택되고, 다중정의한 메서드는 정적으로 선택되기 때문이다.

    재정의한 컬렉션 분류기

    public static String classify(Collection<?> c) {
        return c instanceof Set ? "집합" :
               c instanceof List ? "리스트" : "그 외";
    }

    프로그래머에게는 재정의가 정상적인 동작 방식이고, 다중정의가 예외적인 동작으로 보인다. API가 사용자가 매개변수를 넘기면서 어떤 다중정의 메서드가 호출될지 모른다면 프로그램이 오동작하기 쉽다.

    다중정의가 혼란을 주는 상황을 방지하자

    1. 안전하고 보수적으로 가려면 매개변수 수가 같은 다중정의는 만들지 말자. 가변인수를 사용하는 메서드라면 다중정의를 아예 하지 말아야 한다.
    2. 다중정의하는 대신 메서드 이름을 다르게 지어주자.
      ex ) writeBoolean, writeInt, writeLong과 같은 형태의 메서드 이름은 read 메서드의 이름과 짝을 맞추기 좋다. readBoolean, readInt 등
    3. 생성자의 경우 정적 팩터리를 사용할 수 있다.
      -> 생성자는 이름을 다르게 지을 수 없으므로 두 번째 생성자부터는 무조건 다중정의가 된다. 이때, 정적 팩터리 메서드를 이용할 수 있다.

    오토박싱의 도입과 다중정의

    다음은 오토박싱의 도입으로 기존에 존재하던 다중정의 메서드가 문제가 된 케이스이다.

    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);
        }
    }

    set.remove(i)의 시그니처는 remove(Object)이지만 list.remove(i)는 다중정의된 remove(int index)를 선택한다. 이를 해결하기 위해서는 Integer로 형변환하여 올바른 다중정의 메서드를 선택하게 하면 해결된다.

    list.remove((Integer) i);

    제네릭이 도입되기 전인 자바 4까지의 List에서는 Object와 int가 근본적으로 달라서 문제가 없었지만 제네릭과 오토박싱이 등장하면서 두 메서드의 매개변수 타입이 더는 근본적으로 다르지 않게 되었다.

    람다와 메서드 참조 그리고 다중정의

    // 1번. Thread의 생성자 호출
    new Thread(System.out::println).start();
    
    // 2번. ExecutorService의 submit메서드 호출
    ExecutorService exec = Executors.newCachedThreadPool();
    exec.submit(System.out::println);

    1번과 2번이 모습은 비슷하지만, 2번만 컴파일 오류가 난다. 원인은 submit 다중정의 메서드 중에 Callable를 받는 메서드가 있기 때문이다. 이 경우는 참조된 메서드(println)과 호출한 메서드(submit) 양쪽 다 다중정의되어, 다중정의 해소 알고리즘이 우리의 기대처럼 동작하지 않은 상황이다.

    핵심은 다중정의된 메서드(혹은 생성자)들이 함수형 인터페이스를 인수로 받을 때, 비록 서로 다른 함수형 인터페이스라도 인수 위치가 같으면 혼란이 생긴다. 따라서 메서드를 다중정의할 때, 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안된다.

    예외

    어떤 다중정의 메서드가 불리는지 몰라도 기능이 똑같다면 신경 쓸 게 없다. 이렇게 하는 가장 일반적인 방법은 상대적으로 더 특수한 다중정의 메서드에서 덜 특수한 다중정의 메서드로 일을 넘겨버리는(forward)것이다.

    정리

    1. 일반적으로 매개변수가 수가 같을 때는 다중정의를 피하는 게 좋다.
    2. 1번이 불가능하다면 헷갈릴 만한 매개변수는 형변환하여 정확한 다중정의 메서드가 선택되도록 해야 한다.
    3. 2번이 불가능하다면 기존 클래스를 수정해 새로운 인터페이스를 구현해야 할 때는 같은 객체를 입력받는 다중정의 메서드들이 모두 동일하게 동작하도록 만들어야 한다.
    저작자표시 비영리 변경금지 (새창열림)
      '개발서적/Effective Java' 카테고리의 다른 글
      • [Effective Java] null이 아닌, 빈 컬렉션이나 배열을 반환하라
      • [Effective Java] 가변인수를 신중히 사용하라
      • [Effective Java] 메서드 시그니처를 신중히 설계하라
      • [Effective Java] 적시에 방어적 복사본을 만들라
      베어_
      베어_
      Today I learned | 문제를 해결하는 개발자

      티스토리툴바