자바를 자바 15(Generic Programming)
Raw Type의 슈퍼 클래스는 Raw Type이다.
상속 받지 않은 Raw Type의 생성자, 인스턴스 메서드, 인스턴스 필드는 Raw Type이다.
Generic Classes: Cases
- generic class는 non-generic class를 상속받을 수 있다.
class Shape { } //Raw Type
class FruitBox<T> extends Shape { } //Generic Type
- generic class는 generic class으로 부터 상속 받을 수 있다.
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 FruitBox<T> extends Box<T> { } // 이와 같이 generic class를 상속 받을 수 있다.
- 상속을 받는 generic class 클래스와 받을 수 있는 클래스 타입을 정의할 수 있다.
class Box<T> { ... }
class FruitBox<T extends Fruit> extends Box<T> { }
class Shape { } //Raw Type
class FruitBox<T> extends Shape { } //Generic Type
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 FruitBox<T> extends Box<T> { } // 이와 같이 generic class를 상속 받을 수 있다.
class Box<T> { ... }
class FruitBox<T extends Fruit> extends Box<T> { }
위와 같이 기능은 Box에 있는 곳을 상속 받되, 내부에 들이는 객체의 자료형은 Fruit로 제한할 수 있다.
- superclass가 타입을 제한 하였다면 상속을 받은 subclass도 제한을 걸어야 한다.
class Box<T extends Fruit> { ... }
class FruitBox<T> extends Box<T> { ... } // Error
위와 같이 Box 클래스는 Fruit로 제한을 걸어 두었으나 FruitBox는 Box를 상속받으면서도 Fruit를 제한하지 않았다.
class Box<T extends Fruit> { ... }
class FruitBox<T extends Fruit> extends Box<T> { ... } // OK
Generic Classes: Wild Card
class Juicer {
static Juice makeJuice(FruitBox<Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
class Juicer {
static Juice makeJuice(FruitBox<Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
위와 같이 생긴 클래스가 있다고 가정하자. 여기에서 box.getList()는 ArrayList로 작성된 FruitBox 에서 리스트를 반환하는 함수이다.
더 자세한 클래스와 코드에 대한 설명은 이후에서 다루도록 하겠다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
fruitBox = new FruitBox<Fruit>();
appleBox = new FruitBox<Apple>();
System.out.println(Juicer.makeJuice(fruitBox)); // OK
System.out.println(Juicer.makeJuice(appleBox)); // Error: the argument is not of type FruitBox<Fruit>.
이제 중요한 것은 위와 같이 작성하였을 때 appleBox를 집어넣은 곳에서 에러가 난다. 왜냐하면 Juicer에서 파라미터로 FruitBox<Furit>
을 받기로 하였기 때문이다.
그래서 코드를 다음과 같이 바꾸면 어떻게 될까?
class Juicer {
static Juice makeJuice(FruitBox<Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
static Juice makeJuice(FruitBox<Apple> box) { // Error: another method exists
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
안타깝게도 위의 코드도 역시 에러가 난다. 위와 같은 경우 함수 overloading을 하려고 했으나 parameter은 overloading은 되지 않는다고 앞에서 배웠기에 불가능한 것이다.
그렇다면 raw 타입 함수의 이름만 바꾸어서 사용한다??? 굉장히 사용하기 불편할 것이다.
그래서 사용하는 것이 wild card라는 개념이다.
<? extends T>
: 클래스 T와 T의 모든 서브 클래스
<? super T>
: 클래스 T와 T의 모든 슈퍼 클래스
<?>
: all types == <? extends Object>
class Juicer {
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
fruitBox = new FruitBox<Fruit>();
appleBox = new FruitBox<Apple>();
System.out.println(Juicer.makeJuice(fruitBox)); // OK
System.out.println(Juicer.makeJuice(appleBox)); // OK!
DETOUR: for-each statements
for(Fruit f : box.getList())
해석
기존에 반복문은 아래와 같이 작성하였다.
String[] numbers = {"one", "two", "three"};
for(int i=0; i<numbers.length; i++) {
System.out.println(numbers[i]);
}
이것을 iterable object를 사용하여서 출력해 줄 수도 있다.
String[] numbers = {"one", "two", "three"};
for(String number: numbers) {
System.out.println(number);
}
number이라는 iterable object가 numbers 배열에 있는 값들을 하나씩 선택함
Generic Classes: Example 3
import java.util.ArrayList;
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 Juice {
String name;
Juice(String name) { this.name = name + "Juice"; }
public String toString() { return name; }
}
class Juicer {
//여기에서 Fruit 클래스의 subClass를 인자로 받아올 수 있음
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item); }
T get(int i) { return list.get(i); }
ArrayList<T> getList() { return list; }
int size() { return list.size(); }
public String toString() { return list.toString(); }
}
class FruitBox<T extends Fruit> extends Box<T> { }
public class Lecture {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
appleBox.add(new Apple());
System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));
}
}
Generic Methods
method만 Generic 하게 제작할 수 있다. 이렇게 하면 Wild Card를 사용하지 않고도 Wild Card와 유사한 기능을 만들 수 있다.
class Juicer {
//return type전에 type variable을 적어주자
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
단 차이점이 하나 존재하는데 main에서도 이 함수를 호출할때 type variable을 method name 앞에 적어주어야 한다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
fruitBox = new FruitBox<Fruit>();
appleBox = new FruitBox<Apple>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));
이게 귀찮다고 한다면 만약 컴파일러가 차이를 파악할 수 있다면 구지 안적어주어도 된다. 물론 지금과 같은 경우는 파라미터를 전달하기 때문이다.
System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));
Casting Generic Types
Casting 해줄때 Generic Type에서 선언했던 Type이 아니면 Casting 할 수가 없다.
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>)strBox;
// Error: cannot cast from Box<String> to Box<Object>
strBox = (Box<String>)objBox;
// Error: cannot cast from Box<Object> to Box<String>
이 때에도 Wild Card를 사용하는 방법이 존재한다.
Box<? extends Object> objBox = new Box<String>(); // OK
위와 같이 Object 타입의 subtype이면 다 된다 하면 어떠한 타입이라도 type casting이 가능하다.
하지만 다음과 같은 문제가 생길 수 있으니 주의하여야 한다.
FruitBox<? extends Fruit> box = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = (FruitBox<Apple>)box;
위와 같이 type casting을 한다고 할때 box가 만약 Fruit 클래스가 아니였다면 컴파일에러는 생기지 않으나 문제가(Warning) 생긴다.
Erasure of Generic Type
한가지 더 알아 두어야 하는 것이 Generic Type은 컴파일러가 컴파일할때까지만 존재하고 이후에는 없어진다는 것이다.
그렇다면 어짜피 컴파일러가 다 지워버릴 텐데 뭐하려고 Generic Type을 쓰냐고 질문 할 수 있다.
바로 type casting을 바로 해주기 때문이다.
위와 같이 일반적인 타입으로 필요한 조치를 취하여 준다. 그렇기 때문에 프로그래머의 귀찮은 부분을 해결해준다는 장점이 존재한다.
Author And Source
이 문제에 관하여(자바를 자바 15(Generic Programming)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@tonyhan18/자바를-자바-15Generic-Programming저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)