java - 제네릭

원종서·2021년 12월 26일
0

java

목록 보기
2/9

제네릭

1.1 제네릭이란

다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.

객체의 타입 안정성을 높이고 형변환의 번거러움을 줄어든다.

장점

타입 안정성을 제공한다.
타입체크와 형변환을 생략할 수 있음으로 코드가 간결해진다.

1.2 제네릭 클레스의 선언

클래스에 선언하는 제네릭 타입

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

위의 클래스를 제네릭 클래스로 변경하면

Class Box <T>{
	T item;
    
    void setItem(T item) {this.item = item);
	T getItem() { return item; }
}

Box<T.> 에서 T는 타입변수라고 한다. (임의의 참조형 타입)

제네릭 클레스가 된 Box클래스를 생성할 떄에는 다음과 같이 참조변수와 생성자에 T대신 사용될 실제 타입을 지정해줘야한다.

Box<String> b = new Box<String>();
//b.setItem(new Object());  <- error
b.setItem("ABC");
String item = b.getItem(); // 형변환 필요없다.

제네릭 용어

class Box<T.> {}

  • Box<T.> 제네릭 클레스, 'T의 Box' 또는 'T Box'
  • T 타입 변수 또는 타입 매개변수
  • Box 원시타입

타입 매개변수에 타입을 지정하는 것을 제네릭 타입 호출 이라고 하고 , 지정된 타입 'String'을 매개변수화된 타입 (대입딘 타입) 이라고 함.

Box<String.> b = new Box<String.>();

  • <String.> 대입된 타입(매개변수된 타입)
  • Box<String.> 제네릭 타입 호출

제네릭의 제한

static 멤버에 타입 변수를 사용할 수 없다. T는 인스턴스 변수로 간주되기 때문이다.
또 제네릭 타입의 배열을 생성하는 것도 허용되지 않는다 이유는 new 연산자이기 때문인데, new연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야한다. 그런데 Box<T.> 클래스를 컴파일 하는 시점에서는 T가 어떤 타입인지 알 수 없다. (instanceof 연산자도 위와 같은 이유로 허용하지 않는다.)

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

Box<T.> 의 객체를 생성할 때는 다음과 같이 한다.
참조변수와 생성자에 대입된 타입이 일치해야한다.

Box<Apple> appleBox = new Box<Apple>(); //OK
Box<Apple> appleBox = new Box<Grape>(); // Error

두 타입이 상속관계에 있어도 마찬가지다. Apple 이 Fruit의 자손이라고 가정하면

Box<Fruit> appleBox = new Box<Apple>(); //Error

두 제네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것이라면 괜찮다.

Box<Apple> appleBox = new AppleBox<Apple>(); //OK

JDK1.7부터는 생성자에 반복해서 타입을 지정해 주지 않아도 된다.

Box<Apple> appleBox = new Box<>(); //OK

타입 T가 Fruit 인 경우 void add(Fruit item)가 되므로 Fruit의 자손들은 이 메서드의 매개변수가 될 수 있다. Apple 은 Fruit 의 자손

Box<Apple> appleBox = new Box<>();
appleBox.add(new Fruit()); // Error
appleBox.add(new Apple()); //OK
Box<Fruit> fruitBox = new Box<>();
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); //OK

1.4 제한된 제네릭 클래스

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

class FruitBox<T extends Fruit>{
	ArrayList<T> list = new ArrayList<>();
    ...
}

위는 T를 Fruit의 자손만 타입으로 지정 가능하다

클래스 Fruit의 자손이면서 Eatable인터페이스도 구현해야 한다면 & 기호
( 인터페이스를 구현해야한다는 제약이 있을때도 extends)

class FruitBox<T extends Fruit & Eatable> {...}

와일드 카드

매개변수에 과일박스를 대입하면 주스를 만들어서 반환하는 Juicer Class,
static makeJuice() 메서드가 존재

class Juicer {
	static Juice makeJuice(FruitBox<Fruit> box){
    		String tmp ="";
            for(Fruit f : box.getList()) tmp += f +"";
            return new Juice(tmp);           
    }
}

