Generic Programming: 여러가지 타입의 객체를 같은 코드로 만들고자 하는 테크닉
동일한 류의 코드는 한 번만 사용하기 위해서 하는 프로그램 테크닉
예를 들어 다음과 같은 코드 두개가 있다고 가정해 보자
class BoxA {
A item;
void setItem(A item) { this.item = item; }
A getItem() { return item; }
}
class BoxB {
B item;
void setItem(B item) { this.item = item; }
B getItem() { return item; }
}
BoxA 와 BoxB는 같은 역활을 수행하지만 클래스명이 다른 이유로 다른 타입의 객체를 다루게 된 상태이다.
그런데 우리는 super class와 sub class를 만들 수 있기 때문에 다음과 같은 일을 할 수 있다.
class Box {
Object item;
void setItem(Object item) { this.item = item; }
Object getItem() { return item; }
}
위와 같이 우선 Object 변수와 매개변수를 가지는 클래스를 만든다. 이렇게 할 경우 A와 B 클래스 모두 다룰 수 있게 된다.
Box b = new Box();
b.setItem(new Object());
b.setItem("ABC");
String item = (String)b.getItem();
System.out.println(item);
그러나 이렇게 하는 것의 문제점은 Object라는 것이 너무 광범위한 클래스이다 보니 이렇게 해서 사용할때 이 객체가 어떠 클래스를 가리키고 있는지 확인을 해주어야 한다.
또한 다른 객체에 데이터를 넘길때에도 type casting을 해주어야 한다는 단점이 존재한다.
그래서 위와 같은 문제점을 해결하기 위해 Generic CLass들을 선언한다.
class Box<T> {
T item;
void setItem(T item) { this.item = item; }
T getItem() { return item; }
}
class Box<T>
와 같은 Type variable 즉 템플릿을 선언하는 것이다. 그 이후 타입을 바꿀 곳에 자료형을 모두 T로 작성한다.
그래서 객체를 생성한때 타입 T를 우리가 선택하여서 결정해 줄 수 있다.
Box<String> b = new Box<String>();
b.setItem(new Object()); // this line causes error.
b.setItem("ABC");
String item = b.getItem(); // type casting not necessary.
System.out.println(item);
그래서 위와 같이 new BOx<String>()
으로 선언하는 순간 String 타입의 객체가 생성되게 된다.
class Entry<K, V> {
private K key;
private V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
---
객체 선언
Entry<String, Integer> entry = new Entry<String, Integer>("Fred", 42);
혹은
Entry<String, Integer> entry = new Entry<>("Fred", 42);
이를 응용하여서 변수를 두개 이상 선택할 수 있도록 제작할 수도 있다.
용어를 정리해 보면 다음과 같다.
Box<T>
: generic class 이며 "T Box" 혹은 "Box of T"라고 부른다.
T
: a type variable
Box
: a raw type
이때 객체 생성을 위해 class에 적어준 변수 타입을
String
: a parameterized type
주의점 1
T 를 static 변수로 정의할 수는 없다. 왜냐하면 타입이 결정되지 않았기 때문
class Box<T> {
static T item; // Error: cannot define a static variable of type T.
void setItem(T item) { this.item = item; }
T getItem() { return item; }
}
주의점 2
배열 생성과 instanceof 함수는 사용이 불가능하다.
class Box<T> {
T[] itemArr; // This is OK.
T[] createArray() {
T[] tmpArr = new T[10]; // Error: cannot create a generic array of T
return tmpArr;
}
}
실재 컴파일을 진행할때 T를 모두 지워버리고 Object 타입으로 바꿔 버린다.
주의점 3
클래스 외부에서도 배열을 생성할 수 없다. 이건 자바에서 막아놓았는데 간단하게 이야기 해서 자바내부에서 이것을 활용한 에러가 생길때가 있기 때문이다.
Box<String>[] bsa = new Box<String>()[3]; // Error: cannot create a generic array of type Box<String>
Object[] oa = bsa;
oa[0] = new Box<Integer>(3);
String s = bsa[0].x;
주의점 4
또한 primitive type역시 사용불가능하다. 이건 Object의 SubClass만 사용이 가능하기 때문이다.
Box<int> intBox = new Box<int>(); // Error
주의점 5
T 타입 객체를 만들 수 없다.
T t = new T();
그럼 Generic Class는 언제 사용할까????
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 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(); }
}
이제 위와 같이 Fruit, Apple, Grape, Toy 클래스를 선언한 다음 Box generic class를 생성한다.
위에서는 T 클래스 객체를 생성할 수 없지만 T type의 객체를 생성하는 것은 가능하다.
ArrayList<T> list = new ArrayList<T>();
public class Lecture {
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>(); // Error: wrong type
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
appleBox.add(new Apple());
appleBox.add(new Apple());
// appleBox.add(new Toy()); // Error: cannot add Toy to Box<Apple>
toyBox.add(new Toy());
// toyBox.add(new Apple()); // Error: cannot add Apple to Box<Toy>
System.out.println(fruitBox);
System.out.println(appleBox);
System.out.println(toyBox);
}
}
위에서 알 수 있는 교훈은 다음과 같다.
이제 문제점은 클래스에 타입과 객체를 삽입하는 것에 제한은 존재하지만 Generic Class는 모든 타입의 객체를 생성할 수 있기 때문에 다음과 같은 문제가 발생한다.
FruitBox<Toy> fruitBox = new FruitBox<Toy>();
fruitBox.add(new Toy()); // OK. We are adding a toy to a fruit box.
위와 같이 FruitBox는 Fruit 관련 클래스를 받기 위한 클래스임에도 불구하고 Toy class를 type으로 받아왔다는 문제점이 생긴다. 그렇기 때문에 이를 다음과 같이 바꾸어서 넣을 수 있는 타입에 제한을 주어야 한다.
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); }
int size() { return list.size(); }
public String toString() { return list.toString(); }
}
extends Fruit
를 통해서 Fruit관련 클래스를 type으로 받는 클래스를 제작할 수 있다.
또한 superclass를 가지고 혹은 interface만을 가지고 제한을 걸수도 있다.
interface Eatable {}
class FruitBox<T extends Eatable> { ... }
제한이 되는 클래스를 두개 이상 두고 싶다면 &
로 클래스들을 적어주면 된다.
class FruitBox<T extends Fruit & Eatable> { ... }
import java.util.ArrayList;
interface Eatable { }
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"; } }
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 FruitBox<T extends Fruit & Eatable> extends Box<T> { }
위와 같이 코드를 작성하여 FruitBox는 Fruit, Eatable 전용 클래스만 생성함과 동시에 Box<T>
를 상속받아 내부 함수를 사용할 수 있다.
public class Lecture {
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>(); // Error: Type mismatch
아예 타입을 받을 수 없는 경우
// FruitBox<Toy> toyBox = new FruitBox<Toy>(); // Error: Toy cannot be a type of FruitBox.
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
아예 관련이 없어서 객체를 삽입 불가능
// appleBox.add(new Grape()); // Error: Grape is not a subclass of Apple.
grapeBox.add(new Grape());
System.out.println("fruitBox-"+fruitBox);
System.out.println("appleBox-"+appleBox);
System.out.println("grapeBox-"+grapeBox);
}
}