Generic Programming
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; }
}
Java
복사
BoxA 와 BoxB는 같은 역활을 수행하지만 클래스명이 다른 이유로 다른 타입의 객체를 다루게 된 상태이다.
그런데 우리는 super class와 sub class를 만들 수 있기 때문에 다음과 같은 일을 할 수 있다.
class Box {
Object item;
void setItem(Object item) { this.item = item; }
Object getItem() { return item; }
}
Java
복사
위와 같이 우선 Object 변수와 매개변수를 가지는 클래스를 만든다. 이렇게 할 경우 A와 B 클래스 모두 다룰 수 있게 된다.
Box b = new Box();
b.setItem(new Object());
b.setItem("ABC");
String item = (String)b.getItem();
System.out.println(item);
Java
복사
그러나 이렇게 하는 것의 문제점은 Object라는 것이 너무 광범위한 클래스이다 보니 이렇게 해서 사용할때 이 객체가 어떠 클래스를 가리키고 있는지 확인을 해주어야 한다.
또한 다른 객체에 데이터를 넘길때에도 type casting을 해주어야 한다는 단점이 존재한다.
Generic Classes
그래서 위와 같은 문제점을 해결하기 위해 Generic CLass들을 선언한다.
class Box<T> {
T item;
void setItem(T item) { this.item = item; }
T getItem() { return item; }
}
Java
복사
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);
Java
복사
그래서 위와 같이 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);
Java
복사
이를 응용하여서 변수를 두개 이상 선택할 수 있도록 제작할 수도 있다.
용어를 정리해 보면 다음과 같다.
Box<T> : generic class 이며 "T Box" 혹은 "Box of T"라고 부른다.
T : a type variable
Box : a raw type
이때 객체 생성을 위해 class에 적어준 변수 타입을
String : a parameterized type
Generic Classes: Limitations
주의점 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; }
}
Java
복사
주의점 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;
}
}
Java
복사
실재 컴파일을 진행할때 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;
Java
복사
주의점 4
또한 primitive type역시 사용불가능하다. 이건 Object의 SubClass만 사용이 가능하기 때문이다.
Box<int> intBox = new Box<int>(); // Error
Java
복사
주의점 5
T 타입 객체를 만들 수 없다.
T t = new T();
Java
복사
Generic Classes: Example 1
그럼 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(); }
}
Java
복사
이제 위와 같이 Fruit, Apple, Grape, Toy 클래스를 선언한 다음 Box generic class를 생성한다.
위에서는 T 클래스 객체를 생성할 수 없지만 T type의 객체를 생성하는 것은 가능하다.
ArrayList<T> list = new ArrayList<T>();
Java
복사
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);
}
}
Java
복사
위에서 알 수 있는 교훈은 다음과 같다.
•
객체를 생성할때 객체의 타입은 반드시 일치하여야 한다.
•
타입간 슈퍼 클래스와 서브 클래스 관계라고 하더라도 다른 타입은 객체 생성이 불가능하다.
•
raw 클래스간 슈퍼 클래스와 서브 클래스 관계라면 객체 생성이 가능하다.
•
슈퍼 클래스 타입 instance에 서브 클래스 객체를 사입할 수는 있다. 하지만 아예 관계가 없는 클래스는 불가능하다.
Generic Classes: Limiting Types
이제 문제점은 클래스에 타입과 객체를 삽입하는 것에 제한은 존재하지만 Generic Class는 모든 타입의 객체를 생성할 수 있기 때문에 다음과 같은 문제가 발생한다.
FruitBox<Toy> fruitBox = new FruitBox<Toy>();
fruitBox.add(new Toy()); // OK. We are adding a toy to a fruit box.
Plain Text
복사
위와 같이 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(); }
}
Java
복사
extends Fruit 를 통해서 Fruit관련 클래스를 type으로 받는 클래스를 제작할 수 있다.
또한 superclass를 가지고 혹은 interface만을 가지고 제한을 걸수도 있다.
interface Eatable {}
class FruitBox<T extends Eatable> { ... }
Java
복사
제한이 되는 클래스를 두개 이상 두고 싶다면 &로 클래스들을 적어주면 된다.
class FruitBox<T extends Fruit & Eatable> { ... }
Java
복사
Generic Classes: Example 2
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> { }
Java
복사
위와 같이 코드를 작성하여 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);
}
}
Java
복사