쉽게 말하자면 데이터 타입을 일반화하는 것이다.
클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 것이다.
JDK 1.5 이전의 경우 여러 타입을 사용하는 클래스나 메서드에서 매개변수나 반환값으로 Object를 사용했다. 이 경우 Object 타입을 다시 원하는 타입으로 변환해야 하며, 그 과정에서 오류가 날 가능성이 있었다.
JDK 1.5부터는 제네릭을 사용하면서 컴파일 시에 미리 타입이 정해지므로, 타입 변환 같은 번거로운 작업을 생략할 수 있다.
class MyArray<T> {
T element;
void setElement(T element) { this.element = element; }
T getElement() { return element; }
}
위에서 사용된 T를 말하며, 임의의 참조형 타입을 의미한다. 어떠한 문자를 사용하여도 상관없으며, 타입 변수 여러 개를 사용하는 경우 쉼표로 구분하여 명시한다. 메서드의 매개변수나 반환값으로도 사용 가능하다.
변수를 선언하거나 인스턴스 생성 시 타입 변수 자리에는 실제 타입을 명시해야 한다. 아래와 같이 실제 타입을 명시하면, 내부적으로 타입 변수가 실제 타입으로 처리된다.
MyArray<Integer> myArr = new MyArray<Integer>();
Java SE 7부터는 인스턴스 생성 시 타입 추정이 가능한 경우 생략 가능하다.
MyArray<Integer> myArr = new MyArray<>();
바로 T의 배열을 생성하지 않고 Object 배열을 생성 후 T[]로 형변환을 해야 한다. 선언부의 경우 T가 결정되기 전이기 때문에 배열을 생성할 수가 없는 것이다.
public class Course<T> {
private String name;
private T[] students;
public Course(String name, int size){
super();
this.name = name;
this.students = (T[])new Object[size];
}
}
타입 변수도 다형성을 가져서, 부모 클래스가 타입 변수로 사용된 경우 자식 클래스의 인스턴스가 참조될 수 있다.
import java.util.*;
class LandAnimal {
public void crying() { System.out.println("육지동물");}
}
class Cat extends LandAnimal {
public void crying() { System.out.println("냐옹냐옹"); }
}
class Dog extends LandAnimal {
public void crying() { System.out.println("멍멍"); }
}
class Sparrow { public void crying() { System.out.println("짹짹"); } }
class AnimalList<T> {
ArrayList<T> al = new ArrayList<T>();
void add(T animal) { al.add(animal); }
T get(int index) { return al.get(index); }
boolean remove(T animal) { return al.remove(animal); }
int size() { return al.size(); }
}
public class Generic01 {
public static void main(String[] args) {
AnimalList<LandAnimal> landAnimal = new AnimalList<>();
landAnimal.add(new LandAnimal());
landAnimal.add(new Cat());
landAnimal.add(new Dog());
// landAnimal.add(new Sparrow()); // 오류가 발생함.
for (int i = 0; i < landAnimal.size() ; i++) {
landAnimal.get(i).crying();
}
}
}
선언되고 사용된 제네릭 타입은 컴파일 시 컴파일러에 의해 자동으로 검사되어 타입 변환된다. 그 이후에는 모든 제네릭 타입이 제거되어 class 파일에는 제네릭 타입들은 포함되지 않게 된다. 이렇게 동작하는 이유는 제네릭을 사용하지 않는 코드와의 호환성을 위해서다.
extends 키워드를 사용하여 특정 타입만을 사용하도록 제한할 수 있다. 타입 변수에서 특정 클래스의 기능을 사용해야 할 때 유용하다.
class AnimalList<T extends LandAnimal> { ... }
public static <T extneds Number> Student<T> changing(T t){...}
타입 변수가 인터페이스를 구현해야 하는 경우에도 implements 대신 extends를 사용한다.
interface WarmBlood { ... }
class AnimalList<T extends WarmBlood> { ... }
클래스를 상속받는 동시에 인터페이스를 구현해야 한다면, '&' 기호를 사용한다.
class AnimalList<T extends LandAnimal & WarmBlood> { ... }
메서드의 선언부에 타입 변수를 사용한 메서드를 말한다.
타입 변수의 선언이 메서드 선언의 타입 변환 앞에 위치한다. 리턴 타입이나 매개변수의 타입을 제한하기 위해 사용한다.
public class Util {
// 매개변수의 타입이 Person의 타입 변수에 들어가서 return 타입이 결정됨
public static<T> Person<T> changing(T t){
Person<T> person = new Person<>();
person.setT(t);
return person;
}
}
public class PersonEx {
public static void main(String[] args) {
Person<Integer> p1 = Util.<Integer>changing(100);
}
}
우변의 타입은 생략 가능하다.
Person<Integer> p1 = Util.changing(100);
리턴 타입 앞에 타입 변수를 명시하는 부분에서 타입 변수를 제한할 수가 있다.
public class Compare {
public static <T extends Number> int compare(T t1, T t2){
double value1 = t1.doubleValue();
double value2 = t2.doubleValue();
return Double.compare(value1, value2);
}
}
public class CompareEx {
public static void main(String[] args) {
int result = Compare.compare(100, 200);
System.out.println(result);
Compare.compare("홍길동", "김길동");
// String은 Number를 상속받지 않으므로 error
}
}
이름에 제한을 두지 않는다는 것을 표현하기 위해 사용하는 기호다. 자바의 경우 물음표를 사용한다. 주로 제네릭 매개변수의 타입 변수를 제한하기 위해 사용한다.
<?> // 타입 변수에 모든 타입을 사용할 수 있음.
<? extends T> // T 타입과 T 타입을 상속받는 자손 클래스 타입만을 사용할 수 있음.
<? super T> // T 타입과 T 타입이 상속받은 조상 클래스 타입만을 사용할 수 있음.
import java.util.*;
class LandAnimal {
public void crying() { System.out.println("육지동물");}
}
class Cat extends LandAnimal {
public void crying() { System.out.println("냐옹냐옹"); }
}
class Dog extends LandAnimal {
public void crying() { System.out.println("멍멍"); }
}
class Sparrow { public void crying() { System.out.println("짹짹"); } }
class AnimalList<T> {
ArrayList<T> al = new ArrayList<T>();
public static void cryingAnimal(AnimalList<? extends LandAnimal> al)
{
LandAnimal la = al.get(0);
la.crying();
}
void add(T animal) { al.add(animal); }
T get(int index) { return al.get(index); }
boolean remove(T animal) { return al.remove(animal); }
int size() { return al.size(); }
}
제네릭 타입을 부모 클래스로 사용할 경우, 부모 클래스에서 사용한 타입 변수를 자식 클래스에도 반드시 사용해야 한다.
class Student<T, M> extends Person<T, M>{...}
타입 변수를 추가적으로 사용하는 것도 가능하다.
class Student<T, M, C> extends Person<T, M>{...}
제네릭 타입의 인터페이스를 구현하는 경우에도 인터페이스에 사용한 타입 변수를 클래스에서도 사용해야 한다.
class Student<T> implements Comparable<T>
http://tcpschool.com/java/java_generic_concept
http://tcpschool.com/java/java_generic_various