제네릭은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다.
위 코드의 p.infor와 p2.infor의 데이터 타입은 결과적으로 아래와 같다.
p1.infor : String
p2.infor : Stringbuilder
각각의 인스턴스를 생성할 때 사용한 < > 사이에 어떤 데이터 타입을 사용했느냐에 달려있다.
제네릭의 사용 이유 :
위의 코드는 StudentPerson과 EmployyPerson가 사실상 같은 구조를 가지고 있다 중복이 발생하고 있는 것이다. 중복을 제거해보자.
클래스 Person의 생성자는 매개변수 info의 데이터 타입이 Object이다. 따라서 모든 객체가 될 수 있다. 그렇기 때문에 위와 EmployeeInfo의 객체가 아니라 String이 와도 컴파일 에러가 발생하지 않는다. 대신 런타임 에러가 발생한다. 컴파일 언어의 기본은 모든 에러는 컴파일이 발생할 수 있도록 유도해야 한다는 것이다. 런타임은 실제로 애플리케이션이 동작하고 있는 상황이기 때문에 런타임에 발생하는 에러는 항상 심각한 문제를 초래할 수 있기 때문이다.
위와 같은 에러를 타입에 대해서 안전하지 않다고 한다. 즉 모든 타입이 올 수 있기 때문에 타입을 엄격하게 제한 할 수 없게 되는 것이다.
타입이 안전하지 않는 문제를 해결하기
Person이 StudentInfoPerson과 EmployyPerson처럼 나누어져 있을 때는 타입이 안전하다는 장점이 있었다.
코드의 중복을 제거하면 편의성은 있지만 타입이 안전하지 않다는 문제가 있다. 이 두마리 토끼를 잡기위해 도입된 기능이 제네릭이다.
p1은 잘 동작할 것이다. 중요한 것은 p2다. p2는 컴파일 오류가 발생하는데 p2.info가 String이고 String은 rank 필드가 없는데 이것을 호출하고 있기 때문이다. 여기서 중요한 것은 아래와 같이 정리할 수 있다.
컴파일 단계에서 오류가 검출된다.
중복의 제거와 타입 안전성을 동시에 추구할 수 있게 되었다.
복수의 제네릭 :
제네릭<>에는 참조 데이터 타입만이 올 수 있다. 즉 기본데이터 타입(int, char, double ..)은 불가능하다.
이 기본 데이터 타입을 사용하기 위해 레퍼 데이터 타입을 쓰면 된다. 기본 데이터 타입을 마치 객체인 것처럼 만들 수 있는 객체를 제공한다. 이를 레퍼 클래스라 한다. int는 Integer, double는 Double .. 등이 있다.
기본 데이터 타입과 제네릭 :
new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다. 이러한 클래스를 래퍼(wrapper) 클래스라고 한다. 덕분에 기본 데이터 타입을 사용할 수 없는 제네릭에서 int를 사용할 수 있다.
제네릭의 생략 :
EmployeeInfo e = new EmployeeInfo(1);
int i = 10;
이렇게 e와 i가 어떤 타입인지 알고 있을 때
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
위의 코드에 <>를 생략할 수 있다 왜냐하면 e, i의 데이터 타입을 이미 알고 있기 때문이다.
제레닉은 메소드로도 사용 가능하다.
제네릭의 제한 :
즉 Person의 T는 Info 클래스나 그 자식 외에는 올 수 없다.
extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.
interface로 했는데 Person이 왜 implements가 아닌 extends인 것이 의문일 것이다.
extends는 제네릭안에서 사용될 때는 상속이 아니라 부모가(Info의 부모) 누구인지 가르키기 때문이다.
'코딩 > Java' 카테고리의 다른 글
Collections framework2 (0) | 2021.01.11 |
---|---|
Collections framework (0) | 2021.01.11 |
참조와 복제 (0) | 2021.01.09 |
상수와 enum2 (0) | 2021.01.09 |
상수와 enum (0) | 2021.01.08 |