Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

forDevLife

[Java] Recursive Type Bound 본문

Java

[Java] Recursive Type Bound

JH_Lucid 2021. 11. 14. 18:50

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