정의 : 클래스 내부에서 사용할 데이터의 Type을 외부에서 지정하는 기법을 의미한다.
예제 코드를 보자.
public class Animal<T> {
// 현재 data 필드의 Type을 명시적으로 선언하지 않음
public T data;
}
// 이때는, data의 Type이 String으로 정해진다.
// A1.data의 Type -> String
Animal<String> A1 = new Animal<String>();
// 이때는, data의 Type이 StringBuilder로 정해진다.
// A2.data의 Type -> StringBuilder
Animal<StringBuilder> A2 = new Animal<StringBuilder>():
여러 개의 Generic이 필요한 경우는 어떻게 처리할지 알아보자.
Reference Data Type만 Generic에서만 사용이 가능하다.
Primitive Data Type에 대해서는 사용이 불가능
class PersonInfo{
public int rank;
PersonInfo(int rank) { this.rank = rank;}
}
class Person<T,S>{
public T info;
public S id;
Person(T info, S id){
this.info = info;
this.id = id;
}
}
public class Main{
public static void main(String[] args){
PersonInfo pi = new PersonInfo(1);
Interger i = new Integer(2);
// 이와 같이 기본 데이터 타입은 적용이 불가능하므로, 참조형 데이터 타입으로 바꿔줄 필요가 있다.
Person<PersonInfo, Interger> p1 = new Person<PersonInfo, Interger>(pi,i);
}
}
/* -- Class 구현 부분 -- */
class PersonInfo{
public int rank;
PersonInfo(int rank) { this.rank = rank;}
}
class Person<T,S>{
public T info;
public S id;
Person(T info, S id){
this.info = info;
this.id = id;
}
}
public class Main{
public static void main(String[] args){
PersonInfo pi = new PersonInfo(1);
Interger i = new Integer(2);
/* -- 이전 파트에서 Generic 생략하지 않고 사용한 부분 -- */
Person<PersonInfo, Interger> p1 = new Person<PersonInfo, Interger>(pi,i);
/* -- Generic 생략한 부분 -- */
Person p1 = new Person(pi,i);
}
}
pi, i -> Argument를 생성자에서 전달하면
Person(T info, S id)
여기서 info 라는 부분은 pi가 PersonInfo라는 타입으로 들어왔기 때문에 추측이 가능하고
여기서 id라는 부분은 i가 Interger라는 타입으로 들어왔기 때문에 추측이 가능하다.
따라서, 위와 같은 이유로 Generic을 생략하고 생성자를 호출할 수 있다.
// 기존의 Person class에 Generic Method를 선언해보겠다.
class Person<T,S>{
public T info;
public S id;
Person(T info, S id){
this.info = info;
this.id = id;
}
public <U> void printInfo(U info){
System.out.println(info)
}
}
public class Main{
public static void main(String[] args){
PersonInfo pi = new PersonInfo(1);
Interger i = new Integer(2);
Person p1 = new Person(pi,i);
/* -- Generic 생략하지 않고 사용한 부분 -- */
p1.<PersonInfo>printInfo(pi);
/* -- Generic 생략한 부분 -- */
p1.printInfo(pi);
}
}
// 부모 클래스
abstract class Info{
public abstract int getLevel();
}
// 자식 클래스
class EmployeeInfo extends Info{
public int rank;
EmployeeInfo(int rank) { this.rank = rank; }
pubic int getLevel(){
return this.rank;
}
}
// extends -> 상속으로 제한시키는 방법
class Person<T extends Info>{
public T info;
Person(T info) { this.info = info; }
}
// 인터페이스
interface Info{
int getLevel();
}
// 구현 클래스
class EmployeeInfo implements Info{
public int rank;
EmployeeInfo(int rank) { this.rank = rank;}
public int getLevel(){
return this.rank;
}
}
// implements -> 구현으로 제한시키는 방법, 하지만, extends를 써야한다.
class Person<T extends Info>{
public T info;
Person(T info) { this.info = info; }
}
public class Test<T> {
// 이렇게 타입 클래스로 배열을 선언은 가능하다.
private T[] array;
// 하지만, 아래와 같이 초기화 하면 작동하지 않는다ㅡ.
array = new T[10];
// 초기화를 위해서는 Object배열로 초기화가 필요하다.
array = (T[]) new Object[10];
}
<?>
: 모든 클래스나 인터페이스 타입이 올 수 있다. <? extends UpperType>
: 상위 클래스로 제한한 타입이 올 수 있다. <? super LowerType>
: 하위 클래스로 제한한 타입이 올 수 있다.