- 제네릭스의 제약
- static 멤버에 타입변수 사용불가.
class Box<T> {
static T item; //에러
static int compare(T t1, T t2) {...} //에러
...
}
→ 모든 객체에 동일하게 동작해야하는 static멤버에 타입 변수 T를 사용할 수 없다.
T는 인스턴스 변수로 간주되기 때문이다. 알다시피 static멤버는 인스턴스변수를 참조할 수 없다.
- 배열 생성할 때 타입변수 사용불가, 타입 변수로 배열 선언은 가능.
class Boc<T> {
T[] itemArr; //OK. T타입의 배열을 위한 참조변수
...
T[] toArray(){
T[] tmpArr=new T[itemArr.length]; //에러. 지네릭 배열 생성불가.
...
return tmpArr;
}
...
}
→ 지네릭 배열을 생성할 수 없는 것은 new연산자 때문인데, 이 연산자는 컴파일 시점에 타입 T가 정확히 뭔지 알아야 한다. 그런데 위의 코드에 정의된 Box<T>클래스를 컴파일하는 시점에서는 T가 어떤 타입이 될지 알 수 없기 때문에 타입변수로 사용이 불가하다.
- 와일드 카드
: 제네릭 클래스를 생성할 때, 참조변수에 지정된 제네릭 타입과 생성자에 지정된 제네릭 타입은 일치해야 한다.
ArrayList<Tv> list=new ArrayList<Tv>(); //OK. 제네릭 타입 일치
List<Tv> list=new ArrayList<Tv>(); //OK. 다형성. 제네릭 타입 일치
ArrayList<Product> list=new ArrayList<Tv>(); //에러. 제네릭 타입 불일치
- <? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
- <? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
- <?> : 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
와일드 카드를 이용하면 하나의 참조변수로 다른 제네릭 타입이 지정된 객체를 다룰 수 있다.
ArrayList<? extends Product> list=new ArrayList<Tv>();
ArrayList<? extends Product> list=new ArrayList<Audio>();
→ Tv와 Audio가 Product의 자손이라고 가정
→ 제네릭 타입이 '? extends Product'이면, Product와 Product의 모든 자손이 제네릭 타입으로 가능하다.
<와일드 카드 예제>
import java.util.ArrayList;
class Fruit2 { public String toString() {return "Fruit";}}
class Apple2 extends Fruit2 { public String toString() {return "Apple";}}
class Grape2 extends Fruit2 { public String toString() {return "Grape";}}
class Juice {
String name;
Juice(String name) {
this.name=name+"Juice";
}
public String toString() {
return name;
}
}
class JavaJungsuk_Generics_wildCard {
public static void main(String[] args) {
FruitBox2<Fruit2> fruitBox=new FruitBox2<Fruit2>();
FruitBox2<Apple2> appleBox=new FruitBox2<Apple2>();
fruitBox.add(new Apple2());
fruitBox.add(new Grape2());
appleBox.add(new Apple2());
appleBox.add(new Apple2());
System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));
}
}
class FruitBox2<T extends Fruit2> extends Box2<T> {}
class Box<T> {
ArrayList<T> list=new ArrayList<T>();
void add(T item) {
list.add(item);
}
T get(int i) {
return list.get(i);
}
ArrayList<T> getList() {
return list;
}
int size() {
return list.size();
}
public String toString() {
return list.toString();
}
}
결과
Apple Grape Juice
Apple Apple Juice
- 제네릭 메서드
: 메서드의 선언부에 제네릭 타입이 선언된 메서드를 제네릭 메서드라 한다.
class FruitBox<T> {
...
static<T> void sort(List<T> list, Comparator<? super T> c) {
...
}
}
→ 제네릭 클래서 FruitBox에 선언된 타입 매개변수 T와 제네릭 메서드 sort()에 선언된 타입 매개변수 T는 타입 문자만 같을 뿐 서로 다른 것이다.
→ static멤버에 타입 매개변수를 사용할 수 없지만, sort() 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능하다.
메서드를 호출할 때는 아래와 같이 타입 변수에 타입을 대입해야 한다.
FruitBox<Fruit> fruitBox=new FruitBox<Fruit>();
FruitBox<Apple> appleBox=new FruitBox<Apple>();
...
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));
→ 대부분의 경우 컴파일러가 대입된 타입을 추정할 수 있기 때문에 생략해도 된다.
System.out.println(<Fruit>makeJuice(fruitBox)); //에러. 클래스 이름 생략불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); //OK
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); //OK
→ 제네릭 메서드를 호출할 때, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생략할 수 없다.
→ 같은 클래스 내에 있는 멤버들끼리는 참조변수나 클래스이름, 즉 'this.'이나 '클래스이름'을 생략하고 메서드 이름만으로 호출이 가능하지만, 대입된 타입이 있을 때는 반드시 써줘야 한다.
- 제네릭 타입의 형변환
Box box=null;
Box<Object> objBox=null;
box=(Box)objBox; //OK. 제네릭 타입->원시 타입. 경고발생
objBox=(Box<Object>)box; //OK. 원시 타입->제네릭 타입. 경고발생
→ 제네릭 타입과 원시타입간의 형변환은 항상 가능하다. 다만 경고가 발생한다.
Box<Object> objBox=null;
Box<String> strBox=null;
objBox=(Box<Object>)strBox; //에러.
strBox=(Box<String>)objBox; //에러.
→ 대입된 타입과 제네릭 타입간의 형변환은 불가능하다.
'프로그래밍 > Java' 카테고리의 다른 글
Java_애너테이션·어노테이션(annotation) (0) | 2023.01.25 |
---|---|
Java_열거형(enum)_열거형의 정의와 사용, 열거형의 조상, 열거형에 멤버 추가 (0) | 2023.01.23 |
Java_제네릭스·지네릭스(1)_타입 변수, 제네릭스 용어, 제네릭의 타입과 다형성, Iterator<E>, HashMap<K,V>, 제한된 제네릭 클래스 (0) | 2023.01.15 |
Java_컬렉션프레임웍(7)_Collections의 메서드(동기화, 변경불가, 싱글톤, 단일 컬렉션), 컬렉션 클래스 정리 (0) | 2023.01.11 |
Java_컬렉션프레임웍(6)_HashMap과 Hashtable (0) | 2023.01.08 |