다양한 타입에 적용할 수 있는 객체를 만들수 있도록 지원
public class Box<T> {
T item;
public Box(T item) {
this.item = item;
}
public T getItem() {
return this.item;
}
}
class Test {
public static void main(String[] args) {
Box<String> box1 = new Box<String> ("abc");
Box<Integer> box2 = new Box<> (1);
}
}
Generics는 타입 안정성(Type Safety)를 지원
-- 타입 파라미터를 통해 전달된 타입 정보를 이용하여 컴파일 타임에 검사
java.lang.Object 타입으로 변환되는 객체 형 변환은 런타임에 검사됨
-- 컴파일시에 오류를 발견할 수 없음
-- java.lang.ClassCastException 예외 발생
Generics는 타입이 명확히 명시되므로 컴파일 타임에 오류를 검출할 수 있음
public class TypeSafetyGenericsExample {
public static class Box<T> {
T item;
…
}
public static void main(String [] args) {
…
Integer value = box.get();
…
}
}
아래는 Generic 을 사용한 버블소트 정렬 메소드 예제.
import java.util.Arrays;
public class Box {
public static <T extends Comparable<T>> void bubbleSort(T[] items) {
for(int i=items.length-1; i>0; i--) {
for(int j=0; j<i; j++) {
if(items[j].compareTo(items[j+1]) > 0) {
T item = items[j];
items[j] = items[j+1];
items[j+1] = item;
}
}
}
}
public static void main(String[] args) {
Integer[] integerList = {1, 10, 7, 2, 5, 4, 9, 8, 3, 6};
String[] stringList = {"기욱", "채호", "John", "Michael",
"William", "Noah", "ASAP", "Jacob"};
System.out.println("정렬 전 : " + Arrays.toString(integerList));
bubbleSort(integerList);
System.out.println("정렬 후 : " + Arrays.toString(integerList));
System.out.println("정렬 전 : " + Arrays.toString(stringList));
bubbleSort(stringList);
System.out.println("정렬 후 : " + Arrays.toString(stringList));
}
}
정렬 전 : [1, 10, 7, 2, 5, 4, 9, 8, 3, 6]
정렬 후 : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
정렬 전 : [James, Robert, John, Michael, William, Noah, Liam, Jacob]
정렬 후 : [Jacob, James, John, Liam, Michael, Noah, Robert, William]
class Pair<K, V> {
private K key;
private V value;
public K getKey() {
return this. key;
}
public V getValue() {
return this. value;
}
}
class Box<T> {
T box;
public Box(T box) {
this.box = box;
}
}
위와 같이 선언한 클래스는 아래와 같은 방식으로 생성할 수 있습니다.
Box<Integer> box = new Box<Integer>(1);
Box<Integer> box = new Box<Integer>();
Box<String> box = new Box<String>();
Box<Object> s = new Box<String>();
public class GenericMethod {
public static <T> List<T> arrayToList(T[] array) {
List<T> list = new LinkedList<T>();
for(T t : array) {
list.add(t);
}
return list;
}
public static void main(String[] args) {
Integer[] array = {1, 2, 3};
GenericMethod me = new GenericMethod();
//메소드 호출 시 메소드 이름앞에 <> 기호와 함께 타입 파라미터를 선언해야 합니다.
List<Integer> list = me.<Integer>arrayToList(array);
}
}
T 는 레퍼런스 타입이므로 아래의 코드는 컴파일이 불가능하다.
public <T extends Number> T add(T i, T j) {
return i+j; // 에러
}
public <T> void fromArrayToCollection (T[] array, Collection<T> collection) {
for(T t : array) {
collection.add(t);
}
}
String[] sa = new String[100];
Collection<String> cs = new ArrayList< >();
fromArrayToCollection(sa, cs); // T는 String으로 추론됨
String[] sa = new String[100];
Collection<Integer> ci = new ArrayList< >();
fromArrayToCollection(sa, ci); // ERROR : T는 String과 Integer로 추론됨
타입 파라미터에는 기본 데이터 타입을 제외한 모든 타입을 사용할 수 있으며, 정의되는 타입 내에서 변수 선언의 타입으로 사용됩니다.
타입 파라미터 명명 권장 사항
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
Pair<String, Integer> p1 = new OrderPair("Even", 8);
Pair<String, String> p2 = new OrderPair("Hello", "World");
Pair<String, int> p1 = new OrderedPair<String, int>("Even", 8); //ERROR
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // OK
box.add(new Double(10.1)); // OK
public class Product<T, M> {
private T kind;
private M model;
public T getKind() {
return kind;
}
public void setKind(T kind) {
this.kind = kind;
}
public M getModel() {
return model;
}
public void setModel(M model) {
this.model = model;
}
}
public class ChildProduct<T, M, C> extends Product<T, M> {
private C company;
public C getCompany() {
return company;
}
public void setCompany(C company) {
this.company = company;
}
}
파라미터나 return 값이 특정한 타입 파라미터를 정의하지 않고 임의의 타입을 지정
무제한 와일드 카드
void genericMethod(Collection<?> collection) { ... }
void genericMethod(List<? extends Number>) { ... }
void genericMethod(List<? super Number>) { ... }
Generics에서 타입 파라미터는 컴파일 타임에만 사용되고, 컴파일 후에는 삭제 후 Object 타입으로 치환되어 바이트 코드에는 Generic에 관한 정보가 저장되지 않습니다.
Generic 타입 파라미터는 컴파일시에 모두 삭제된다. 타입 파라미터가 제한되지 않는 경우, Object 타입으로 변경된다. 타입 제한이 있는 경우, 해당 타입으로 변경됨
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() {
return data;
}
}
해당 소스가 컴파일 시 아래의 코드로 변경된다.
public class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
public Comparable getData() {
return data;
}
}
Comparable의 서브 타입으로 Generic 파라미터를 제한합니다. 이 경우 컴파일러는 타입 파라미터 T를 첫 번째 바인딩된 타입인 Comparable로 대치합니다.
Generic 타입과 동일하게 타입 파라미터는 삭제되며, 타입 제한이 없는 경우 타입 파라미터는 Object 타입으로 변경된다. 타입 제한이 없는 경우 해당 타입으로 변경됨
public static <T extends Shape> void draw(T shape) { ... }
해당 소스가 컴파일 시 아래의 코드로 변경된다.
public static void draw(Shape shape) { ... }
컴파일러는 draw Generic 메소드의 타입 파라미터 T를 Shape로 대치합니다.
Generic 타입은 일반 타입과 달리 제약 사항들이 있습니다.
타입 파라미터 인자로 기본 데이터 타입을 사용할 수 없음
타입 파라미터 변수의 객체를 생성할 수 없음
public static <E> void append(List<E> list, Class<E> cls) {
E elem = cls.newInstance();
list.add(elem);
}
Generic 타입은 타입 변환 될 수 없으며, instanceof 연산자를 사용할 수 없음
Generic 타입의 배열은 생성할 수 없음
Exception 타입의 서브 타입이 될 수 없음
// 직접적으로 Exception 클래스 확장
class MathException<T> extends Exception { /* ... */ } // 컴파일시 오류
// Throwable 직접적으로 확장
class QueueFullException<T> extends Throwable { /* ... */ } // 컴파일시 오류
public class Example {
public void print(Set<String> set) { }
public void print(Set<Integer> set) { }
}
타입 파라미터는 모두 컴파일시 Object로 치환되기 때문이다.