보글보글 개발일지
반응형

지네릭스

JDK1.5에서 도입

지네릭스란?

지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시에 타입체크를 해주는 기능.
객체 타입을 컴파일 시에 체크 -> 객체의 타입 안정성 높이고 형변환의 번거로움을 줄임

타입 안정성 높인다? 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환 되어 발생할 수 있는 오류 줄임.

EX) ArrayList와 같은 컬렉션 클래스는 다양한 종류 객체 담을 수 있지만 한 종류의 객체를 담는 경우 더 많다. 한 종류 객체인데도 꺼낼 때 마다 타입체크 하고 형변환 하는 것은 불편. 원하지 않는 종류의 객체 포함되는 것 막을 법 없음. 그래서!! 지네릭스를 쓴다.

지네릭스의 장점
1. 타입 안정성을 제공
2. 타입체크와 형변환을 생략할 수 있으므로 코드 간결

어렵게 얘기했지만.. 한마디로 다룰 객체의 타입 미리 명시해서 번거로운 형변환 줄여준다!!

지네릭 클래스의 선언

class Box {
	Object item;
    void setItem(Object item) { this.item = item; }
    Object getItem() { return item; }
}

//이걸 지네릭 클래스로 변경하면 클래스 옆에 <T>를 붙이면 됨
class Box<T> {
	T item;
    void setItem(T item) { this.item = item; }
    T getItem() { return item; }
}

T를 타입 변수라고 부른다. Type의 T!
꼭 T여야 하는 것은 아님. E도 가능, 변수 여러 개인 경우 Map<K,V>와 같이 콤마를 구분자로 나열.
상황에 맞는 의미있는 문자 선택해서 사용. 문자는 다르더라도, 모두 "임의의 참조형 타입"을 의미

지네릭 클래스인 Box 클래스의 객체를 생성할 때는 참조변수와 생성자에 타입 T대신에 실제 사용될 타입 지정

Box<String> b = new Box<String>(); // 타입 T 대신 실제 타입 지정
b.setItem(new Object()); // 에러! String 이외의 타입은 지정 불가
b.setItem("ABC"); // OK. String 타입이므로 가능
String item = (String)b.getItem(); // 형변환이 필요 없음

타입 T대신에 String 타입을 지정했으므로 지네릭 클래스 Box<T>는 다음과 같이 정의된 것과 같다.

class Box {
	String item;
    void setItem(String item) { this.item = item; }
    String getItem() { return item; }
}

지네릭 도입 이전 코드와의 호환을 위해 지네릭 클래스인데도 예전 방식으로 객체 생성하는 것이 허용. 그러나 타입 지정 안해서 안전하지 않다는 경고 발생.

지네릭스의 용어

Box<T>: 지네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다
T: 타입 변수 또는 타입 매개변수
Box: 원시 타입 
  • 지네릭 타입 호출: 타입 매개변수에 타입 지정
  • 매개변수화된 타입(대입된 타입): 지정된 타입 'String'

지네릭스의 제한

객체별로 다른 타입 지정하는 것은 적절. 그러나 모든 객체에 대해 동일하게 동작해야하는 static 멤버에 타입 변수 T를 사용할 수 없다. T는 인스턴스변수로 간주되기 때문. static 멤버는 인스턴스 변수 참조 불가!

class Box<T> {
	static T item; //에러
    static int compare(T t1, T t2) { ... } //에러
}

static 멤버는 대입된 타입 종류에 상관없이 동일한 것이어야 하기 때문!
Box<Apple>.item과 Box<Grape>.item이 다르면 안됨. 
지네릭 타입의 배열 생성도 불가! 지네릭 배열 타입의 참조변수를 선언하는 것은 가능하지만, 'new T[10]'과 같이 배열 생성하는 것은 안됨.

class Box<T> {
	T[] itemArr; //가능. T타입의 배열을 위한 참조 변수
    
    T[] toArray() {
    	T[] tmpArr = new T[itemArr.length]; //에러. 지네릭 배열 생성 불가!
        return tmpArr;
    }
}

