제네릭이란?
다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능.
→ 객체 타입을 컴파일 시에 체크하기 때문에 타입 안정성을 높이고 형변환 생략을 통해 간결한 코드 가능
class Box<T> //제네릭 타입 T를 선언
{
T item;
void setItem(T item){ this. item = item;}
T getItem(){ return item;}
}
Box<String> b = new Box<String>(); //타입 T대신 실제 타입을 지정
→ T를 type variable 이라고 하며 다른 글자로 대체 가능.
(e.g. ArrayList의 경우 element의 약자 E 사용.
Map<K,V> - Key, Value)
제네릭 클래스의 객체 생성
참조변수와 생성자에 대입된 타입(매개변수화된 타입)이 일치해야 함
e.g. Box appleBox = new Box();
Box appleBox = new Box<>(); // 추정 가능한 경우 타입 생략 가능
Box appleBox = new FruitBox(); // 상속관계
import java.util.ArrayList;
class Fruit { 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" ;}}
class FruitBoxEx1 {
public static void main(String[] args) {
Box<Fruit> fruitBox = new Box<Fruit>();
Box<Apple> appleBox = new Box<Apple>();
Box<Toy> toyBox = new Box<Toy>();
// Box<Grape> grapeBox = new Box<Apple>(); // 에러. 타입 불일치
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); // OK. void add(Fruit item)
appleBox.add(new Apple());
appleBox.add(new Apple());
// appleBox.add(new Toy()); // 에러. Box<Apple>에는 Apple만 담을 수 있음
toyBox.add(new Toy());
// toyBox.add(new Apple()); // 에러. Box<Toy>에는 Apple을 담을 수 없음
System.out.println(fruitBox);
System.out.println(appleBox);
System.out.println(toyBox);
} // main의 끝
}
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 Box{}Box: 원시타입
T: 타입 변수 또는 타입 매개변수. (T는 타입 문자)
Box: 제네릭 클래스
1) 제네릭스의 제한
제네릭 클래스 Box의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 가능
(= 인스턴스 별로 다르게 동작하도록 만든 기능이므로)
Box<Apple> appleBox = new Box<Apple>(); // apple객체만 저장 가능
Box<Grape> grapeBox = new Box<Grape>(); // grape객체만 저장 가능
→ 그러나, static멤버에 타입 변수 T 사용 불가
(T는 인스턴스변수로 간주되기 때문에 모든 객체에 대해 동일하게 동작해야하는 static 멤버는 인스턴스 변수를 참조할 수 없음.
⇒ static 멤버는 타입 변수에 지정도니 타입, 즉 대입된 타입의 종류에 관계없이 동일한 것이어야 함)
→ 제네릭 배열 타입의 참조변수 선언은 가능하나, 제네릭 타입의 배열을 생성하는 것은 불가.
class Box<T>{
T[] itemArr; // (o) T타입의 배열을 위한 참조변수
T[] toArray(){
T[] tmpArr = new T[itemArr.length]; // (x) 제네릭 배열 생성 불가
→ new 연산자는 컴파일 시점에 타입 T가 뭔지 알아야함. 그러나 위의 코드에서는 컴파일 시점에 T가 어떤 타입이 될지 알 수 없음. (instance of 역시 마찬가지)
→ 따라서, new 연산자 대신 Reflection API의 newInstance()메서드 혹은 Object 배열을 생성 후 형변환 하는 방법 사용
제네릭 타입에 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> {}
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();}
}
2) 와일드 카드
여러가지 타입의 매개변수를 갖는 제네릭 타입을 만들때 제네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않기때문에 고안된 방법
?로 표시하며 어떤 타입도 될 수 있음
💡 : 와일드 카드의 상한 제한. T와 그 자손들만 가능 : 와일드 카드의 하한 제한. T와 그 조상들만 가능 : 제한 없이 모든 타입이 가능. 와 동일import java.util.ArrayList;
class Fruit { public String toString() { return "Fruit";}}
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Juice {
String name;
Juice(String name) { this.name = name + "Juice"; }
public String toString() { return name; }
}
class Juicer {
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
}
class FruitBoxEx3 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
appleBox.add(new Apple());
System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));
} // main
}
class FruitBox<T extends Fruit> extends Box<T> {}
class Box<T> {
//class FruitBox<T extends Fruit> {
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();}
}
메서드의 선언부에 제네릭 타입이 선언된 메서드
→ 제네릭 메서드 호출 시 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생략 할 수 없음.
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>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));//메서드 호출시 타입변수에 타입 대입
System.out.println(Juicer.<Apple>makeJuice(appleBox));
System.out.println(Juicer.makeJuice(fruitBox));// 대입된 타입 생략 가능
System.out.println(Juicer.makeJuice(appleBox));
System.out.println(makeJuice(fruitBox));//에러. 클래스 이름 생략 불가
class Box<T extends Fruit>
{
void add(T t)
{...}
}
👇
class Box {
void add (Fruit t)
{ ... }
}