아이템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)를 이해하면 재귀적 타입 한정은 쉽게 사용할 수 있다.
반환값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 훨씬 안전하고 사용하기도 쉽다.
형변환은 좋지 않으니 제네릭 메서드를 사용할 수 있다면 무조건 제네릭 메서드를 사용하는 것이 좋다.