클래스와 마찬가지로 메서드도 제네릭으로 만들 수 있다. 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다.
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
해당 코드는 컴파일은 되지만 경고가 2개 발생한다.
Union.java:5: warning: [unchecked] unchecked call to HashSet(Collection<? extends E>) as a member of raw type HashSet
Set result = new HashSet(s1);
Union.java:6: warning: [unchecked] unchecked call to addAll(Collection<? extends E>) as a member of raw type Set
result.addAll(s2);
이러한 경고를 없애려면 메서드를 타입 안전하게 만들어야 한다. 메서드 선언에서의 입력값 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;
}
Collections.reverseOrder
같은 함수 객체나 Collections.emptySet
같은 컬렉션용으로 사용.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>
로 형변환하면 비검사 형변환 경고가 발생한다. T가 어떤 타입이든 UnaryOperator<Object>
는 UnaryOperator<T>
가 아니기 때문에 발생하는 경고이다. 하지만 해당 예시는 항등함수를 반환하는 예시이므로 T 가 어떤 타입이든 UnaryOperator<T>
를 사용해도 타입 안전하다는 보장이 있으므로 @SuppressWarnings
어노테이션을 사용하여 오류나 경고없이 컴파일 할 수 있다.
public static void main(String[] args) {
String[] strings = {"one", "two","three"};
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));
}
예시에서 UnaryOperator
을 사용했는데 모양새가 처음보는 형태라 간단하게 정리해봤다.
UnaryOperator
는 Type T의 인자 하나를 받고, 동일한 Type T 객체를 리턴하는 함수형 인터페이스이다.
public interface UnaryOperator<T> extends Function<T, T> {
}
UnaryOperator는 Function을 상속하며, apply()를 호출하여 어떤 작업을 수행하게 된다.
public interface Function<T, R> {
R apply(T t);
}
또, UnaryOperator는 Lambda 표현식으로 구현할 수 있다.
아래와 같이 UnaryOperator 객체의 apply() 호출과 함께 인자를 전달하면 구현 내용이 수행되고, 인자와 같은 리턴타입이 반환된다.
import java.util.function.UnaryOperator;
public class UnaryOperatorExample1 {
public static void main(String[] args) {
UnaryOperator<Integer> unaryOperator1 = n -> n * n;
Integer result = unaryOperator1.apply(10);
System.out.println(result);
UnaryOperator<Boolean> unaryOperator2 = b -> !b;
Boolean result2 = unaryOperator2.apply(true);
System.out.println(result2);
}
}
Comparable
인터페이스와 함께 쓰인다.public interface Comparable<T>{
int compareTo(T o);
}
여기서 타입 매개변수 T 는 Comparable<T>
를 구현한 타입이 비교할 수 있는 원소의 타입을 정의한다. 실제로 거의 모든타입은 자신과 같은 타입끼리의 비교만 가능하므로, String
은 Comparable<String>
을 구현하고 Integer
은 Comparable<Integer>
을 구현하는 식이다.
public static <E extends Comparable<E>> E max (Collection<E> c) {
if(c.isEmpty()) throw new IllegalArguementException();
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}
타입 한정인 <E extends Comparable<E>>
는 모든타입 E는 자신과 비교할 수 있다라는 의미이다.
그래서 구현된 예시에서 볼 수 있듯이 compareTo
메서드를 통하여 E 형끼리 서로 비교하여 함수를 구현할 수 있다.