Java/Java

[Java] 제네릭의 이해2

Gamii 2024. 5. 7. 07:44
728x90

 

 

 

제네릭의 이해 2에서는 제네릭 타입 제한과 와일드카드에 대해서 다뤄보려고 한다. 

 

 

 

 

[제네릭의 이해 1 ] 링크는 아래 참고.. 

 

[Java] 제네릭의 이해1

이번에 기능 개발에 참고하고 싶은 코드가 제네릭으로 되어있어서, 제대로 이해하고 사용하고 싶어서 공부하게 되었다. 예전에는 정확히는 알지 못했지만, 자바 기본서나 선배들이 작성해 놓은

run-ran-run-ant.tistory.com

 

 


 

제네릭 타입 제한의 필요성

 

아래와 같이 관계도가 있다고 가정해 보자.

 

 

 

 

그리고 VIPUserCategory 클래스로 예시를 들어보려고 한다.

 

class VIPUserCategory<T> {
    private T t;
    
    public void set(T t){
    	this.t = t;
    }
    
    public T get(){
    	return t;
    }
}

VIPUserCategory<BlueUser> vipUser = new VIPUserCategory<>();  //OK
VIPUserCategory<BasicUser> basicUser = new VIPUserCategory<>(); //NO!

 

VIPUserCategory는 VIPUser만 들어갈 수 있다. 그러므로 VIPUser를 상속받은 BlueUser와 BlackUser만 사용할 수 있게 제한을 걸어야 한다. 위 코드처럼 BlueUser는 성립하지만, 멤버가 아닌 BasicUser는 성립해서는 안된다. 이때 제네릭 타입을 제한을 걸어야 한다. 

 

 

 

 

방법은 <T extends VIPUser>을 추가하면 된다.

 

class VIPUserCategory <T extends VIPUser> {
    private T t;
    
    public void set(T t){
    	this.t = t;
    }
    
    public T get(){
    	return t;
    }
}

VIPUserCategory<BlueUser> vipUser = new VIPUserCategory<>();  //OK
VIPUserCategory<BasicUser> basicUser = new VIPUserCategory<>(); //Compile Error!

 

 

 

VIPUser를 상속받은 BlueUser, BlackUser 클래스만 타입 매개변수로 사용할 수 있게 제한을 걸어두고, 이를 상한 경계라고 불린다.

 

 

 

 

 

 

제네릭 타입과 와일드 카드의 차이

 

제네릭 타입 : 타입은 모르지만, 타입이 정해지면 그 타입의 특성에 맞게 사용하고 싶을 때.

와일드 카드 : 타입에 상관없이, 타입을 정해두지 않고 사용하고 싶을 때.

 

 

 

모든 타입을 지정을 해줄 때, 제네릭 타입 <T>를 사용하면 된다. 하지만, 제네릭 타입을 사용하고 싶지만, 들어오는 실제 타입 매개변수에 상관없이 사용하고 싶다면 이때 와일드카드 <?>를 사용하면 된다.

 

 

 

 

 

 

 

와일드카드 종류

 

1. <?> Unbounded Wildcards 

<> 안에 오는 모든 타입이 가능하다. 보통 데이터가 아닌 '기능'의 사용에 관심 있는 경우에 <?>를 사용한다.

 

 

 

 

2. <? extends VIPUser> Upper Bounded Wildcards

상한 경계라고 불리고 위에서 봤던 예시처럼 VIPUser와 VIPUser 하위 타입만 가능하다.

 

 

 

 

타입을 안전하게 가져올 수 있다(get).

 

 

 

3. <? super VIPUser> Lower Bounded Wildcards

하한 경계라고 불리며, VIPUser와 VIPUser의 상위 타입만 가능하다.

 

 

안전하게 값을 저장할 수 있다(set).

 

 

 

언제 와일드카드를 사용해야 할까?

Effective Java 3/E에서는 PECS라는 공식을 사용하라고 한다. PECS는 producer - extends. consumer - super의 줄임말이다. 생성을 하는 곳에서는 extends를 사용하고, 소비를 하는 곳에는 super을 사용하라고 한다.

 

 

 

 

 

 

제네릭 타입 소거

 

컴파일 시에 타입을 검사하고 런타임시에 타입을 소거한다. 

 

1) 타입 매개변수의 경계가 없는 경우에는 Object로 치환한다. 

 

//런타임 전
class VIPUserCategory<T> {
    private T t;
    
    public void set(T t){
    	this.t = t;
    }
    
    public T get(){
    	return t;
    }
}

//런타임 후
class VIPUserCategory {
    private Object t;
    
    public void set(Object t){
    	this.t = t;
    }
    
    public Object get(){
    	return t;
    }
}

 

 

 

경계가 있는 경우는 최상위 경계 타입으로 타입 파라미터를 변경한다.  

(VIPUser가 최상위이므로 VIPUser로 타입 소거 된다.)

 

//런타임 전
class VIPUserCategory<T extends VIPUser> {
    private T t;
    
    public void set(T t){
    	this.t = t;
    }
    
    public T get(){
    	return t;
    }
}

//런타임 후
class VIPUserCategory {
    private VIPUser t;
    
    public void set(VIPUser t){
    	this.t = t;
    }
    
    public VIPUser get(){
    	return t;
    }
}

 

 

 

 

2) 타입 안정성을 유지하기 위해, 필요한 경우 타입 변환 추가

 

 

3) 제네릭 타입을 상속받은 클래스의 다형성을 유지하기 위해 Bridge method 생성