반응형

ITEM 30 "이왕이면 제네릭 메서드로 만들라"

 

이전 아이템과 마찬가지로 메서드도 제네릭으로 만들 수 있다.

[접근제한자] [식별자][리턴타입][메소드명](매개변수1, 매개변수2, ...) 의 일반적인 메서드 형식에서 매개변수에 사용할 타입 매개변수들을 접근제한자와 리턴타입 사이에 열거하고, 매개변수에서 사용하면 된다. 아래처럼 형식이 바뀐다.

[접근제한자] [식별자][타입 매개변수 목록][리턴타입][메소드명]([타입 매개변수]매개변수1, [타입 매개변수] 매개변수2, ...) 이렇게 형식이 바뀌면, 경고 없이 컴파일되며, 타입 안전하고 쓰기도 쉽다.

 

public static Set union(Set s1, Set s2) {
    Set result = new HashSet(s1);
    result.addAll(s2);

    return result;
}

 

위 메서드는 타입 안정성이 보장되지 않은 메서드로 컴파일은 되지만 타입 안전성을 보장하지 않는다. new HashSet(s1) 부분과 result.addAll(s2) 부분에서 raw 타입에 대한 unchecked call 이 발생하여 경고가 발생한다.

 

public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);

    return result;
}

public static void main(String[] args) {
	Set<String> guys = Set.of("톰", "딕", "해리");
    Set<String> stooges = Set.of("래리", "모에", "컬리");
    Set<String> aflCio = union(guys, stooges);
    System.out.println(aflCio);
}

 

이 메서드를 제네릭 메서드로 바꾸면 타입 안전해진다. union 메서드는 입력 2개, 출력 1개의 타입이 모두 같아야 한다. 컴파일 타임 때 이미 String 으로 타입을 확정지었기 때문에 안전하게 실행할 수 있다.

 

불변객체를 여러 타입으로 활용할 수 있게 만들어야 할 때가 있다. 제네릭은 런타임 때 타입 정보가 소거되므로 하나의 객체를 어떤 타입으로 매개변수화 할 수 있는 장점이 있다. 하지만 이렇게 하려면 요청한 매개 타입 변수에 맞게 매번 그 객체의 타입을 바꿔주는 정적 팩토리 메서드가 필요하다.

 

항등함수를 직접 구현해보면서 제네릭 메서드 구현 실습을 해보자.

 

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

@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
    return (UnaryOperator<T>) IDENTITY_FN;
}

 

IDENTITY_FN 을 UnaryOperator<T> 로 형변환하면 비검사 형변환 경고가 발생한다. 제네릭 불공변 원칙에 따라 UnaryOperator<Object> 는 UnaryOperator<T> 가 아니기 때문이다. 하지만 항등함수란 입력 값을 수정 없이 그대로 반환하는 특별한 함수이므로, T가 어떤 타입이든 UnaryOperator<T> 를 사용해도 타입 안전하다. 이를 근거로 @SuppressWarnings 애너테이션을 추가하여 컴파일 경고를 없애줄 수 있다.

 

재귀적 타입 한정이라는 개념을 이용하면 자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정할 수 있다. 재귀적 타입 한정은 주로 타입의 순서를 정하는 Comparable 인터페이스와 함께 쓰인다.

 

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

 

Comparable 인터페이스의 타입 매개변수 T는 해당 인터페이스를 구현한 타입이 비교할 수 있는 원소의 타입을 정의한다.

 

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

 

재귀적 타입 한정인 <E extends Comparable<E>> 는 "모든 타입 E 는 자신과 비교할 수 있다" 라는 뜻이다. 위의 코드는 컬렉션에 담긴 원소의 자연적 순서를 기준으로 최댓값을 계산하며, 컴파일 오류나 경고는 발생하지 않는다.

 

"제네릭 타입과 마찬가지로, 입력 매개변수와 반환값을 명시적으로 형변환해야 하는 메서드보다

제네릭 메서드가 더 안전하다."

반응형

+ Recent posts