1. 다중 타입 매개변수 지정 가능
2. 기본 자료형에 대한 제한
3. 다이아몬드 기호 <>
4.매개변수화 타입을 타입 인자로 전달
5.제네릭 클래스의 타입 인자 제한
6.타입 인자 제한의 효과
7.제네릭 클래스의 타입 인자를 인터페이스로 제한
8.타입 인자를 하나의 클래스와 하나의 인터페이스로 동시 제한
제네릭은 한개 이상의 타입 매개변수를 지정할 수 있습니다.
class DBox<L, R> {
private L left; //왼쪽 수납 공간
private R right; //오른쪽 수납 공간
public void set(L o, R r) {
left = o;
right = r;
}
@Override
public String toString() {
return left + " & " +right;
}
}
DBox<String, Integer> box = new DBox<String, Integer>();
타입 매개변수의 인자로 primitive 타입을 전달할 수 없습니다.
Box<int> box = new Box<int>();
int, boolean, 같은 기본 자료형을 전달하게 되면 컴파일 오류가 발생하게 됩니다.
기본 자료형을 넣어주고 싶다면 래퍼 클래스를 타입 인자로 전달함으로써 사용할 수 있습니다. (int 대신 Integer)
Box<Integer> box = new Box<Integer>();
❗️ 제네릭의 타입 인자로 primitive 타입을 전달 불가능 하다는 것을 기억하자!
참조변수 선언을 통해 컴파일러가 <> 사이에 어떠한 매개변수가 와야하는지 유추하기 때문에 추가적으로 명시해주지 않아도 됩니다.
//Box<Integer> box = new Box<Integer>();
Box<Integer> box = new Box<>(); //Integer 생략 가능
용어정리
타입 인자 → Box< T > 에서 T
타입 매개변수 → Box< Apple > 에서 Apple
매개변수화 타입 → Box< Apple >
타입 인자 T에 기존 클래스들 이외에도 제네릭을 통해 생성된 Box< Apple > 같은 클래스도 인자로 전달 가능합니다.
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
class BoxInBox {
public static void main(String[] args) {
Box<String> sBox = new Box<>();
sBox.set("Secret Word");
// Box 안에 Box<String> => T 에 Box<String>을 전달
Box<Box<String>> wBox = new Box<>();
wBox.set(sBox);
// Box 안에 Box 안에 Box<String>
Box<Box<Box<String>>> zBox = new Box<>();
zBox.set(wBox);
System.out.println(zBox.get().get().get());
}
}
제네릭 클래스의 타입 인자에 제한을 걸어 해당 타입이거나, 해당 타입을 직, 간접적으로 상속받은 클래스만 타입 인자로 올 수 있도록 제한할 수 있습니다.
class Box<T extends Number> { // Number 하위 클래스로 제한
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
class BoundedBox {
public static void main(String[] args) {
Box<Integer> iBox = new Box<>();
iBox.set(24);
Box<Double> dBox = new Box<>();
dBox.set(5.97);
Box<String> sBox = new Box<>(); // ERROR 발생!
}
}
타입 인자를 제한하게 되면 부수적인 효과가 생기는데 그 효과가 중요합니다.
class Box<T> {
private T ob;
....
public int toIntValue(){
return ob.intValue(); // ERROR!
}
}
타입 매개변수 T에 어떤 타입이 올지 예측할 수 없기 때문에 intValue() 같은 특정클래스만 사용할 수 있는 메소드는 내부에서 사용할 수 없습니다.
class Box<T extends Number> {
private T ob;
....
public int toIntValue(){
return ob.intValue(); // OK!
}
}
타입 매개변수 T의 인자로 올 수 있는 타입은 Number 또는 Number를 상속받은 클래스로 제한되었기 때문에 클래스 내부 T에 Number의 메소드를 사용할 수 있습니다.
T로 전달 될 수 있는 타입이 Number의 메소드를 호출할 수 있는 클래스로 제한되었기 때문에 Number의 메소드 호출이 가능해집니다.
타입 매개변수를 extends를 통해 클래스로 제한을 걸 수 있지만, 동일하게 타입 인자를 인터페이스로 제한할 수 있습니다.
interface Eatable {
public String eat();
}
class Apple implements Eatable{
public String eat(){
...
}
}
class Box<T extends Eatable> {
T ob;
public void set(T o) {
ob = o;
}
public T get() {
// T가 Eatable 인터페이스를 구현한 것이 보장되기에 eat()메소드 호출 가능
System.out.println(ob.eat());
return ob;
}
Box<Apple> aBox = new Box();
타입 인자의 제한을 & 를 통해 하나의 클래스, 하나의 인터페이스를 동시에 만족하도록 제어할 수 있습니다.
class Box<T extends Number & Eatable> {
.
.
.
}
클래스는 하나만, 인터페이스는 여러개로 제한 할 수도 있습니다.
class Box<T extends Number & Eatable & InterfaceA ...> {
.
.
.
}