제네릭은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법이다.
package practice;
class Student<T> {
public T info;
}
public class GenericDemo {
public static void main(String[] args) {
Student<String> s1 = new Student<String>();
Student<StringBuilder> s2 = new Student<StringBuilder>();
}
}
s1.info와 s2.info의 데이터 타입은 각각의 인스턴스를 생성할 때 사용한 <> 사이에 어떤 데이터 타입을 사용했느냐에 달려있다.
public T info;
: 클래스 Student의 필드 info의 데이터 타입은 T이다. 이 값은 class Student<T>{}
의 T에서 정해진다. 여기서의 T는 Student<String> s1 = new Student<String>();
의 <>
안에 지정된 데이터 타입에 의해서 결정된다.
Student<String> s1 = new Student<String>();
에서 Student<String> p1
은 변수 p1의 데이터 타입을 정의하고 있고, new Student<String>();
은 인스턴스를 생성하고 있다.
👓 제네릭이란 클래스를 정의할 때 info의 데이터 타입을 확정하지 않고
인스턴스를 생성할 때 데이터 타입을 지정하는 기능
이다.
package practice;
class StudentInfo{
public int grade;
StudentInfo(int grade){ this.grade = grade; }
}
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
}
class Person{
public Object info;
Person(Object info) { this.info = info; }
}
public class GenericDemo {
public static void main(String[] args) {
Person p1 = new Person("부장");
EmployeeInfo ei = (EmployeeInfo)p1.info;
System.out.println("Rank is... " + ei.rank); // 1
}
}
해당 코드를 컴파일하면 런타임 에러가 발생한다. 클래스 Persion의 생성자는 매개변수 info의 데이터 타입이 Object여서, 모든 객체가 될 수 있기 때문이다.
package practice;
class StudentInfo{
public int grade;
StudentInfo(int grade){ this.grade = grade; }
}
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T>{
public T info;
Person(T info) { this.info = info; }
}
public class GenericDemo {
public static void main(String[] args) {
Person<EmployeeInfo> p1 = new Person<EmployeeInfo>(new EmployeeInfo(1));
EmployeeInfo ei1 = p1.info;
System.out.println("Rank is... " + ei1.rank); // 성공
Person<String> p2 = new Person<String>("부장");
String ei2 = p2.info;
System.out.println(ei2.rank); // 컴파일 실패
}
}
p2는 컴파일 오류가 발생한다. p2.info가 String이고, String은 rank 필드가 없는데 이를 호출하고 있기 때문이다. 이로써 제네릭을 사용하는 이유는 다음과 같다고 볼 수 있다.
클래스 내에서 여러 개의 제네릭을 필요로 하는 경우가 있다.
package practice;
class EmployeeInfo{
public int rank;
EmployeeInfo(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 GenericDemo {
public static void main(String[] args) {
Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int>(new EmployeeInfo(1), 1);
}
}
제네릭은 참조 데이터 타입에 대해서만 쓸 수 있다. (기본 데이터 타입에서는 쓸 수 없음)
package practice;
class EmployeeInfo{
public int rank;
EmployeeInfo(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 GenericDemo {
public static void main(String[] args) {
EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
System.out.println(p1.id.intValue());
}
}
new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다. 이러한 클래스를 '래퍼(wrapper) 클래스'라고 한다. 이 덕분에 기본 데이터 타입을 사용할 수 없는 제네릭에서 int를 사용할 수 있다.
제네릭은 생략 가능하다.
public class GenericDemo {
public static void main(String[] args) {
EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
Person p2 = new Person(e, i);
}
}
e와 i의 데이터 타입을 이미 알고 있으므로 Person p2 = new Person(e, i);
만 작성해도 된다.
package practice;
class EmployeeInfo{
public int rank;
EmployeeInfo(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 <U> void printInfo(U info){
System.out.println(info);
}
}
public class GenericDemo {
public static void main(String[] args) {
EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
p1.<EmployeeInfo>printInfo(e);
p1.printInfo(e);
}
}
Reference
1. 제네릭