와일드 카드
💡 제네릭 메소드 vs 일반 메소드
// 제네릭 메소드 정의
public static <T> void peekBox(Box<T> box) {
System.out.println(box);
}
// 일반 메소드 정의
public static void peekBox(Box<Object> box) {
System.out.println(box);
}
- 제네릭 메소드의 Box<T>의 T에는 Box<String>의 인스턴스, Box<Integer>의 인스턴스를 모두 인자로 전달 가능하다.
- 일반 메소드의 Box<Object>는 매개변수화 타입으로, Box<String>의 인스턴스, Box<Integer>의 인스턴스를 인자로 전달 가능할 것 같지만 불가능하다.
- Box<Object>와 Box<String>, Box<Integer>는 상속 관계를 형성하지 않는다.
- Object와 String과 Integer는 상속 관계이지만, Box<타입이름>은 매개변수화 타입으로 또 하나의 타입이기 때문에 상속관계가 이어지지 않는다.
💡 제네릭 메소드 vs 와일드 카드
// 제네릭 메소드 정의
public static <T> void peekBox(Box<T> box) {
System.out.println(box);
}
// 와일드 기반 메소드 정의
public static void peekBox(Box<?> box) {
System.out.println(box);
}
- 와일드 카드는 키워드 ?를 사용해 메소드의 매개변수를 선언한다.
- 타입 인자로 무엇이든 올 수 있다는 기능적 측면으로는 제네릭 메소드와 동일하다.
- 제네릭 메소드보다 와일드카드 기반 메소드 정의가 더 간결하다는 장점이 있다.
- 간결함 때문에 제네릭 메소드보다 와일드 기반 메소드 정의를 선호한다.
class Box<T> {
protected T ob;
public void set(T o) { ob = o;}
public T get() { return ob;}
@Override
public String toString() {
return ob.toString();
}
}
class Unboxer {
// 상자 안의 내용물을 확인(출력)하는 기능의 메소드 정의
/* 제네릭 메소드 */
// public static <T> void peekBox(Box<T> box) {
// System.out.println(box);
// }
/* 와일드 카드 기반의 메소드 */
public static void peekBox(Box<?> box) {
System.out.println(box);
}
}
class Wildcard {
public static void main(String[] args) {
Box<String> box = new Box<>();
box.set("Simple String");
Unboxer.peekBox(box);
}
}
// 실행 결과
Simple String
와일드카드의 제한
상한 제한(upper bound) | <? extends T> | T와 T를 상속하는 자손들의 인스턴스만 전달되도록 제한 |
하한 제한(lower bound) | <? super T> | T와 T가 상속하는 부모들의 인스턴스만 전달되도록 제한 |
- 상한 제한
- Box<? extends Number> box
- Box<T>의 T로 전달되는 대상을 Number와 Number를 상속하는 자손 클래스로 제한
- Box<Number>, Box<Integer>, Box<Long>, Box<Short> 등..
- 하한 제한
- Box<? super Integer> box
- Box<T>의 T로 전달되는 대상을 Integer와 Integer가 상속하는 부모 클래스로 제한
- Box<Integer>, Box<Number>, Box<Object>
💡 와일드 카드 제한의 목적
- extends로 상한 제한을 두면 꺼내는 것(get)만 가능, 넣는 것(set) 불가능
- super로 하한 제한을 두면 넣는 것(set)만 가능, 꺼내는 것(get) 불가능
- 필요한만큼만 기능을 허용하여, 코드의 오류가 컴파일 과정에서 최대한 발견되도록 하기 위함이다.
class Box<T> {
private T ob;
public void set(T o) { ob = o;}
public T get() { return ob;}
}
class Toy {
@Override
public String toString() {
return "I am a Toy";
}
}
class BoxHandler {
// 상자에서 물건을 꺼내는 메소드
public static void outBox(Box<? extends Toy> box) {
Toy t = box.get(); // 상자에서 꺼내기
// box.set(new Toy()); // 넣으려 하면 컴파일 오류 발생
System.out.println(t);
}
// 상자에 물건을 넣는 메소드
public static void inBox(Box<? super Toy> box, Toy n) {
box.set(n); // 상자에 넣기
// Toy toy = box.get(); // 꺼내려 하면 컴파일 오류 발생
}
}
public class Wildcard_BoundedUsage {
public static void main(String[] args) {
Box<Toy> box = new Box<>();
BoxHandler.inBox(box, new Toy()); // 상자에 넣기
BoxHandler.outBox(box); // 상자에서 꺼내기
}
}
// 실행 결과
I am a Toy
❓ 상한제한이 get만 되는 이유 ?
class Car extends Toy {...} // 자동차 장난감
class Robot extends Toy {...} // 로봇 장난감
public static void outBox(Box<? extends Toy> box) {
Toy t = box.get();
box.set(new Toy()); // 컴파일 에러 발생
}
- 위와 같이 Toy를 상속하는 하위 클래스가 있다면 Box<? extends Toy>로 Box<Toy>, Box<Car>, Box<Robot>가 전달될 수 있다.
- 이때 만약 매개변수 타입으로 Box<Car>가 온다면
- Box<Car>타입 인스턴스에 Toy 인스턴스를 저장하는 것은 불가능하다. (자식클래스에 부모 클래스를 담는 것은 불가능한 것과 같은 맥락)
- Box<Car>타입 인스턴스에 Toy 인스턴스를 저장하는 것은 불가능하다. (자식클래스에 부모 클래스를 담는 것은 불가능한 것과 같은 맥락)
❓ 하한제한이 set만 되는 이유 ?
class Plastic {...}
class Toy extends Plastic {...}
public static void inBox(Box<? super Toy> box, Toy n) {
box.set(n);
Toy toy = box.get(); // 컴파일 에러발생
}
- 위와 같이 Toy가 상속하는 상위 클래스가 있다면 Box<? super Toy>로 Box<Toy>, Box<Plastic>이 전달될 수 있다.
- 이때 만약 매개변수 타입으로 Box<Plastic>이 온다면
- Toy 타입 참조변수 toy가 Box<Plastic>타입 인스턴스를 참조하는 것은 불가능 하다. (자식클래스에 부모 클래스를 담는 것은 불가능한 것과 같은 맥락)
제한된 와일드 카드 선언을 갖는 제네릭 메소드
- 자바는 제네릭 등장 이전에 정의된 클래스들과의 상호 호환성 유지를 위해 컴파일 시 제네릭과 와일드카드 관련 정보를 지우는 과정(Type Erasure)을 거친다.
- 컴파일 과정에서 Type Erasure이 이뤄지기 때문에 다음과 같이 메소드 오버로딩이 인정되지 않는 경우가 생긴다.
class BoxHandler {
// 다음 두 메소드는 오버로딩 인정 안됨.
public static void outBox(Box<? extends Toy> box) {...} // 컴파일러의 Type Erasure에 의해 (Box box)로 수정됨
public static void outBox(Box<? extends Robot> box) {...} // 컴파일러의 Type Erasure에 의해 (Box box)로 수정됨
// 다음 두 메소드는 두번째 매개변수로 인해 오버로딩 인정.
public static void inBox(Box<? super Toy> box, Toy n) {...} // 컴파일러의 Type Erasure에 의해 (Box box, Toy n)으로 수정됨
public static void inBox(Box<? super Robot> box, Robot n) {...} // 컴파일러의 Type Erasure에 의해 (Box box, Robot n)으로 수정됨
}
이는 다음과 같이 제한된 와일드카드 선언을 갖는 제네릭 메소드 정의를 통해 해결할 수 있다.
- public static <T> void outBox(Box<? extends T> box) {...}
class Box<T> {
private T ob;
public void set(T o) { ob = o;}
public T get() { return ob;}
}
class Toy {
@Override
public String toString() {
return "I am a Toy";
}
}
class Robot {
@Override
public String toString() {
return "I am a Robot";
}
}
class BoxHandler {
// 상자에서 물건을 꺼내는 메소드
public static <T> void outBox(Box<? extends T> box) {
T t = box.get(); // 상자에서 꺼내기
System.out.println(t);
}
// 상자에 물건을 넣는 메소드
public static <T> void inBox(Box<? super T> box, T n) {
box.set(n); // 상자에 넣기
}
}
public class Wildcard_Bounded_GenericMethod {
public static void main(String[] args) {
Box<Toy> tBox = new Box<>();
BoxHandler.inBox(tBox, new Toy());
BoxHandler.outBox(tBox);
Box<Robot> rBox = new Box<>();
BoxHandler.inBox(rBox, new Robot());
BoxHandler.outBox(rBox);
}
}
// 실행 결과
I am a Toy
I am a Robot
'☕ Java > 이론' 카테고리의 다른 글
[Java] Set 컬렉션 클래스 (0) | 2022.01.25 |
---|---|
[Java] List 컬렉션 클래스 (0) | 2022.01.25 |
[Java] Map 컬렉션 클래스 (0) | 2022.01.25 |
[Java] 컬렉션 프레임워크 (0) | 2022.01.18 |
[Java] 제네릭2 (상속, 제네릭 인터페이스) (0) | 2022.01.14 |
[Java] 제네릭1 (제네릭 클래스, 제네릭 메소드) (0) | 2022.01.13 |
[Java] Arrays 클래스 (0) | 2022.01.12 |
[Java] 기본 클래스 (Math, Random, StringTokenizer) (0) | 2022.01.12 |