jeonyoungho Aug 20, 2022 2022-08-20T00:00:00+09:00
Aug 22, 2022 2022-08-22T08:45:14+09:00 8 min
로 타입으로 이루어진 메서드를 제네릭을 사용하여 변환
아래 예제 코드를 보자.
1
2
3
4
5
6
| // 코드 30-2 제네릭 메서드 (177쪽)
public static Set union(Set s1, Set s2) {
Set result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
|
컴파일은 되지만 경고가 두 개 발생한다.
1
2
3
4
5
6
| Warning:(20, 22) java: unchecked call to HashSet(java.util.Collection<? extends E>) as a member of the raw type java.util.HashSet
Warning:(21, 22) java: unchecked call to addAll(java.util.Collection<? extends E>) as a member of the raw type java.util.Set
Warning:(29, 35) java: unchecked conversion
required: java.util.Set<java.lang.String>
found: java.util.Set
|
메서드 선언에서의 세 집합(입력 2개, 반환 1개)의 원소 타입을 타입 매개변수로 명시하고, 메서드 안에서도 이 타입 매개변수만 사용하게 수정하면 된다. (타입 매개변수들을 선언하는) 타입 매개변수 목록은 메서드의 제한자와 반환 타입 사이에 온다.
아래 코드에서 타입 매개변수 목록은 <E>
이고 반환 타입은 Set<E>
이다.
1
2
3
4
5
6
| // 코드 30-2 제네릭 메서드 (177쪽)
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
|
- 단순한 제네릭 메서드라면 이 정도면 충분하다.
- 이 메서드는 경고 없이 컴파일 되며, 타입 안전하고, 쓰기도 쉽다.
- 아래는 이 메서드를 사용하는 간단한 클라이언트 코드이다.
- 직접 형 변환하지 않아도 어떤 오류나 경고 없이 컴파일된다.
1
2
3
4
5
6
7
| // 코드 30-3 제네릭 메서드를 활용하는 간단한 프로그램 (177쪽)
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);
}
|
- 이번에는 항등함수(입력 값을 수정 없이 그대로 반환하는 특별한 함수)를 만들어보자.
- java 라이브러리인 Function.identity를 사용하면 되지만(아이템 59), 학습을 위해 작성해보자.
- 항등함수 객체는 상태가 없으니 요청할 때 마다 새로 생성하는 것은 낭비다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // 제네릭 싱글턴 팩터리 패턴 (178쪽)
public class GenericSingletonFactory {
// 코드 30-4 제네릭 싱글턴 팩터리 패턴 (178쪽)
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
// 코드 30-5 제네릭 싱글턴을 사용하는 예 (178쪽)
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));
}
}
|
- T가 어떤 타입이든
UnaryOperator<T>
를 사용해도 타입 안전하다. - 실제 클라이언트 main코드를 보면
UnaryOperator<String>
과 UnaryOperator<Number>
로 사용하는 모습을 볼 수 있다.- 위의 예시와 마찬가지로 형변환을 하지 않아도 컴파일 오류나 경고가 발생하지 않는다.
상대적으로 드물긴 하지만, 자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정할 수 있다.(재귀적 타입 한정: recursive type bound)
- 재귀적 타입 한정은 주로 타입의 자연적 순서를 정하는 Comparable 인터페이스(아이템 14)와 함께 쓰인다.
1
2
3
| public interface Comparable<T> {
int compareTo(T o);
}
|
- 여기서 타입 매개변수 T는
Comparable<T>
를 구현한 타입이 비교할 수 있는 원소의 타입을 정의한다. - 실제로 거의 모든 타입은 자신과 같은 타입의 원소와만 비교할 수 있다.
- 따라서 String은
Comparable<String>
을 구현하고, Integer는 Comparable<Integer>
를 구현하는 식이다.
Comparable
을 구현한 원소의 컬렉션을 입력받는 메서드들은 주로 그 원소들을 정렬 혹은 검색하거나, 최솟값이나 최대값을 구하는 식으로 사용된다.- 이 기능을 수행하려면 컬렉션에 담긴 모든 원소가 상호 비교될 수 있어야 한다.
- 아래 예제 코드는 이 제약을 코드로 표현한 모습이다.
1
| public static<E extends Comparable<E>> E max(Collection<E> c);
|
- 타입 한정인
<E extends Comparable<E>>
는 모든 타입 E는 자신과 비교할 수 있다라고 읽을 수 있다. - 이와 관련된 아래 예제 코드를- 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현 (179쪽)
public class RecursiveTypeBound {
// 코드 30-7 컬렉션에서 최댓값을 반환한다. - 재귀적 타입 한정 사용 (179쪽)
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("컬렉션이 비어 있습니다."); // 빈 컬렉션이 인자로 들어오면 IllegalArgumentException을 던지니, Optional<E>를 반환하도록 고치는 편이 나을 것이다(아이템 55)
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}
public static void main(String[] args) {
List<String> argList = Arrays.asList(args);
System.out.println(max(argList));
}
}
|
핵심 정리: 제네릭 타입과 마찬가지로, 클라이언트에서 입력 매개변수와 반환값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 더 안전하며 사용하기도 쉽다. 타입과 마찬가지로, 메서드도 형변환 없이 사용할 수 있는 편이 좋으며, 많은 경우 그렇게 하려면 제네릭 메서드가 되어야 한다. 역시 타입과 마찬가지로, 형변환을 해줘야 하는 기존 메서드는 제네릭하게 만들자. 기존 클라이언트는 그대로 둔 채 새로운 사용자의 삶을 훨씬 편하게 만들어줄 것이다(아이템26)