왜 지네릭 배열을 생성할 수 없을까? -> new 연산자 때문! 이 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야한다. instanceof 연산자도 new연산자와 같은 이유로 T를 피연산자로 사용 불가.

꼭 지네릭 배열을 생성해야할 경우 new 대신 'Reflection API'의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열 생성하거나 Object 배열을 생성해서 복사한 다음에 T[]로 형변환 하는 방법 사용.

지네릭 클래스의 객체 생성과 사용

Box<T>의 객체에는 한 가지 종류, 즉 T타입의 객체만 저장할 수 있다. 

//참조변수와 생성자에 대입된 타입이 일치해야 한다.
Box<Apple> appleBox = new Box<Apple>(); //OK
Box<Apple> appleBox = new Box<Grape>(); //에러!!

//상속관계의 경우: Apple이 Fruit의 자손
Box<Fruit> appleBox = new Box<Apple>(); //에러. 대입된 타입이 다르다.

//단 두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮다. 
//FruitBox가 Box의 자손
Box<Apple> appleBox = new FruitBox<Apple>(); //OK. 다형성

//1.7버전 부터는 추정 가능한 경우 타입 생략 가능. 
//참조변수 타입으로부터 Box가 Apple 타입의 객체만 저장한다는 것 알 수 있음 --> 생성자에 반복해서 타입지정 안해도 됨.
Box<Apple> appleBox = new Box<Apple>();
Box<Apple> appleBox = new Box<>(); //OK! 1.7부터 생략 가능

//생성된 Box<T>의 객체에 void add(T item)으로 객체를 추가할 때, 대입된 타입과 다른 타입의 객체는 추가 불가
Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple()); //OK
appleBox.add(new Grape()); //에러. Box<Apple>에는 Apple 객체만 추가 가능

//타입 T가 Fruit인 경우 void add(Fruit item)이 되므로  Fruit의 자손들은 이 메서드의 매개변수가 될 수 있음
Box<Fruit> fruitBox = new Box<Fruit>();
fruitBox.add(new Fruit()); //OK
furitBox.add(new Apple()); //OK. void add(Fruit item)

제한된 지네릭 클래스

타입 매개변수 T에 지정할 수 있는 타입의 종류 제한할 수 있는 방법?

class FruitBox<T extends Fruit> { //Fruit의 자손만 타입으로 지정 가능
	ArrayList<T> list = new ArrayList<T>();
}

와일드 카드

지네릭 타입이 다른 것 만으로는 오버로딩 불가. 지네릭 타입은 컴파일러가 컴파일할 때만 사용하고 제거. 그래서 메서드 중복 정의로 성립. 그래서 와일드 카드 사용. ? 기호 사용

<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> : 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
  • 참고: 지네릭 클래스와 달리 와일드 카드에는 '&'를 사용할 수 없다! <? extends T & E> 불가
  • 와일드 카드를 통해 FruitBox<Fruit>에서 FuitBox<? extends Fruit>으로 바꾸면 Fruit 뿐만 아니라 Apple, Grape도 가능!
  • 매개변수 타입을 FruitBox<? extends Object>로 하면 모든 종류의 FruitBox가 매개변수로 가능.
static Juice makeJuice(FruitBox<? extends Fruit> box){
	String tmp = "";
    for (Fruit f:box.getList()) tmp+= f+" ";
    return new Juice(tmp);
}

지네릭 메서드

static <T> void sort(List<T> list, Comparator<? super T> c)
  • 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 별개의 것!
static Juice makeJuice(FruitBox<? extends Fruit> box){
	String tmp = "";
    for (Fruit f:box.getList()) tmp+= f+" ";
    return new Juice(tmp);
}
//제너릭 메서드로 바꾸면?
static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
	String tmp = "";
    for (Fruit f:box.getList()) tmp+= f+" ";
    return new Juice(tmp);
}
반응형
profile

보글보글 개발일지

@보글

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!