스크롤이 다소 길기 때문에 먼저 요약을 하자면.
다루려고 하는 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여준다. 1. 타입 안정성을 제공한다. 2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다. Box<T>, ArrayList<E>, Map<K, V>의타입변수 T, E ,K ,V는 기호의 종류만 다를 뿐 '임의의 참조형 타입'을 의미한다는 것은 모두 같다. static 멤버에 타입 변수 T를 사용할 수 없다. 타입변수는 인스턴스변수이기 때문에 static이 인스턴스에 접근할 수 없듯이 static멤버가 인스턴스 타입변수 T를 사용 할 수 없다. new T[] , T instanceof등에 사용 타입변수를 사용 할 수 없다. 이유는 new 연산자 때문이다. new 연산자는 컴파일 시점에 타입 T가 어떤 타입이 될지 정확히 알 아야 하지만 이를 알 수 없기 때문이다. (instanceof 도 사용 불가) |
제네릭스?
간단히 말하자면 다루려고 하는 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여준다.
제네릭스는 다양한 타입의 객체를 다루는 메서드, 컬렉션 클래스에 컴파일 시의
타입체크(Compile - type check)를 해주는 기능인데 컴파일시에 객체의 타입을 체크하기 때문에
객체의 타입 안정성을 높이도 형변환의 번거로움이 줄어든다.
의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때
원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 문제를 줄여준다.
제네릭스의 장점 1. 타입 안정성을 제공한다. 2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다. |
제네릭스의 용어
제네릭스에서 사용되는 용어는 헷갈리기 쉽우니 제네릭스의 용어를 정리 하도록 하자👍
Box<T> 제네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다. T 타입변수 또는 타입 매개변수.(T는 타입 문자) Box 원시 타입(raw type) |
타입문자 T는 제네릭 클래스 Box<T>의 타입 변수 또는 타입 매개변수라 하는데
메소드의 매개변수와 유사한 면이 있기 때문이다.
그래서 아래와 같이 타입 매개변수에 타입을 지정하는 것을 제네릭 타입 호출이라 하고
지정된 타입 String을 대입된 타입 이라고 한다.
제네릭 클래스 선언
제네릭 타입은 클래스와 메서드에 선언할 수 있다
예를 들어 클래스 Box가 다음과 같이 정의되어 있다고 가정하자
class Box {
Object item;
void setItem(Object item) {
this.item = item;
}
Object getItem() {
return item;
}
}
위의 코드를 제네릭 클래스로 변경하려면 다음과 같이 클래 옆에'<T>'를 붙이면 된다.
class Box<t> { // 제네릭 타입 T를 선언
T time;
void setItem(T time){
this.item = item;
}
T getItem() {
return item;
}
}
Box<T>에서 T를 '타입변수(type variable)'라고 하며, 'Type'의 첫 글자에서 따온 것이다.
ArrayList<E> 타입 변수 E는 'Element(요소)'의 첫 글자를 따서 사용된 것이다.
타입 변수가 여러개인 경우 Map<K, V>와 같이 콤마 ','를 구분자로 나열하게 된다.
타입변수 T, E ,K ,V는 기호의 종류만 다를 뿐 '임의의 참조형 타입'을 의미한다는 것은 모두 같다.
이제 위의 제네릭 클래스가 된 Box클래스의 객체를 사용하기 위해 생성할 때는 참조변수와 생성자에 타입 T대신에
사용될 실제 타입을 지정해주어야 한다.
Box<String> b = new Box<String>() // 타입 T 대신, 실제 타입을 지정
b.setItem(new Object()); // 에러. String이외의 타입은 지정불가
b.setItem("ABC"); // OK. String타입이므로 가능
//String item = (String) b.getItem(); // 제네릭으로 String을 설정했기에 형변환도 필요 없다.
String item = b.getItem(); // 형변환 필요없음
위의 코드에서 타입 T대신 String타입을 지정해줬으므로, 제네릭 클래스 Box<T>는 아래와 같이 정의된 것이다.
class Box { // 제네릭 타입을 String으로 지정
String item;
void setItem(String item) {
this.item = item;
}
String getItem() {
return item;
}
}
제네릭 클래스여도 예전의 방식으로 객체를 생성하는 것이 허용된다.
다만 제네릭 타입을 지정하지 않아 안전하지 않다는 경고가 발생한다.
Box b= new Box(); // Ok. T는 Object로 간주된다
b.setItem("ABC"); // 경고. unchecked or unsafe operation
b.setItem(new Object()); // 경고. unchecked or unsafe operation
하지만 아래 코드처럼 변수 T에 Object타입을 지정하면 타입을 지정하지 않은 것이 아니라
알고 적었기 때문에 경고가 발생하지 않는다.
Box<Object> b = new Box<Object>();
b.setItem("ABC"); // 경고 발생 안함
b.setItem(new Obejct()); // 경고 발생 안함
제네릭스의 제한
static 멤버에 타입 변수 T를 사용할 수 없다.
타입 변수 T는 인스턴스변수로 간주되기 때문에 static 멤버에 타입 변수 T를 사용할 수 없다
static은 인스턴스 변수를 참조할 수 없기 때문이다.
class Box<T> {
static T item; // 에러
static int compare(T t1, T t2) { ... } // 에러
}
제네릭 타입의 배열을 생성할 수 없다.
제네릭 배열 타입의 참조변수를 선언하는 것은 가능하지만
'new T[10]'과 같이 배열을 생성하는 것은 안 된다.
class Box<T> {
T[] itemArr; // OK. T타입의 배열을 위한 참조변수
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; // 에러. 제네릭 배열 생성불가
return tmpArr;
}
}
제네릭 배열을 생성할 수 없는 것은 new연산자 때문이다
new 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 하지만
코드에 정의된 Box<T>클래스를 컴파일 하는 시점에서 T가 어떤 타입이 될지
알 수 없기 때문이다.
instanceof 연산자도 new 연산자와 같은 이유로 T를 피연산자로 사용할 수 없다.
댓글