-
제네릭 - 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. 조슈아 블로크 저자(글) · 개앞맵시(이복연) 번역
728x90'Study > Effective Java' 카테고리의 다른 글
제네릭 - Item 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) 2023.01.10 제네릭 - 아이템 29. 이왕이면 제네릭 타입으로 만들라 (0) 2023.01.05 제네릭 - 아이템 28. 배열보다는 리스트를 사용하라 (0) 2023.01.04 제네릭 - 아이템 27. 비검사 경고를 제거하라 (0) 2023.01.04 제네릭 - 아이템 26. 로 타입은 사용하지 말라 (0) 2023.01.03