generic이란 data type을 generalize(일반화)하는 것을 의미한다. 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법이다.
Object를 여러타입을 사용하는 클래스나 메소드에 사용했다. 하지만 이 경우에는 반환된 Object객체를 다시 원하는 타입으로 변환해야하고 이때 오류가 발생하는 경우가 생긴다. Java5부터 도입된 제네릭을 사용하면 컴파일 시에 타입이 정해지므로 타입 검사나 타입 변환고 같은 번거로운 작업을 생략할 수 있다.제네릭은 아래 표의 타입들이 많이 사용된다.
| 타입 | 설명 |
|---|---|
| < T > | Type |
| < E > | Element |
| < K > | Key |
| < V > | Value |
| < N > | Number |
public class ClassName <T> {...}
public Interface InterfaceName <T> {...}
이렇게 선언할 수 있고 더 나아가 제네릭 타입을 두개로 둘 수도 있다.
public class ClassName <T, K> {...}
public Interface InterfaeName <T, K> {...}
이렇게 생성된 제네릭 클래스를 사용할 때, 객체를 생성하게 되는데 이때 구체적인 타입을 명시해 준다.
public class ClassName <T, K> {...}
public class Main{
public static void main(String[] args){
ClassName<String, Integer> a = new ClassName<String, Integer>();
}
}
T가 String, K가 Integer가 되는 것이다. 주의할 점은 타입 파라미터로 면시할 수 있는 것은 참조타입 뿐이다.
이 말은 아래의 코드처럼 사용자가 정의한 클래스도 타입으로 올 수 있다는 뜻이다.
public class ClassName <T> {...}
public class Student {...}
public class Main{
public static void main(String[] args){
ClassName<Student> a = new ClassName<Student>();
}
}
제네릭 메소드는 타입 매개변수를 사용하는 메소드이다. 제네릭 타입을 선언하는 것과 비슷하지만 제네릭 메소드에서 타입 매개변수의 scope는 선언된 메소드로 제한된다.
public <T> T genericMethod(T o){
...
}
[접근제어자] <제네릭타입> [반환타입] [메소드명]([제네릭타입] [파라미터]){
...
}
public class Util{
public static <T> WitchPot<T> put(T t){
return new WitchPot<>(t);
}
}
public class WitchPot<T> {
private T meterial;
public WitchPot(T meterial) {
this.meterial = meterial;
}
public static void main(String[] args) {
String frog = "개구리";
//명시적으로 타입 파라미터를 지정
WitchPot<String> pot = Util.<String>put(frog);
//반환대상이 WitchPot<String> 인것을 이용하여 String으로 추정
WitchPot<String> pot2 = Util.put(frog);
System.out.println(pot.meterial);
System.out.println(pot2.meterial);
}
}
컴파일러가 제네릭 메소드의 반환대상의 타입을 미리 검사하여 타입을 추론할 수 있어 타입 파라미터의 생략이 가능하다.
제네릭 타입에서 타입 인자로 사용할 수 있는 타입을 제한하려고 할때가 있다. 클래스나 인터페이스를 설계할 때 가장 흔하게 사용된다.
public class Person<T extends Number> {...}
public interface Person<T extends Number> {...}
만약 위와 같이 제네릭으로 파라미터를 정의한 경우 Number의 하위 클래스가 아닌 클래스는 타입 파라미터로 적용되는 것이 불가능하다.

다음 컴파일 오류를 보자. BoundTypeSample클래스의 Type 파라미터를 T로 선언하고 <T extends Number>로 선언한다. BoundTypeSample의 타입으로 Number의 서브타입만 허용한다.
하지만 Integer는 Number의 서브타입이기 때문에 BoundTypeSample과 같은 선언이 가능하지만 set함수의 인자로 문자열을 전달하려고 했기 때문에 컴파일 에러가 발생한다.
제네릭을 사용하면 타입이 제한된다. 와일드 카드는 ?와 extends, super을 사용해 만들 수 있다.
<? super T> : 와일드 카드의 상한 제한으로 T와 그 자식들만 가능하다.<? extends T> : 와일드 카드의 하한 제한으로 T와 그 조상들만 가능하다.<?> : 제한없이 모든 타입이 가능하다. <? extends Object>와 동일하다.예를들어 Person을 상속받은 Jieun 클래스가 있다.
public class Person {}
public class Jieun extends Person {}
public static void main(String[] args){
List<? super Person> test = new ArrayList<Person>();
List<? super Person> test2 = new ArrayList<Jieun>(); //컴파일 에러
List<? extends Person> test3 = new ArrayList<Person>();
List<? extends Person> test4 = new ArrayList<Jieun>();
}
다음과 같이 ?extends Person을 사용한 경우만 Jieun을 사용해 다형성을 사용할 수 있다.
erasure란 원소타입을 컴파일 시에 검사하고 런타임에는 해당 타입 정보를 알 수 없는 것으로 컴파일을 할 때 타입에 대한 제약 조건을 적용하고 런타임에는 타입정보를 Erasure하는 것을 말한다.
자바의 Erasure 는 제네릭 사용 시 자바의 하위 버전과의 호환성을 지키기 위해 컴파일을 할때만 타입을 체크하고 런타임에는 체크를 하지 않는다.
public satic void main(String[] args){
List<String> erasureTest = new ArrayList<>();
}
이를 실행하게 되면
//access flags 0x9
public static main([Ljava/lang/String;)V
//parameter args
L0
LINENUMBER 8 L0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 1
L1
LINENUMBER 9 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
LOCALVARIABLE erasureTest [Ljava/util/List; L1 L2 1
// signarture Ljava/util/List<Ljava/lang/String;>;
// declaration: erasureTest extends java.util.List<java.lang.String>
MAXSTACK = 2
MAXLOCALS = 2
다음과 같이 erasureTest 객체는 List일 뿐 String이라는 정보는 아래 주석으로만 남겨지고 사라진것을 볼 수 있다.