목표
자바의 제네릭에 대해 학습.
학습할 내용
제네릭 사용법
제네릭 주요 개념 (바운디드 타입, 와일드 카드)
제네릭 메소드 만들기
Erasure
■ 제네릭.
참조변수의 자료형과 상관 없이 데이터를 입출력 시키는 클래스가 필요할 때.
Object형의 참조변수로 데이터를 입출력 시키는 클래스로 가능하다.
하지만 문제는,
1. 필수적으로 형변환 과정이 수반된다.
2. 컴파일러의 오류 발견 가능성을 낮추는 결과로 이어진다.
특히 2.의 문제가 가장 심각한데, 실행 결과에서도 오류를 발견하지 못하는
치명적인 문제로 이어질 수 있다.
■ 제네릭 클래스 구조
타입 매개변수(Type Parameter) - Box<T> 에서 T
타입 인자(Type Argument) - Box<Apple> 에서 Apple
매개변수화 타입(Parameterized Type) - Box<Apple>(별도의 자료형)
[타입 매개변수의 일반적인 관례]
• 한 문자로 이름을 짓는다.
• 대문자로 이름을 짓는다
보편적인 선택
E - Element
K - Key
N - Number
T - Type
V - Value
[기본 자료형에 대한 제한]
타입 인자로 기본 자료형이 올 수 없다.
기본 자료형 사용이 필요할 때는 래퍼클래스의 오토박싱/언박싱을 이용하여 진행한다.
[다이아몬드 기호]
Box aBox = new Box();
다음과 같이 쓸 수 있다.
Box aBox = new Box<>();
참조변수 선언을 통해서 컴파일러가 우측 <> 사이에 Apple이 와야 함을 유추한다.
■ '매개변수화 타입'을 '타입 인자'로
class Box<T> {
private T ob;
public void set (T o) {
ob = o;
}
public T get() {
return ob;
}
}
Box<String> sBox = new Box<>();
sBox.set("Hi");
Box<Box<String>> wBox = new Box<>();
wBox.set(sBox);
Box<Box<Box<String>>> zBox = new Box<>();
zBox.set(wBox);
System.out.println(zBox.get().get().get());
타입 인자는 여러 개를 전달할 수 있다.
class Box<T, K> {
private T obT;
private K obK;
}
■ 제네릭 클래스의 타입 인자 제한하기(바운디드 타입)
class Box<T extends Number> {...}
-> 인스턴스 생성 시 타입 인자로 Number 또는 이를 상속하는 클래스만 올 수 있음
class Box<T> {
private Tob;
...
public int toInvValue() {
return ob.intValue(); // ERROR!
}
}
class Box<T extends Number> {
private T ob;
...
public int toIntValue() {
return ob.intValue(); // OK!
}
}
개발자가 Number를 상속하는 클래스를 타입인자로 받는 제네릭 클래스를 설계하고자 할 때, 첫 번째 Box 클래스와 같이 코드를 작성할 수 없다. 왜냐하면 Box 클래스에 타입인자로 Number를 상속하는 클래스가 들어올지 안들어올지 모르기 때문에 컴파일러가 허용하지 않는다.
그래서 두 번째 Box 클래스처럼 타입인자를 제한하여 해결할 수 있다.
• 제네릭 클래스의 타입 인자를 인터페이스로 제한할 수도 있다.
• 하나의 클래스와 하나의 인터페이스에 대해 동시 제한할 수도 있다.
class Box<T extends Number & AAA> {…}
[타입인자 제한의 목적, 효과]
물론 순수하게 타입인자를 제한하고자 하는 목적도 있지만,
위에 설명한 효과가 타입 인자를 제한하는 실질적인 이유인 경우가 많다.
정리를 하자면, 제네릭 클래스를 설계할 때, 제네릭 클래스 안에서 사용할 메서드의 범위는
사용자가 정의하거나, object클래스 안에 있는 메서드만 사용할 수 있다.
하지만 타입인자를 제한함으로써 그 범위를 확장할 수 있다.
■ 와일드 카드
[자바의 와일드 카드]
와일드카드란, 이름 또는 문자열에 제한을 가하지 않음을 명시하는 용도로 사용되는 기호를 말한다.
자바에서는 클래스의 이름을 명시하는데 있어서 '?' 기호를 와일드카드로 정의하고 있다.
FruitBox<? extends Fruit> box1 = new FruitBox<Fruit>();
위의 <? extends Fruit>가 의미하는 바는 "Fruit 클래스와 Fruit을 상속하는 모든 클래스"이다.
따라서 참조변수 box1은 아래의 형태로 생성되는 인스턴스를 참조 가능하다.
new FruitBox<'Fruit 클래스, 또는 Fruit 클래스를 상속하는 클래스'>
[하위 클래스를 제한하는 용도의 와일드 카드]
FruitBox<? super Apple> box1 = new FruitBox<Fruit>();
다이아몬드 기호 안에서 extends가 "~을 상속하는 클래스"라고 한다면,
super 키워드는"~이 상속하는 클래스"라고 해석한다.
즉 위에 선언된 참조변수 boundedBox는 FruitBox의 인스턴스를 참조하되,
T가 Apple 클래스 는 Apple 클래스가 간접적으로 상속하는 클래스인 경우에만 참조할 수 있다.
클래스가 전부가 아닌 메서드 하나에 대해 제네릭으로 정의
class BoxFactory {
public <T> Box<T> makeBox(T o) {
Box<T> box = new Box<T>();
box.set(o);
return box;
}
}
제네릭 클래스는 인스턴스 생성 시 타입 인자를 결정하지만
제네릭 메서드는 메서드 호출 시점에 결정한다.
Box<String> sBox = BoxFactory.<String>makeBox("Sweet");
Box<Double> dBox = BoxFactory.<Double>makeBox(1.23);
다음과 같이 타입 인자 생략이 가능하다.
Box<String> sBox = BoxFactory.makeBox("Sweet");
Box<Double> dBox = BoxFactory.makeBox(1.23);
double의 경우 wrapper클래스 Double형으로 오토박싱이 진행된다
제네릭 클래스는 참조변수 선언을 통해서 컴파일러가 우측 <> 사이에 Apple이 와야 함을 유추한다.
제네릭 메서드는 메서드 호출 시 전달되는 인자의 자료형을 근거로 판단한다.
참고 서적 : 난 정말 JAVA를 공부한 적이 없어요 (ORANGE MEDIA 윤성우 저)