Hyunebee
제네릭스(Generics) 본문
Generics
다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일시 타입체크를 해주는 기능이다.
컴파일 시에 체크하기 때문에 객체의 타입 안전성을 높이고 형변환의 번거로움이 줄어든다.
Generics은 클래스와 메서드에 선언할 수 있다.
ex) 클래스
class init{
Object item;
void setItem(Object item){
this.item = item;
}
Object getItem() {
return item;
}
}
class init<T>{ // T는 타입변수
T item;
void setItem(T item){
this.item = item;
}
T getItem() {
return item;
}
}
아래 처럼 변경이 가능하다. T는 타입변수라고 하며 T이외에 다른 문자를 사용해도 상관은 없다. ArrayList<E>와 같은 맥락이라고 볼 수 있다. 이 둘은 기호의 종류만 다를 뿐 임의의 참조형 타입을 의미하는것은 모두 같다.
Generics 용어
class Box<T>
Box<T> : 제네릭 클래스
Box : 원시타입
T : 타입변수
Generics의 제한
static멤버에 타입 변수 T를 사용할 수 없다. T또한 인스턴스변수로 간주되기 때문이다.
static멤버는 타입 변수에 지정된 타입, 즉 대입된 타입의 종류에 관계없이 동일한 것이여야한다.
Box<smallBox>.instance == Box<bigBox>.instance여야 하는것 이다.
또한 Generics 타입으로 배열을 생성할 수 없다. new T[] 등
이유는 new 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야한다. 하지만 컴파일시에 T를 정확하게 알수는 없다. 같은 이유로 instanceof또한 사용할 수 없다.
꼭 Generics 타입의 배열을 생성해야한다면 newInstance()와 같이 동적으로 개체를 생성하는 메서드로 배열을 생성하면 된다.
Generics 예제
Generics 제한
FruitBox<Toy> toyBox = new FruitBox<Toy>();
사용할 타입을 명시해서 지정한 타입만 받을 수 있다. 하지만 모든 종류에 타입을 지정가능 함으로 문맥상 이상하게 보일 수 있을것 이다. 위에도 코드를 보면 과일박스에 장난감이 들어있다. 이때 Generics는 extends를 통해 매개변수의 타입을 제한할 수 있다.
import java.util.ArrayList;
class Fruit implements Eatable {
public String toString() { return "Fruit";}
}
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Toy { public String toString() { return "Toy" ;}}
interface Eatable {}
class FruitBoxEx2 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
// FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // 에러. 타입 불일치
// FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러.
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); // 에러. Grape는 Apple의 자손이 아님
grapeBox.add(new Grape());
System.out.println("fruitBox-"+fruitBox);
System.out.println("appleBox-"+appleBox);
System.out.println("grapeBox-"+grapeBox);
} // main
}
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}// Fruit의 자식이면서 Eatable을 구현해야 한다면 &사용
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();}
}
위의 코드를 보면 T는 Fruit을 상속받고 Fruit은 Eatable을 구현하고 있다. 그렇기 때문에 FruitBox에 Toy가 매개변수로 사용되지 못하게 된것이다.
와일드카드
와일드카드는 매개변수의 타입을 제한하기 위해서 사용한다. 사용 방법은 다양하다.
-<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
-<? Super T> : 와일드 카드의 하한 제한 T와 그 조상들만 가능
-<?> : 제한 없음. 모든 타입이 가능하다.
ex)
import java.util.*;
class Fruit {
String name;
int weight;
Fruit(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String toString() { return name+"("+weight+")";}
}
class Apple extends Fruit {
Apple(String name, int weight) {
super(name, weight);
}
}
class Grape extends Fruit {
Grape(String name, int weight) {
super(name, weight);
}
}
class AppleComp implements Comparator<Apple> {
public int compare(Apple t1, Apple t2) {
return t2.weight - t1.weight;
}
}
class GrapeComp implements Comparator<Grape> {
public int compare(Grape t1, Grape t2) {
return t2.weight - t1.weight;
}
}
class FruitComp implements Comparator<Fruit> {
public int compare(Fruit t1, Fruit t2) {
return t1.weight - t2.weight;
}
}
class FruitBoxEx4 {
public static void main(String[] args) {
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
appleBox.add(new Apple("GreenApple", 300));
appleBox.add(new Apple("GreenApple", 100));
appleBox.add(new Apple("GreenApple", 200));
grapeBox.add(new Grape("GreenGrape", 400));
grapeBox.add(new Grape("GreenGrape", 300));
grapeBox.add(new Grape("GreenGrape", 200));
Collections.sort(appleBox.getList(), new AppleComp());
Collections.sort(grapeBox.getList(), new GrapeComp());
System.out.println(appleBox);
System.out.println(grapeBox);
System.out.println();
Collections.sort(appleBox.getList(), new FruitComp());
Collections.sort(grapeBox.getList(), new FruitComp());
System.out.println(appleBox);
System.out.println(grapeBox);
} // main
}
class FruitBox<T extends Fruit> extends Box<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();
}
}
위의 코드를 보면
Collections의 sort를 통해서 정렬하구 있다.
Collections의 sort 메소드의 Api 문서에 들어가보면
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
위의 코드대로 대입을 해보자
Collections.sort(appleBox.getList(), new AppleComp());
public static void sort(List<Apple> list, Comparator<? super Apple> c)와 동일하다.
우리는 Comparator로 Apple의 상위인 Apple, Fruit, Object 3가지 타입을 사용할 수 있게 되는것이다.
사실 Apple과 Grape 둘다 같은 코드임으로 하나로 통합된
Collections.sort(appleBox.getList(), new FruitComp());
FruitComp가 더욱 객체지향적인 코드라 볼 수 있다.