아이템 30. 이왕이면 제네릭 메서드로 만들라

문법식·2022년 8월 16일
0

Effective Java 3/E

목록 보기
30/52

클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다. 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제너릭이다.
제네릭 메서드의 작성법은 제네릭 타입 작성법과 비슷하다. 다음 예제 코드를 보면 된다.

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 메서드는 집합 3개의 타입이 모두 같아야 한다. 이를 한정적 와일드카드 타입을 사용하여 더 유연하게 개선할 수 있다.

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

private static UnaryOperator<Object> IDENTITY_FN=(t) -> t;

@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
	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));
}

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

public interface Comparble<T>{
	int compareTo(T o);
}

타입 매개변수 TComparable<T>를 구현한 타입이 비교할 수 있는 원소의 타입으로 정의한다. 실제로 거의 모든 타입은 자신과 같은 타입의 원소와만 비교할 수 있다. 따라서 StringComparable<String>을 구현하고 IntegerComparable<Integer>를 구현하는 식이다.
Comparable을 구현한 원소의 컬렉션을 입력받는 메서드들을 주로 그 원소들을 정렬 혹은 검색하거나, 최솟값이나 최댓값을 구하는 식으로 사용된다. 이 기능을 수행하려면 컬렉션에 담긴 모든 원소가 상호 비교될 수 있어야 한다. 다음은 이 제약을 코드로 표현한 것이다.

public static<E extends Comparable<E>> E max(Collection<E> c);

타입 한정인 <E extends Comparable<E>>는 "모든 타입 E는 자기 자신과 비교할 수 있다"라는 의미이다. 위의 메서드를 구현한 코드는 아래와 같다.

public static<E extends Comparble<E>> E max(Collections<E> c){
	if(c.isEmpty())
    	throw new IllegalArgumentException("컬렉션이 비어 있습니다.");
    
    E result=null;
    for (E e : c)
    	if(result==null|| e.compareTo(result)>0)
        	result=Objects.requireNonNull(e);
    
    return result;
}

재귀적 타입 한정은 복잡해질 수 있지만, 다행히 그런 일은 잘 일어나지 않는다.

profile
백엔드

0개의 댓글