Item30. 이왕이면 제네릭 메서드로 만들라!
- 클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다.
- 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭
- 대표적으로
Collections
의 ‘알고리즘’ 메서드(binarySearch
,sort
등)는 모두 제네릭이다.
잘못된 사용
로 타입 사용(수용 불가함)
1
2
3
4
5
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
- 컴파일은 되지만 경고가 발생한다.
- 이 경고를 없애기 위해
type-safe
하게 만들어야 한다. - 메서드 선언에서의 원소 타입을 타입 매개변수로 명시한다. 메서드 안에서도 타입 매개변수만 사용하게 수정한다.
- 이 경고를 없애기 위해
수정 후
1
2
3
4
5
public static <E> set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
- 단순한 제네릭이라면 이 정도로 충분하다.
- 경고 없고, 컴파일 되며,
type-safe
하고, 사용도 쉽다.
- 경고 없고, 컴파일 되며,
불변 객체를 여러 타입으로 활용
- 제네릭은 런타임에 타입 정보가 소거된다.
- 하나의 객체를 어떤 타입으로든 매개변수화할 수 있다.
- 이렇게 하려면 요청한 타입 매개변수에 맞게 매번 그 객체의 타입을 변환하는 정적 팩터리가 필요하다.
- 이러한 패턴을 **제네릭 싱글턴 팩터리**라 한다.
- 하나의 객체를 어떤 타입으로든 매개변수화할 수 있다.
항등함수를 담은 클래스
- 자바 라이브러리의 Function.identity를 사용하면 쉽게 만들 수 있다.
- 항등함수 객체는 상태가 없다. 그러니 요청마다 새로 생성하는 것은 낭비다.
- 자바의 제네릭은 소거 방식을 사용했기에 싱글턴 하나면 충분하다.
1
2
3
4
5
6
public static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked") // 비검사 형변환 경고 방지
public static <T> UnaryOperator<T> identiyFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
UnaryOperator<T>
로 형변환 시, 비검사 형변환 경고가 발생한다.- T가 어떤 타입이든
UnaryOperator<Object>
는UnaryOperator<T>
가 아니기 때문이다.- 하지만 항등함수는 입력 값을 수정 없이 그대로 반환하는 특별한 함수다.
- T가 어떤 타입이든
UnaryOperator<T>
를 사용해도type-safe
하다. type-safe
함이 보장되니@SuppressWarnings
애너테이션을 사용해 경고를 없앤다.
- T가 어떤 타입이든
- 하지만 항등함수는 입력 값을 수정 없이 그대로 반환하는 특별한 함수다.
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
String[] strings = { "삼베", "대마", "나일론" };
UnaryOperator<String> sameString = identityFunction();
for (String s : strings)
System.out.println(sameString.apply(s));
Number[] numbers = { 1, 2.0, 3L };
UnaryOperator<Number> sameNumber = identityFunction();
for (Number n : numbers)
System.out.println(sameNumber.apply(n));
}
- 위 코드는 제네릭 싱글턴을 사용하는 예이다.
- 형변환을 하지 않아도 컴파일 오류나 경고가 발생하지 않는다.
재귀적 타입 한정(recursive type bound)
상대적으로 드물긴 하나, 자기 자신이 들어간 표현식을 사용해 타입 매개변수의 허용 범위 제한이 가능하다.
재귀적 타입 한정은 주로 타입의 자연적 순서를 정하는 Comparable 인터페이스와 함께 쓰인다.
1 2 3
public interface Comparable<T> { int compareTo(T o); }
- 타입 매개변수 T는
Comparable<T>
를 구현한 타입이 비교할 수 있는 원소 타입을 정의한다.- 거의 모든 타입 -> 자신과 같은 타입 원소와만 비교 가능
- 타입 매개변수 T는
상호비교가 가능함을 보여주는 예
1
public static <E extends Comparable<E>> E max(Collection<E> c);
<E extends Comparable<E>>
는 모든 타입 E는 자신과 비교할 수 있음을 의미한다.- 즉, 상호 비교가 가능하다는 뜻을 명확히 전달한다.
재귀적 타입 한정은 훨씬 복잡해질 가능성이 존재한다.
- 다행히 그런 일은 잘 안일어남.
관용구, 와일드 카드를 사용한 변형, 시뮬레이트한 셀프 타입 관용구를 이해한다면, 실전의 재귀적 타입 한정을 무리없이 다룰 것이다.
핵심 정리
- 제네릭 타입과 마찬가지로, 클라이언트에서 입력 매개변수와 반환값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 더 안전하며 사용하기도 쉽다.
- 타입과 마찬가지로, 메서드도 형변환 없이 사용할 수 있는 편이 좋다.
- 그러려면 대부분 제네릭을 사용하면 된다.
- 타입과 마찬가지로, 형변환이 필요한 기존 메서드는 제네릭하게 만들자.
- 위와 같은 방식을 이용하면, 기존 클라이언트는 그대로 둔 채 새로운 사용자의 사용성 강화가 가능하다.
Comments powered by Disqus.