ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 제네릭 - item 30. 이왕이면 제네릭 메서드로 만들라
    Study/Effective Java 2023. 1. 5. 17:43

    이왕이면 제네릭 메서드로 만들라

    제네릭 메서드
    public static Set union(Set s1, Set s2) {
        Set result = new HashSet(s1);
        result.addAll(s2);
        return result;
    }

    클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다. 제네릭 메서드 작성법은 제네릭 타입 작성법과 비슷하다. 위 메서드는 두 집합의 합집합을 반환하는 문제가 있는 메서드이다. 위 메서드는 컴파일은 되지만, 문제가 있는 메서드이다.

    경고를 없애려면 이 메서드를 타입 안전하게 만들어야 한다. 메서드 선언에서의 세 집합(입력 2개, 반환 1개)의 원소 타입을 타입 매개변수로 명시하고, 메서드 안에서도 이 타입 매개변수만 사용하게 수정하면 된다.

     

    public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

    위 코드는 앞선 union() 메서드를 제네릭 메서드로 변환한 코드이다. 단순한 제네릭 메서드라면 이 정도면 충분하다. 이 메서드 경고 없이 컴파일되며, 타입 안전하고, 쓰기도 쉽다.

    다만, 메서드에 제네릭을 사용할 때는 메서드의 제한자와 반환 타입 사이에 타입 매개변수 목록을 넣어주어야 한다는 것을 잊지 말아야 한다.

     

    public static Set<E> union(Set<E> s1, Set<E> s2) {
        ...
    }

    예를 들어, 앞선 union() 메서드에서 위 코드와 같이 static과 Set<E> 사이에 <E>를 쓰지 않게 되면 Cannot resolve symbol 'E'라는 오류가 뜨며 컴파일할 수 없게 된다.

    만약, 메서드에 여러 개의 서로 다른 제네릭 타입을 사용해야 한다면 사용하는 모든 타입 매개변수를 <> 안에 명시해주어야 한다.

    단, 클래스가 제네릭을 사용하는 클래스라면, 메서드에서 같은 제네릭을 사용할 때는 타입 매개변수를 지정해주지 않아도 된다.

     

    public static void main(String[] args) {
        Set<String> set01 = Set.of("1", "2", "3");
        Set<String> set02 = Set.of("3", "4", "5");
        Set<String> result = union(set01, set02);
        System.out.println(result);
    }

    위 코드는 변환한 제네릭 메서드를 사용한 예이다. 직접 형변환하지 않아도 어떤 오류나 경고 없이 컴파일된다. 이 프로그램을 실행하면 [1, 2, 3, 4, 5]가 출력된다.

    변환한 union() 메서드는 집합 3개(입력 2개, 반환 1개)의 타입이 모두 같아야 한다. 이를 한정적 와일드카드 타입을 사용하여 더 유연하게 개선할 수 있다.

     

    제네릭 싱글톤 팩토리

    때때로 불변 객체를 여러 타입으로 활용할 수 있게 만들어야 할 때가 있다. 제네릭은 런타임 시점에 Object 타입으로 타입 정보가 소거되므로 하나의 객체를 어떤 타입으로든 매개변수화할 수 있다. 하지만 이렇게 하려면 요청한 타입 매개변수에 맞게 매번 그 객체의 타입을 바꿔주는 정적 팩토리를 만들어야 한다. 이 패턴을 제네릭 싱글톤 팩토리라고 한다.

     

    public class GenericFactoryMethod {
        private static final Set IMMUTABLE_EMPTY_SET = Set.copyOf(new HashSet());
        
        public static void main(String[] args) {
            Set<String> immutableEmptyStringSet = immutableEmptySet();   // String 타입을 가지는 불변의 빈 Set 타입 객체 생성
            Set<Integer> immutableEmptyIntSet = immutableEmptySet();   // Integer 타입을 가지는 불변의 빈 Set 타입 객체 생성
        }
        
        @SuppressWarnings("unchecked")
        public static <T> Set<T> immutableEmptySet() {
            return (Set<T>) IMMUTABLE_EMPTY_SET;
        }
    }

    immutableEmptySet() 메서드는 원소 타입으로 어떤 타입을 요청해도 불변의 빈 Set을 돌려주어야 한다. 또한, 새로운 원소가 추가되거나 할 여지도 없다. 따라서, 미리 빈 불변의 HashSet을 만들어 놓고 요청이 들어오면 반환하는 형태로 사용할 수 있다. 이때, Set 내부의 원소가 없으므로 T에 어떤 타입 요청이 오더라도 비검사 형변환을 통해 내보내도 타입 안전을 보장할 수 있다.

     

    재귀적 타입 한정

    자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정할 수 있다. 바로 재귀적 타입 한정이라는 개념이다. 재귀적 타입 한정은 주로 타입의 자연적 순서를 정하는 Comparable 인터페이스와 함께 쓰인다.

     

    Collection의 요소들 중 가장 큰 요소를 반환하는 max() 메서드를 작성한다고 하면, 매개변수로 받아오는 컬렉션의 요소들은 비교 및 정렬이 가능해야 한다. 즉, Comparable을 구현한 객체들의 컬렉션에 대해서만 max() 메서드를 사용할 수 있는데, 제네릭에 이 부분을 반드시 명시해 줄 필요가 있다.

     

    public static <E extends Comparable<E>> E max(Collection<E> c) {
        ...
    }

    위 코드를 보면, 매개변수 타입을 넣어주는 부분에서 그냥 E를 넣는 것이 아니라 <E extends Comparable<E>>를 넣어줌으로써 Comparable의 하위 구현체인 타입만 올 수 있다는 것을 명시해 주었다. 이 표현은 모든 타입 E는 자신과 비교할 수 있다는 의미이다.

     

    최종 정리

    제네릭 타입과 마찬가지로, 클라이언트에서 입력 매개변수와 반환값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 더 안전하며 사용하기도 쉽다. 타입과 마찬가지로, 메서드도 형변환 없이 사용할 수 있는 편이 좋으며, 많은 경우 그렇게 하려면 제네릭 메서드가 되어야 한다. 역시 타입과 마찬가지로, 형변환을 해줘야 하는 기존 메서드는 제네릭하게 만들어야 한다. 기존 클라이언트는 그대로 둔 채 새로운 사용자의 삶을 훨씬 편하게 만들어줄 것이다.


    출처

     이펙티브 자바 Effective Java 3/E. 조슈아 블로크 저자(글) · 개앞맵시(이복연) 번역

    https://github.com/woowacourse-study/2022-effective-java/blob/main/05%EC%9E%A5/%EC%95%84%EC%9D%B4%ED%85%9C_30/%EC%9D%B4%EC%99%95%EC%9D%B4%EB%A9%B4_%EC%A0%9C%EB%84%A4%EB%A6%AD_%EB%A9%94%EC%84%9C%EB%93%9C%EB%A1%9C_%EB%A7%8C%EB%93%A4%EB%9D%BC.md

     

    728x90

    댓글

Designed by Tistory.