forDevLife
[Java] Recursive Type Bound 본문
Effective Java 학습 중, Recursive Type Bound가 class에 적용된 사례가 나오는데 이해가 잘 가지 않아서 알아봤다.
public abstract class Pizza {
public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
final Set<Topping> toppings;
// 재귀적 타입 한정 - 모든 타입 T는 자기 자신? 자기 자신이 들어간 표현식을 사용하여, 타입 매개변수의 허용범위를 한정한다.
// 즉, T는 자신을 포함하는 Builder<T>에 의해 한정된다.
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class); // 빈 Topping collection을 갖는 EnumSet 만들기
// 재귀 타입 한정으로 인해, MyPizza(구현체)에서 이 메서드를 호출할 수 있다. -> 한정 안해도 동작하는거 같은데?
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
// 구현 필요
abstract Pizza build();
// 구현 필요
protected abstract T self(); // 하위클래스에서 이를 이용하여 형변환 없이 메서드 연쇄를 지원할 수 있다.
}
// 최종적으로 반환되는 피자
Pizza(Builder<?> builder) { //? : 제한 없다. 모든 타입 가능
toppings = builder.toppings.clone(); // EnumSet은 cloneable을 구현해서 clone 메서드를 통한 복사가 가능하다.
}
}
- 위에서 Builder 클래스는 타입 T를 사용하는데, 이 T는 자신의 Builder<T>를 extends 해야 한다? 라는 의미가 헷갈렸다.
- 내부에 작성되어있는 Builder<T>에는 extends가 생략되었다고 이해해보자.
- 결국 Builder<T>의 T 타입도 Builder<T>를 extends한다. 여기에 recursive가 적용되었다.
- 파라미터 T를 반환하는 메서드가 두 개 존재한다. 이는 빌더 패턴으로, 반환되는 T를 통해 chaining을 진행하기 위함이다.
- chaining을 위해서는 T도 Builder<T>를 구현해야 한다. 따라서 T도 Builder<T>를 extends하도록 강제한다.
이를 통해 addTopping으로 반환된 T도 addTopping을 호출할 수 있게 된다. T가 Builder<T>를 extends 했기 때문이다. - 참고 : https://stackoverflow.com/questions/7385949/what-does-recursive-type-bound-in-generics-mean
1. Recursive Type Bound를 이용한 Fruit<T>
public class Fruit<T extends Fruit<T>> implements Comparable<T> {
private final Integer size;
public Fruit(Integer size) {
this.size = size;
}
public Integer getSize() {
return size;
}
@Override
public int compareTo(T other) {
return other.getSize();
}
}
- compareTo의 T도 Fruit<T>를 extends 하도록 bound를 설정했다.
- other도 getSize라는 메서드를 구현하고 있다는 것을 보증할 수 있다. 따라서 compareTo 메서드가 정상적으로 실행된다.
2. Fruit<T>를 extends한 Apple, Orange
class Apple extends Fruit<Apple> {
public Apple(Integer size) {
super(size);
}
}
class Orange extends Fruit<Orange> {
public Orange(Integer size) {
super(size);
}
}
3. Main
public static void main(String[] args) {
Apple a1 = new Apple(10);
Apple a2 = new Apple(10);
Orange o1 = new Orange(20);
Orange o2 = new Orange(20);
System.out.println(a1.compareTo(a2));
System.out.println(o1.compareTo(o2));
System.out.println(a1.compareTo(o1)); // Compile error!
}
- Apple 또는 Orange 끼리의 비교는 가능하다.
- 하지만 Apple과 Orange의 직접적인 compareTo는 불가능하다.
- 앞서 compareTo의 인자가 Fruit<T>를 extends 하도록 강제되었다.
- a1.compareTo(Fruit<Apple>)로 인자가 정해지므로, Fruit<Orange>는 인자로 올 수 없다. - 이와 같이, Recursive Bound를 설정한 class 내에서 'T'를 파라미터 또는 return으로 사용할 경우,
T other; other.getSize();와 같은 자신의 메서드 호출 또는 chaining 호출을 대비할 수 있다.
'Java' 카테고리의 다른 글
[Java] notify() vs notifyAll() (1) | 2022.01.26 |
---|---|
[Java] Call back(Listener) (0) | 2021.11.18 |
[정규식] 문자열 계산기 (0) | 2021.09.30 |
[java] import, static import 문 (0) | 2021.06.09 |
[java] hashCode(), toString(), equals() (0) | 2021.06.02 |
Comments