728x90
아이템30. 이왕이면 제네릭 메서드로 만들어라
서론
- 클래스뿐만 아니라 메서드도 제네릭화를 할 수 있습니다.
- 대표적인 예로 매개변수를 받는 정적 유틸리티 메서드(Collections의 알고리즘 메서드 binarySearch, sort ...)이 보통 제네릭 메서드입니다.
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
- 그렇다면 제네릭 메서드 작성법을 한번 알아보겠습니다.
잘못된 메서드
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
- 위의 메서드처럼 합집합이 있다고 했을때 컴파일을 하면 아래와 같은 경고가 발생한다.
warning: [unchecked] unchecked call to
....
- 이 경고는 간단하게 말하자면 타입을 안전하게 만들라는 이야기이다.
- 즉, 메서드 선언에서 Set의 원소 타입을 타입 매개변수로 명싲하고, 메서드 안에서도 타입 매개변수만 사용하게 수정하면 됩니다.
수정
public static <E> set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
- 위의 코드는 3개의 Set이 모두 타입이 같아야 한다.
- 아이템 31에서 설명한 한정적 와일드카드 타입을 사용하면 더 유연하게 가능하기도 하다.
제네릭 메서드의 정의
public static <T> Box<T> getBox(T o) { ...}
불변 객체를 여러 타입으로 활용
- 아이템 28에서 봤듯이 런타임에 타입 정보가 소거되므로 하나의 객체를 어떤 타입으로든 매개변수화할 수 있다.
- 그러나 요청한 타입 매개 변수에 맞게 매번 그 객체의 타입을 바꿔주는 정적 팩터리를 만들어야 한다.
- 이 패턴을 제네릭 싱글턴 팩터리라 하며 아이템 42를 설명해주시는 분이 잘 설명해주실꺼라 믿겠습니다..ㅠㅠ
항등 함수 (identity function)
- 가장 쉬운 방법은 Function.identity를 사용하면 된다. (아이템 59를 설명해주시는 분이 잘 설명 해주시겠죠..?)
- 그러나 공부 목적으로 한번 만들어 보자.
- 항상함수 객체는 상태가 없으니, 싱글턴으로 만드는 것이 좋다. 자바의 제네릭이 실체화된다면 항등함수를 타입별로 만들어야 하지만, 소거가 되기 떄문에 제네릭 싱글턴 하나만 충분하다.
public static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked") // 비검사 형변환 경고 방지
public static <T> UnaryOperator<T> identiyFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
- 아래와 같이 사용하면 손쉽게 사용할 수 있습니다.
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 인터페이스와 같이 사용된다.
public interface Comparable<T> {
int compareTo(T o);
}
- 재귀적 타입 한정을 이용해 상호 비교가 가능한 코드를 보도록 하겠습니다.
public static <E extends Comparable<E>> E max(Collection<E> c);
- 이후에 아래와 같이 작성하면 최대값을 구할 수 있습니다.
public static <E extends Comparable<E>> Optional<E> max(Collection<E> collection) {
if (collection.isEmpty())
return Optional.empty();
E result = null;
for (E e : collection) {
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
return Optional.of(result);
}
public static void main(String[] args) {
List<Integer> list = List.of(2, 14, 5, 11, 3);
int result = max(list).orElseGet(null);
System.out.println("result = " + result);
}
- 복잡하긴 하지만, 관용구, 와일드카드 사용한 변형(아이템 31), 시뮬레이트한 셀프 타입 관용구(아이템2)를 이해하면 재귀적 타입 한정은 쉽게 사용할 수 있다.
결론
- 반환값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 훨씬 안전하고 사용하기도 쉽다.
- 형변환은 좋지 않으니 제네릭 메서드를 사용할 수 있다면 무조건 제네릭 메서드를 사용하는 것이 좋다.
728x90
728x90