티스토리 뷰
제네릭스(Generics) - 1
제네릭스란?
다양한 타입의 객체를 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크(compile-time type check)를 해주는 기능이다. 제네릭 타입은 클래스와 메서드에 선언할 수 있다.
제네릭스의 장점?
객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성이 높아지고, 형변환의 번거로움이 줄어든다.
제네릭 클래스 선언
// 일반적인 클래스
class Box {
Object item;
void setItem(Object item) {this.item = item;}
Object getItem() {return item;}
}
// 제네릭 클래스
class Box<T> {
T item;
void setItem(T item) {this.item = item;}
T getItem() {return item;}
}
T란 무엇인가?
위에 제네릭 클래스를 선언할 때 뒤에 <T>
가 붙었는데, 여기서 T는 타입 변수(type variable)이라고 한다. 일종의 매개변수 같은 역할을 하면서, 그 자리에 어떠한 참조형 타입이 들어가게 될 것이라는 표시를 해주는 것이다. 임의의 변수이므로 반드시 T가 아니어도 되고 상황에 따라 E, K, V 등 다양한 문자를 사용할 수 있다.
Box<String> b= new Box<String>(); // 실제 타입을 지정
b.setItem("ABC"); // OK
b.setItem(3); // Err! 스트링 타입만 넣어야 함
인스턴스를 만들 때에 T자리에 넣고 싶은 참조변수 타입을 넣으면 된다. 그러면 그 클래스 내에 T가 있던 자리를 그 참조변수 타입이 대체하는 것과 마찬가지다.(실제로 컴파일 뒤에는 제네릭 타입은 제거되고 원시타입 Box로 대체된다.) 위처럼 String을 넣었으면 item의 타입은 String이 될 것이고, Integer를 넣었으면 item의 타입은 Integer가 된다.
용어 정리
헷갈리는 용어들을 짚고 넘어가자.
Box<T> // 제네릭 클래스. T Box라고 읽는다.
T // 타입 변수 또는 타입 매개변수
Box // 원시 타입(raw type)
제네릭스의 제한
제네릭 클래스에 허용되지 않는 것이 있다.
- static 멤버에 대해 타입 매개변수를 사용하는 것.
- 제네릭 타입의 배열을 생성하는 것.
class Box<T> {
static T item; // Err!
T[] itemArr; // OK
itemArr = new T[10]; // Err!
}
static 멤버에 타입변수를 쓸 수 없는 이유는, T는 인스턴스 변수로 간주되기 때문이다. static멤버는 대입된 타입에 관계 없이 항상 같은 값을 유지해야 한다. 즉 Box<Grape>.item
과 Box<Apple>.item
이 달라져서는 안된다는 뜻이다.
제네릭 타입의 배열 생성이 안 되는 이유는 new 연산자 때문인데, 이 연산자는 컴파일 시점에서 타입 T가 정확히 무엇인지 알기를 원하기 때문이다. 그러나 위 코드에서는 T가 어떤 참조변수 타입으로 선언될지 전혀 할 수 없기 때문에 에러가 난다. (그러나 제네릭 배열을 꼭 만들고 싶은 경우에는 Reflection API의 newInstance()와 같은 메서드를 사용하거나 Object 배열을 생성하여 복사한 다음 T로 형변환하는 경우가 있다고 한다.)
제네릭 클래스의 객체 생성
Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Apple> appleBox = new Box<Grape>(); // Err
Box<Apple> appleBox = new FruitBox<Apple>(); // OK. 다형성
Box의 객체를 생성할 때에는 참조변수와 생성자에 대입된 타입이 일치해야 한다. 즉 <>
안의 타입은 일치해야 한다. 그래서 실제로 사용할 때에는 new 연산자 뒤의 <> 안에 타입을 생략할 수 있다. (항상 일치해야하므로 앞에만 써도 유추가능하다) 다만 class의 경우 상속관계에 있다면 기존의 다형성이 적용된다.
Box<Fruit> fruitBox = new Box<>();
fruitBox.add(new Fruit());
fruitBox.add(new Grape()); // OK!
다만 Box의 참조변수 타입이 Fruit이라고 선언되었을 때, void add 메서드에서 Fruit의 자손 인스턴스들은 메서드의 매개변수가 될 수 있다.
제한된 제네릭 클래스
그렇다면 제네릭 클래스 Box 에서는 실제로 대입할 때에는 어떠한 참조변수가 들어가도 막을 방법이 없는 것일까?
FruitBox<Toy> fruitbox = new FruitBox<>(); // OK, but...
위 예시에서는 FruitBox에 Toy라는 타입이 지정되어 있다. 구문적으로 문제는 없으나, T에 들어갈 수 있는 타입을 제한하고 싶을 수 있다.
class FruitBox<T extends Fruit> {
...
}
이 경우 제네릭 클래스 선언 시, extends
를 사용함으로써 특정 클래스의 자손만 받도록 제한할 수 있다. 만약 클래스가 아니라 인터페이스를 구현해야 한다고 해도 implements
가 아닌 extends
를 사용한다는 점에 주의해야 한다.
'Java' 카테고리의 다른 글
[자바] 제네릭스(Generics) - 2 (와일드카드) (0) | 2019.05.30 |
---|---|
[자바] 제네릭스 문제로 이해하기 (0) | 2019.05.30 |
[표준입출력] nextInt(), nextLine() 차이 (0) | 2019.05.28 |
JDBC 데이터 삽입, 수정, 삭제 (0) | 2019.05.22 |
JDBC (0) | 2019.05.22 |
- Total
- Today
- Yesterday
- 자바
- oracle
- 제네릭스
- SQL
- GIT
- til
- this
- 알고리즘
- jQuery
- Redux
- 인스턴스
- package.json
- JavaScript
- c언어
- Data Structure
- useEffect
- youtube data api
- getter
- linkedlist
- Java
- rxjs
- 리덕스
- CSS
- Conflict
- Session
- 깃
- 포인터 변수
- react
- 개발 공부
- Prefix Sums
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |