지네릭스의 전반적 개념(1)
1. 지네릭스란?
- 지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 대해서 타입 체크를 해주는 기능을 말한다.
- 그러므로, 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄어든다.
- 타입 안정성을 높이는 것은 의도치 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의
타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여준다는 뜻이다.
- 예를 들면, ArrayList 컬렉션 클래스에 다양한 객체를 담을 수 있지만 한 종류의 객체를 담는 경우가 많다. 그런데도
꺼낼 때 마다 타입 체킹을 하거나 사용자가 원하지 않는 종류의 객체를 저장하는 것을 막지 못하는 불편한 점이 있다.
그러한 것을 해결할 수 있는 것이 바로 '지네릭스'이다.
- 아래는 지네릭스를 적용 전과 후를 보여준다.
적용 전 | 적용 후 |
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; } } |
( 이때, Box<T>에서 T를 '타입 변수' 라고 한다. )
( T를 무조건 쓸 필요 없으며, K이나 V 또는 알파벳 어느 것이든 쓰면 된다. )
( 되도록이면 의미 있게 쓰이도록 하자. ex) Map<K,V> )
( 그래서 위 적용 후의 Box 클래스의 생성자는 아래와 같이 생성하면 된다. )
Box<String> b = new Box<String>();
// T 대신 String으로 타입 지정
// b.setItem(new Object());
// String 이외 다른 타입 불가능 에러
b.setItem("ABC");
// String이므로 정상
String item = b.getItem();
// 지네릭스 전에는
// String item = (String) b.getItem()
// 이였음.
( 지네릭스는 JDK 1.5에서 처음 도입된 것이기 때문에, 이전 코드와 호환을 위해 생성자를 생성할 때 타입 변수에
타입을 지정하지 않아도 컴파일이 가능하다. )
( 그러나, 아래와 같이 경고가 발생하게 된다. )
Box b = new Box();
// T는 Object로 간주. 정상
b.setItem("ABC")
// 경고발생
b.setItem(new Object());
// 경고발생
2. 지네릭스의 용어
class Box<T>{}
( 여기서 Box<T>는 지네릭스 클래스라고 부른다. T의 Box라고도 불린다. )
( T는 Box<T>의 타입변수 또는 타입 매개변수라 부른다. )
( Box는 원시타입이라고 부른다. )
Box<String> b = new Box<String>();
( <String>을 대입된 타입 즉, 매개변수화된 타입이라고 부른다. )
( Box<String>은 지네릭 타입 호출이라고 부른다. )
3. 지네릭스의 제한
- 지네릭스에서 불가능한 것이 아래와 같이 두 가지가 있다.
(1) 모든 객체에 대해 동일하게 동작해야하는 static 멤버에 타입 변수 T를 사용할 수 없다.
: 이유는 타입 문자 T는 인스턴스 변수로 간주하며, static 멤버는 항상 대입된 타입의 종류에 관계없이
동일해야 하기 때문이다.
(2) 지네릭 타입의 배열을 생성하는 것도 허용되지 않는다. 배열 타입의 선언은 가능하지만, 'new T[10];'
같은 것은 불가능하다.
: 이유는 new 연산자 때문인데, 이 연산자는 컴피일 시점에 타입 T가 뭔지 정확히 알아야 한다.
: 그러나, new T[10]을 클래스 로드하는 과정에서 선언했기 때문에 불가능하다.
( 꼭 지네릭 배열을 생성해야할 필요가 있을 때는, new 연산자 대신 'Reflection API'의 newInstance()와 같이
동적으로 객체를 생성하는 메서드로 배열을 생성하거나, 아래와 같이 생성한다. )
4. 지네릭 클래스의 객체 생성과 사용
- Box<T> 같은 지네릭 클래스가 있다고 가정해보자.
( 그렇다면, 참조변수와 생성자에 대입된 타입이 일치해야 한다. 그렇지 않을 경우 에러가 발생한다. )
( 또한 상속 관계에서도 에러가 발생한다. 아래의 코드를 보고 이해하자. )
Box<Apple> appleBox = new Box<Apple>();
// 가능
Box<Apple> appleBox = new Box<Grape>();
// 에러
Box<Fruit> appleBox = new Box<Apple>();
// 에러
( 그러나, 아래와 같이 두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 가능하다. )
( 다형성이 가능하다는 것이다. )
Box<Apple> appleBox = new FruitBox<Apple>();
// 가능. 다형성
( 또한 JDK 1.7부터 추정이 가능한 경우 타입을 생략할 수 있게 되었다. )
( 아래와 같이 두 코드는 동일하게 인식한다. )
Box<Apple> appleBox = new Box<Apple>();
// 위 아래 동일한 코드
Box<Apple> appleBox = new Box<>();
( 조심해야 할 것은 아래와 같이 자신이 대입한 타입에만 객체를 생성할 수 있다. )
Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple());
// 정상
appleBox.add(new Grape());
// 에러 발생
( 그러나 타입 T가 자신의 조상인 Fruit인 경우, ArrayList의 void add(Fruit item) 메서드에서는 아래와 같이
정상적으로 컴파일 할 수 있다. )
Box<Fruit> fruitBox = new Box<Fruit>
fruitBox.add(new Fruit());
// 가능
fruitBox.add(new Apple());
// 가능
4-1. 지네릭 클래스의 객체 생성과 사용을 이해하기 위한 예제(1)
: 위에서 봤던 개념을 적용한 예제이다.
import java.util.ArrayList;
public class Exercise001 {
public static void main(String[] args){
Box<Fruit> fruitBox = new Box<Fruit>();
Box<Apple> appleBox = new Box<Apple>();
Box<Toy> toyBox = new Box<Toy>();
// Box<Grape> grapeBox = new Box<Apple>(); // 에러. 타입 불일치
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); // OK. void add(Fruit item)
appleBox.add(new Apple());
appleBox.add(new Apple());
// appleBox.add(new Toy()); // 에러. Box<Apple>에는 Apple만 담을 수 있음
toyBox.add(new Toy());
// toyBox.add(new Apple()); // 에러. Box<Toy>에는 Apple을 담을 수 없음
System.out.println(fruitBox);
System.out.println(appleBox);
System.out.println(toyBox);
}
}
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item); }
T get(int i) { return list.get(i); }
int size() { return list.size(); }
public String toString() { return list.toString();}
}
class Fruit { public String toString() { return "Fruit";}}
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Toy { public String toString() { return "Toy" ;}}
다음장
지네릭스의 전반적 개념(2)
kind-coding.tistory.com