이펙티브 자바/6장 열거 타입과 애너테이션

제네릭을 활용한 열거 타입 예제

말랑공룡 2024. 5. 20. 21:47

한정적 타입 토큰


<T extends Enum<T> & Operation>

자바 제네릭스 문법에서 사용되는 상한 경계(bound) 지정 구문이다.

이 구문은 제네릭 타입 T가 두 가지 조건을 모두 만족해야 함을 나타낸다:

  1. Enum<T>의 서브타입이어야 한다.
  2. Operation 인터페이스를 구현해야 한다.

이 구문을 사용하면 T는 반드시 열거형(enum)이면서 동시에 Operation 인터페이스를 구현한 타입이어야 한다.

이를 통해 열거형 타입에 대한 제네릭 클래스나 메서드를 정의하면서도, 해당 타입이 특정 인터페이스를 구현하도록 강제할 수 있다.

 

Operation 인터페이스 정의

public interface Operation {
    double apply(double x, double y);
}

 

Operation을 구현하는 Enum 정의

public enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) { return x + y; }
    },
    MINUS("-") {
        public double apply(double x, double y) { return x - y; }
    };

    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

 

제네릭 메서드 정의 및 사용

public class Calculator {

    public static <T extends Enum<T> & Operation> void printAllOperations(Class<T> opSet, double x, double y) {
        for (Operation op : opSet.getEnumConstants()) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }

    public static void main(String[] args) {
        printAllOperations(BasicOperation.class, 3, 4);
    }
}

 

한정적 와일드카드 타입


Collection<? extends Operation>

한정적 타입 토큰과의 차이점

  1. 타입 제한 및 유연성:
    • Class<T> 타입의 파라미터는 특정 열거형 타입만 받아들일 수 있어서 제네릭 타입 매개변수 T는 Enum<T>의 서브타입이면서 Operation 인터페이스를 구현해야 한다.
    • Collection<? extends Operation> 타입의 파라미터는 더 유연하여, Operation 인터페이스를 구현하는 객체들의 컬렉션을 받아들일 수 있다. 이는 열거형뿐만 아니라 다른 클래스의 인스턴스도 포함될 수 있다.
  2. 사용 방식:
    • Class<T>를 사용하면 해당 클래스의 열거형 상수를 얻기 위해 getEnumConstants() 메서드를 호출할 수 있다.
    • Collection<? extends Operation>를 사용하면 단순히 컬렉션에 포함된 객체들을 순회하면 된다.
import java.util.Collection;
import java.util.List;

public class Calculator {

    public static void printAllOperations(Collection<? extends Operation> opSet, double x, double y) {
        for (Operation op : opSet) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }

    public static void main(String[] args) {
        List<Operation> operations = List.of(
            BasicOperation.PLUS,
            BasicOperation.MINUS,
            new AdvancedOperation("MULTIPLY", (a, b) -> a * b),
            new AdvancedOperation("DIVIDE", (a, b) -> a / b)
        );

        printAllOperations(operations, 3, 4);
    }
}

 

Collection<? extends Operation>를 사용하면 더 유연한 구조를 가지며, 다양한 Operation 구현체를 처리할 수 있음을 보여준다.

열거형 뿐만 아니라 일반 클래스도 함께 사용할 수 있어 코드 재사용성과 확장성이 높아진다.

 

Operation은 interface인데, extends ?


Java 제네릭에서 사용되는 extends 키워드는 제네릭 타입 파라미터에 대한 상한 경계를 지정하는 데 사용된다. 여기서 extends는 클래스뿐만 아니라 인터페이스에도 적용된다. 이는 제네릭 타입이 특정 클래스의 서브타입이거나 특정 인터페이스를 구현해야 한다는 의미이다. 따라서 Collection<? extends Operation>은 Operation 인터페이스를 구현하는 모든 타입을 포함할 수 있습니다.

제네릭 상한 경계에서 extends의 의미

제네릭 타입 파라미터에서 extends는 특정 클래스의 서브타입이거나 특정 인터페이스를 구현하는 타입으로 제한하는 역할을 한다. 예를 들어, T extends Number는 T가 Number의 서브타입이어야 한다는 의미이며, T extends Runnable은 T가 Runnable 인터페이스를 구현해야 한다는 의미이다.