제네릭 클래스와 상속
class Box<T> {
protected T ob;
public void set(T o) { ob = o; }
public T get() { return ob; }
}
class SteelBox<T> extends Box<T> {
public SteelBox(T o) { // 제네릭 클래스의 생성자
ob = o;
}
}
Box<Integer> iBox = new SteelBox<>(7959);
<-> Box<Integer> iBox = new SteelBox<Integer>(7959);
Box<String> sBox = new SteelBox<>("Simple");
<-> Box<String> sBox = new SteelBox<String>("Simple");
타겟 타입
class Box<T> {
private T ob;
public void set(T o) { ob = o; }
public T get() { return ob; }
}
class EmptyBoxFactory {
public static <T> Box<T> makeBox() {
Box<T> box = new Box<T>();
return box;
}
}
public static void main(String[] args) {
Box<Integer> iBox = EmptyBoxFactory.<Integer>makeBox(); - 생략 가능!!
// Box<Integer> iBox = EmptyBoxFactory.makeBox();
iBox.set(25);
System.out.println(iBox.get());
}
Box<Integer> iBox = EmptyBoxFactory.makeBox();
참조변수의 형 Box<Integer>를 기반으로 makeBox 메소드의 T를 결정하게 된다.
따라서 이를 가리켜 타겟 타입이라 한다.
와일드카드의 설명에 앞서(제네릭 메소드 vs 일반 메소드)
Box<Integer>의 인스턴스, Box<String>의 인스턴스를 인자로 전달 가능
public static <T> void peekBox(Box<T> box) {
System.out.println(box);
}
Box<Integer>의 인스턴스, Box<String>의 인스턴스를 인자로 전달 가능할 것 같지만 불가능!!!
public static void peekBox(Box<Object> box) {
System.out.println(box);
}
"Box<Object>와 Box<String>은 상속 관게를 형성하지 않는다."
"Box<Object>와 Box<String>은 상속 관계를 형성하지 않는다."
그러나 와일드카드를 사용하면 일반 메소드도 이 두 인스턴스를 인자로 받을 수 있다.
와일드 카드
Box<Integer>의 인스턴스, Box<String>의 인스턴스를 인자로 전달 가능
public static void peekBox(Box<?> box) {
System.out.println(box);
}
public static <T> void peekBox(Box<T> box) {} - 제네릭 메소드
제네릭 메소드와 와일드 카드는 기능적으로 차이가 없다.
기능적으로는 두 메소드 완전 동일
public static <T> void peekBox(Box<T> box) {
System.out.println(box);
} // 제네릭 메소드의 정의
public static void peekBox(Box<?> box) {
System.out.println(box);
} // 와일드카드 기반 메소드 정의
그러나 와일드카드 기반 메소드 정의가 더 간결하므로 우선시 해야 한다고 권고하고 있다.
메소드의 정의가 복잡해질수록 와일드카드 기반 메소드 정의가 더 간결해 보인다.
와일드 카드의 상한과 하한의 제한 : Bounded Wildcards
기본적인 문법적 이해는 어렵지 않다.
그러나 목적과 이유에 대한 이해에서 어렵게 느껴질 수 있다.
일단 이해하자. 그리고 이해한 후에는 암기할 정도로 숙달이 되어야 한다.
상한 제한된 와일드 카드(Upper-Bounded Wildcards) - 제네릭도 있음!
public static void peekBox(Box<?> box) {
System.out.println(box);
]
public static void peekBox(Box<? extends Number> box) {
System.out.println(box);
}
box는 Box<T> 인스턴스의 참조 값을 전달받는 매개변수이다.
-> 단, 전달되는 인스턴스의 T는 Number 또는 이를 상속하는 하위 클래스이어야 함
하한 제한된 와일드 카드(Lower-Bounded Wildcards) - 제네릭은 없음!
public static void peekBox(Box<? super Integer> box) {
System.out.println(box);
}
box는 Box<T> 인스턴스의 참조 값을 전 달받는 매개변수이다.
-> 단, 전달되는 인스턴스의 T는 Integer 또는 Integer가 상속하는 클래스이어야 함
즉, 위 메소드의 인자로 전달 가능한 인스턴스는 Box<Integer>, Box<Number>, Box<Object>으로 제한됨
와일드카드 제한의 이유 설명을 위한 도입
class Box<T> {
private T ob;
public void set(T o) { ob = o; }
public T get() { return ob; }
}
class Toy {
public String toString() { return "I am a Toy"; }
}
class BoxHandler {
public static void outBox(Box<Toy> box) {
Toy t = box.get();
System.out.println(t);
}
public static void inBox(Box<Toy> box, Toy n) {
box.set(n); // 상자에 넣기
}
}
아래의 오류 상황에서 컴파일 오류가 발생하지 않는다!
public static void outBox(Box<Toy> box) {
box.get(); // 꺼내는 것! OK!
box.set(new Toy()); // 넣는 것! 이것도 OK!
}
public static void inBox(Box<Toy> box, Toy n) {
box.set(n); // 넣는 것! OK!
Toy myToy = box.get(); // 꺼내는 것! 이것도 OK!
}
상한 제한의 목적
class Box<T> {
private T ob;
public void set(T o) { ob = o; }
public T get() { return ob; }
}
. . .
class Car extends Toy { . . . } // 자동차 장난감
class Robot extends Toy { . . . } // 로봇 장난감
Box<Car> 또는 Box<Robot> 인스턴스가 인자로 전달될 수 있다.
public static void outBox(Box<? extends Toy> box) {
box.get(); // 꺼내는 것! OK!
box.set(new Toy()); // 넣는 것! ERROR!
}
Box<? extends Toy> 이면 매개변수 타입은 Toy 또는 Toy를 상속하는 클래스인데 Toy를 상속하는 클래스(Car)라면
box.set(Car o)가 되기 때문에 box.set(new Toy())를 할 수 없다.
왜냐면 상속받는 클래스의 상위클래스를 넣을 순 없기 때문!! 따라서 컴파일 오류를 발생 시키게 된다!!
다음과 같이 정리하자!
Box<? extends Toy> box 대상으로 넣는 것 불가!
상한 제한의 결과
코드의 수준이 다음에서...
class BoxHandler {
public static void outBox(Box<Toy> box) {
Toy t = box.get(); // 상자에서 꺼내기
System.out.println(t);
}
public static void inBox(Box<Toy> box, Toy n) {
box.set(n); // 상자에 넣기
}
}
다음 수준으로 높아졌다.
class BoxHandler {
public static void outBox(Box<? extends Toy> box) {
Toy t = box.get(); // 상자에서 꺼내기
System.out.println(t);
}
public static void inBox(Box<Toy> box, Toy n) {
box.set(n); // 상자에 넣기
}
}
하한 제한의 목적
class Box<T> {
private T ob;
public void set(T o) { ob = o; }
public T get() { return ob; }
}
class Plastic { . . . }
class Toy extends Plastic { . . . }
. . .
public static void inBox(Box<? super Toy> box, Toy n) {
box.set(n); // 넣는 것! OK!
Toy myToy = box.get(); // 꺼내는 것! Error!
}
Box<Toy>, Box<Plastic> 이 올 수 있음. 만약 Plastic이 매개변수 타입으로 온다면
box.get();은 할 수 없음. 따라서 컴파일 오류가 발생!!
다음과 같이 정리하자!
Box<? super Toy> box 대상으로 꺼내는 것 불가!
하한 제한의 결과
코드의 수준이 다음에서 . . .
class BoxHandler {
public static void outBox(Box<? extends Toy> box) {
Toy t = box.get(); // 상자에서 꺼내기
System.out.println(t);
}
public static void inBox(Box<Toy> box, Toy n) {
box.set(n); // 상자에 넣기
}
}
다음 수준으로 높아졌다.
class BoxHandler {
public static void outBox(Box<? extends Toy> box) {
Toy t = box.get(); // 상자에서 꺼내기
System.out.println(t);
}
public static void inBox(Box<? super Toy> box, Toy n) {
box.set(n); // 상자에 넣기
}
}
상한 제한과 하한 제한의 좋은 예 하나!
class BoxContentsMover {
// from에 저장된 내용물을 to로 이동 - 제한을 걸어두면 from 에서는 get만 할 수 있고, to 에서는 set만 할 수 있음!!!
public static void moveBox(Box<? super Toy> to, Box<? extends Toy> from) {
to.set(from.get());
}
}
제한된 와일드카드 선언을 갖는 제네릭 메소드 : 도입
class BoxHandler {
public static void outBox(Box<? extends Toy> box) {
Toy t = box.get(); // 상자에서 꺼내기
System.out.println(t);
}
public static void inBox(Box<? super Toy> box, Toy n) {
box.set(n); // 상자에 넣기
}
}
위 클래스의 두 메소드는 사실상 Box<Toy> 인스턴스를 대상으로 정의된 메소드이다!
따라서 Toy와 전혀 관계 없는 Robot 클래스가 존재하는 상황에서 Box<Robot>을 대상으로는 동작하지 않는다.
그렇다면 이 상황에서 메소드 오버로딩이 가능할까?
다음 형태로 메소드 오버로딩 불가능하다!
class BoxHandler {
// 다음 두 메소드는 오버로딩 인정 안됨.
public static void outBox(Box<? extends Toy> box) { . . . }
public static void outBox(Box<? extends Robot> box) { . . . }
// 다음 두 메소드는 두 번째 매개변수로 인해 오버로딩 인정 됨.
public static void inBox(Box<? super Toy> box, Toy n) { . . . }
public static void inBox(Box<? super Robot> box, Robot n) { . . . }
}
왜 이러한 형태의 오버로딩을 허용하지 않을까? 이유는 Type Erasure!
컴파일 과정에서 < . . . > 내용이 모두 지워진다. 따라서 컴파일러가 이러한 형태의 메소드 오버로딩을 허용하지 않음.
기존 코드를 유지하기 위해 허용하지 않은 것임!!
그래서 와일드 카드 선언을 갖는 메소드를 제네릭으로
public static void outBox(Box<? extends Toy> box) { . . . }
public static void outBox(Box<? extends Robot> box) { . . . }
-> 이것이 대안!
public static <T> void outBox(Box<? extends T> box) { . . . }
와일드 카드 선언을 갖는 제네릭 메소드가 등장했을 때, 이 설명의 흐름을 기억해야 당황하지 않는다.
여기까지 잘 이해 했다면 한 고비 넘긴 것임!!!
넘겼다!!!!!!!!!!!!!!
제네릭 인터페이스의 정의와 구현
인터페이스 역시 클래스와 마찬가지로 제네릭으로 정의할 수 있다.
interface Getable<T> {
public T get();
}
// 인터페이스 Getable<T>를 구현하는 Box<T> 클래스
class Box<T> implements Getable<T> {
private T ob;
public void set(T o) { ob = o; }
@Override
public T get() { return ob; }
}
class Toy {
@Override
public String toString() {
return "I am a Toy";
}
}
public static void main(String[] args) {
Box<Toy> box = new Box<>();
box.set(new Toy());
// Box<T>가 Getable<T>를 구현하므로 참조 가능
Getable<Toy> gt = box;
System.out.println(gt.get());
}
'# 02 > Java' 카테고리의 다른 글
[윤성우 열혈자바] 23-2. List<E> 인터페이스를 구현하는 컬렉션 클래스들 (0) | 2019.10.24 |
---|---|
[윤성우 열혈자바] 23-1. 컬렉션 프레임워크의 이해 (0) | 2019.10.24 |
[윤성우 열혈자바] 21-2. 제네릭의 기본 문법 (0) | 2019.10.23 |
[윤성우 열혈자바] 21-1. 제네릭의 이해 (0) | 2019.10.23 |
[윤성우 열혈자바] 20-4. Arrays 클래스 (0) | 2019.10.23 |