Skip to content

Latest commit

 

History

History
146 lines (108 loc) · 5.19 KB

30_이왕이면_제네릭_메서드로_만들어라_김세윤.md

File metadata and controls

146 lines (108 loc) · 5.19 KB

아이템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)를 이해하면 재귀적 타입 한정은 쉽게 사용할 수 있다.

결론

  • 반환값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 훨씬 안전하고 사용하기도 쉽다.
  • 형변환은 좋지 않으니 제네릭 메서드를 사용할 수 있다면 무조건 제네릭 메서드를 사용하는 것이 좋다.