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

수박참외메론·2023년 1월 4일
0

1. 제네릭 메서드

클래스와 마찬가지로 메서드도 제네릭으로 만들 수 있다. 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다.

  • raw 타입 사용
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;
}

2. 제네릭 싱글턴 팩터리

  • 불변객체를 여러 타입으로 활용할 수 있게 만들어야 할때 활용
  • 제네릭은 런타임에 타입정보가 소거되므로 하나의 객체를 어떤 타입으로든 매개변수화가 가능하다 -> 이렇게 하려면 요청한 타입 매개변수에 맞게 매번 그 객체의 타입을 바꿔주는 정적 팩터리를 만들어야 함.
  • Collections.reverseOrder 같은 함수 객체나 Collections.emptySet 같은 컬렉션용으로 사용.

2-1. Generic Singleton Factory Pattern : 항등함수 예시

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

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

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

2-1-1. UnaryOperator

예시에서 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);
    }
}

3. 재귀적 타입 한정 (recursive type bound)

  • 자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용범위를 한정하는 개념
  • 주로 타입의 순서를 정하는 Comparable 인터페이스와 함께 쓰인다.
public interface Comparable<T>{
	int compareTo(T o);
}

여기서 타입 매개변수 T 는 Comparable<T>를 구현한 타입이 비교할 수 있는 원소의 타입을 정의한다. 실제로 거의 모든타입은 자신과 같은 타입끼리의 비교만 가능하므로, StringComparable<String> 을 구현하고 IntegerComparable<Integer>을 구현하는 식이다.

3-1. 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현

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 형끼리 서로 비교하여 함수를 구현할 수 있다.

profile
하루하루는 성실하게 인생전체는 되는대로

0개의 댓글