본문 바로가기
Java

제네릭의 모든것

by bloodFinger 2020. 8. 8.

자바5부터 제네릭 타입이 새로 추가되었다.

 

제네릭을 이용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있게 되었다.

제네릭을 정확하게 알고있고 클래스를 설계한다면 클래스는 더욱 이쁘고 , 깔끔해 질 수있다고 확신한다!!

 

대표적인 장점 2가지

  • 컴파일 시 강한 타입 체크를 할 수 있다.
  • 타입 변환(casting)을 제거한다.

 

제네릭

 

제한된 타입 파라미터 <T extends 최상위 타입>

class Fruit { 
    public String toString(){
        return "Fruit";
    }
}

class Apple extends Fruit{ 
    public String toString(){
    	return "Apple";
    }
}
class Banana extends Fruit{ 
    public String toString(){
    	return "Banana";
    }
}

class Toy { 
    public String toString (){ 
    	return "Toy";
    }
}

class Box<T> {
    ArrayList<T> list = new ArrayList<T>();
    
    void add(T item) { 
    	list.add(item); 
    }
    T get(int i) { 
    	return list.get(i); 
    }
    //크기 측정 메소드
    int size() { 
    	return list.size(); 
    }
    public String toString() { 
    	return list.toString(); 
    }
}

class FruitBox<T extends Fruit> extends Box<T>{ }
FruitBox<Fruit> fruitAppleBox = new FruitBox<>();
//에러
//FruitBox<Toy> fruitAppleBox = new FruitBox<>();

 

 

interface를 구현한 클래스를 제한할때는 &로 연결해서 사용한다

 

interface Eatable { }

class Fruit implements Eatable{ public String toString(){return "Fruit";}}

class Apple extends Fruit{ public String toString(){return "Apple";}}
class Banana extends Fruit{ public String toString(){return "Banana";}}
class Toy { public String toString (){ return "Toy";}}

class Box<T> {
    ArrayList<T> list = new ArrayList<T>();
    void add(T item) { list.add(item); }
    T get(int i) { return list.get(i); }
    int size() { return list.size(); }
    public String toString() { return list.toString(); }
}

class FruitBox<T extends Fruit & Eatable> extends Box<T>{ } //Fruit 상속을 받았으며 Eatable 구현한 클래스

 

class Juicer {
	static Juice makeJui ce (FruitBox<Fruit> box) {  // <Fruit>으로 지정
	    String tmp = "";
	    for(Fruit f : box.getList()){
                tmp += f + " ";
            }
        
            return new Juice(tmp);
        }
}

juicer클래스는 제네릭  클래스가 아닌데다가, 제네릭 클래스라고 해도 static 메서드에는 타입 매개변수 T를  매개변수에  사용할 수  없다.

 

그래서 방법은  2가지다.

 

  1. 아예  제네릭을 적용하지 않던가
  2. 위와 같이 타입 매개변수 대신, 특정 타입을 지정해주거나

제네릭 타입이 달흔 것만으로는 오버로딩이 성립하지 않기 때문에  2번은 문제가 있다. 

그러면 우리는 제네릭을 못쓰는가?

 

이럴 때 사용하기 위해 고안된 것이 바로 '와일드 카드' 이다.

 

 

와일드 카드

 

가끔 오픈소스를 보다보면 아래와 같은 문법을 볼수있습니다.

<? extends T> 

<? super T>

<?>

 

필요성을 한번 살펴보자

 

<? extends T> - 와일드 카드의 상한 제한 이며 T와 그 자손들만 가능하다

<? super T> - 와일드 카드의 하한 제한 이며 , T와 그 조상들만  가능하다.

<?> - 제한이 없으면 모든 타입이 가능하며. Object와 같다!

 

 

그러면 위에 코드를 다시 고쳐보자

 

class Juicer {
	static Juice makeJui ce (FruitBox<? extends Fruit> box) {  // <Fruit>으로 지정
	    String tmp = "";
	    for(Fruit f : box.getList()){
                tmp += f + " ";
            }
        
            return new Juice(tmp);
        }
}

이제  이 메서드의 매개변수로 FruitBox<Fruit> 뿐만 아니라 , Fruit<Apple> , Fruit<Banana>도 가능하게 되었다.

 

+ 만약에  아래와 같이 Object를 상속받는다면 어떨까?

class Juicer {
	static Juice makeJui ce (FruitBox<? extends Object> box) {  // <Fruit>으로 지정
	    String tmp = "";
	    for(Fruit f : box.getList()){
                tmp += f + " ";
            }
        
            return new Juice(tmp);
        }
}

전과 달리 box의 요소가 Fruit의 자손이라는 보장이 없으므로 아래의 for문에서는 box에 저장된 요소를 Fruit 타입의 참조변수로 못받는다.

 

그런데!! 실제로 테스트를 해보면 문제가 없다.

이유는  ? 

이미 제네릭클래스인 FruitBox를 제한했기 때문이다.

 

컴파이라러는 위 문장으로 부터 모든 FruitBox의 요소들이 Fruit의 자손이라는 것을 알고 있으므로 문제 삼지 않는다!

'Java' 카테고리의 다른 글

리펙토링 - 일급컬렉션 사용기  (0) 2021.05.28
상속 괜찮은가? (컴포지션)  (0) 2020.08.10
객체직렬화 ?  (0) 2020.02.23
Network - TCP 통신  (0) 2020.01.16
디자인패턴(템플릿 메소드 패턴 / 팩토리 메소드 패턴)  (0) 2019.12.16