본문 바로가기
개발관련 서적 정리/Effective Java

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

by Backchus 2023. 2. 5.

매개변수화 타입을 받는 정적 유틸리티 메서드

제네릭 메서드를 사용하면 컴파일 타임에 타입안정성을 보장할 수 있습니다.

제네릭을 사용하지 않는 메서드

package me.whiteship.chapter05.item30._01_before;

import java.util.HashSet;
import java.util.Set;

// 제네릭 union 메서드와 테스트 프로그램 (177쪽)
public class Union {

    // 코드 30-2 제네릭 메서드 (177쪽)
    public static Set union(Set s1, Set s2) {
        Set result = new HashSet(s1);
        result.addAll(s2);
        return result;
    }
}

Union클래스에 union이라는 메소드가 있는데 전혀 제네릭타입을 쓰고 있지 않습니다.

        Set<String> guys = Set.of("톰", "딕", "해리");
        Set<Integer> stooges = Set.of(1, 2, 3);
        Set<String> all = union(guys, stooges);

위와 같이 두개의 Set을 union메서드를 통해서 하나로 합치는데에는 타입이 다르다 하더라도 문제가 없습니다. 문제는 꺼내서 쓸 때인데 둘 중에 한가지의 타입을 예측해서 예를들면 String타입으로 나온다고 가정하고 사용한다면 문제가 됩니다.

        for (String o : all) {
            System.out.println(o);
        }

위와같이 String타입으로 for문을 실행할 경우 integer타입으로 저장된 요소를 호출할때 ClassCastException이 터집니다. 이런문제를 컴파일 타임에 방지를 하려면 아래와 같은 제네릭 타입을 적용해서 구현하면 됩니다.

제네릭을 사용한 union메서드

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

제네릭을 사용한 union을 사용해서 다른 타입의 Set을 하나로 합치려고하면 컴파일타임에 에러가 발생하기때문에 조기에 타입캐스팅문제를 잡을 수 있습니다.

        Set<String> guys = Set.of("톰", "딕", "해리");
        Set<Integer> stooges = Set.of(1, 2, 3);
        Set<String> all = union(guys, stooges);     // 컴파일 에러

또 다른 한가지 용도가 있는데 제네릭 싱글턴 팩토리가 있습니다.

싱글턴 팩토리

package me.whiteship.chapter05.item30._01_before;

import java.util.function.Function;
import java.util.function.UnaryOperator;

// 제네릭 싱글턴 팩터리 패턴 (178쪽)
public class GenericSingletonFactory {

    public static Function<String, String> stringIdentityFunction() {
        return (t) -> t;
    }

    public static Function<Number, Number> integerIdentityFunction() {
        return (t) -> t;
    }
}

싱글턴 팩토리는 어떤 싱글톤 객체를 리턴하는 팩토리 메서드를 이야기 합니다. 여기서 제네릭을 활용하면 여러개의 인스턴스를 만들 필요가 없어집니다. 위의 코드는 Function타입의 객체를 2개 생성하는데 제네릭이 타입이 다르기 때문입니다. 하지만 제네릭은 컴파일시 소거가 되기때문에 위의 두 Function은 같은 객체가 됩니다.

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

    public static <T> UnaryOperator<T> identityFunction() {
        return (UnaryOperator<T>) IDENTITY_FN;
    }

위와 같이 UnaryOperator타입으로 하나의 항등함수를 선언하고 그 함수를 우리가 원하는 제네릭타입으로 형변환을 해서 리턴하는 identityFunction메서드를 생성하였습니다. 결국 싱글턴 객체를 리턴하는 싱글턴 팩토리 입니다. 하나의 인스턴스이지만 호출하는 곳에서 그대로 타입만 설정해서 쓰면 됩니다.

        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));