Juicer class는 제네릭 클래스가 아닌데다가, 제네릭 클래스 이라고 해도 static 메서드에서는 타입 매개변수 T를 매개변수로 사용할 수 없음으로, 아예 제네릭을 적용하지 않던가, 위와 같이 타입 매개변수 대신 특정 타입을 지정해 줘야한다.

하지만 위와 같이 특정 타입을 지정해 두면 오직 Fruit 타입만을 매개변수로 받을 수 있는 메서드가된다.

그르머로 여러 가지 타입의 매개변수를 같는 makeJuice()를 만들 수 밖에 없다.

하지만 여러 가지 타입의 매개변수를 같는 makeJuice()를 만들때 제네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않기 떄문에 에러를 발생한다.

! 이럴 때 사용하기 위해 고안된 것이 바로 와일드 카드 이다. 기호는 '?' (어떠한 타입도 될 수 있다)로 포현한다.

"?" 만으로는 Object 타입과 다를게 없음으로, 다음과 같이 상한, 하한을 제한할 수 있다.

<.? extends T> 와일드 카드의 상한 제한, T 와 그 자손들만 가능
<.? super T> 와일드 카드의 하한 제한, T 와 그 조상들만 가능
<.?> 제한 없음 <.? extends Object> 와 동일

class Juicer {
	static Juice makeJuice(FruitBox<? extends Fruit> box){
    		String tmp ="";
            for(Fruit f : box.getList()) tmp += f +"";
            return new Juice(tmp);           
    }
}

위처럼 와일드 카드를ㄹ 사용해서 메서드의 매개변수를 바꾸면 매개변수로 FruitBox<.Fruit> 뿐 아니라 FruitBox<.Apple> FruitBox<.Grape> 등의 Fruit 자식들을 모두 매개변수로 받을 수 있다.

static <T> void sort (List<T> list, Comparator<? super T> c)

Comparator<? super Apple>

  • Comparator<Apple.>
  • Comparator<Fruit.>
  • Comparator<Object.>

1.6 제네릭 메서드

메서드의 선언부에 제네릭 타입이 선언된 메세더를 제네릭 메서드라 한다.

class FruitBox<T> {
	static <T> void sort (List<T> list, Comparator<? super T> c) {
	...
	}
}

sort()가 static 메서드이다. 하지만 타입 매개변수를 사용하고 있다. 이유는 메서드에 제네릭 타입을 선언하고 사용했기 때문이다.

메서드에 선언된 제레릭 타입은 지역변수를 선언한 것과 같다고 생각하면 된다.
이 타입 매개변수는 메서드 내에서만 지역적으로 사용될 것이므로 스태틱이든 아니건 상관 없다.

class Juicer {
	static Juice makeJuice(FruitBox<? extends Fruit> box){
    		String tmp ="";
            for(Fruit f : box.getList()) tmp += f +"";
            return new Juice(tmp);           
    }
}

를 제네릭 메서드를 이용하여

class Juicer {
	static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
    		String tmp ="";
            for(Fruit f : box.getList()) tmp += f +"";
            return new Juice(tmp);           
    }
}

로 바꿀 수 있다.

이 메세드를 호출하기 위해서는 타입 변수에 타입을 대입해야한다.

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

Juicer.<Fruit>makeJuice(fruitBox));
Juicer.<Apple>makeJuice(appleBox));

Juicer.makeJuice(fruitBox)); // 대입된 타입 생략 가능
Juicer.makeJuice(appleBox));

한가지 주의할 점은 같은 클래스 내에 있는 맴버들끼리는 참조변수나 클래스이름, 즉 this, 클래스 이름을 생략하고 메서드 이름만 호출이 가능하지만, 대입된 타입이 있을때는 반드시 써 줘야한다.

<Fruit>makeJuice(fruitBox)); // error
this.<Fruit>makeJuice(fruitBox));
Juicer.<Fruit>makeJuice(fruitBox));

1.7 제네릭 타입의 형변환

제네릭 타입과 원시타입의 형변환

Box box =null;
Box objBox =null;

box = (Box)objBox; // OK 제네릭 -> 원시
objBox = (Box)box; // OK 원시 -> 제네릭

단 대입된 타입이 다른 제네릭 타입간에는 형변환이 불가능하다.

0개의 댓글