Search
Duplicate

자바를 자바 15(Generic Programming)

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
Java
Scrap
태그
9 more properties
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
Plain Text
복사
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를 상속 받을 수 있다.
Plain Text
복사
상속을 받는 generic class 클래스와 받을 수 있는 클래스 타입을 정의할 수 있다.
class Box<T> { ... } class FruitBox<T extends Fruit> extends Box<T> { }
Plain Text
복사
위와 같이 기능은 Box에 있는 곳을 상속 받되, 내부에 들이는 객체의 자료형은 Fruit로 제한할 수 있다.
superclass가 타입을 제한 하였다면 상속을 받은 subclass도 제한을 걸어야 한다.
class Box<T extends Fruit> { ... } class FruitBox<T> extends Box<T> { ... } // Error
Plain Text
복사
위와 같이 Box 클래스는 Fruit로 제한을 걸어 두었으나 FruitBox는 Box를 상속받으면서도 Fruit를 제한하지 않았다.
class Box<T extends Fruit> { ... } class FruitBox<T extends Fruit> extends Box<T> { ... } // OK
Plain Text
복사

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); } }
Plain Text
복사
위와 같이 생긴 클래스가 있다고 가정하자. 여기에서 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>.
Plain Text
복사
이제 중요한 것은 위와 같이 작성하였을 때 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); } }
Plain Text
복사
안타깝게도 위의 코드도 역시 에러가 난다. 위와 같은 경우 함수 overloading을 하려고 했으나 parameter은 overloading은 되지 않는다고 앞에서 배웠기에 불가능한 것이다.
그렇다면 raw 타입 함수의 이름만 바꾸어서 사용한다??? 굉장히 사용하기 불편할 것이다.
그래서 사용하는 것이 wild card라는 개념이다.
<? extends T>
Plain Text
복사
: 클래스 T와 T의 모든 서브 클래스
<? super T>
Plain Text
복사
: 클래스 T와 T의 모든 슈퍼 클래스
<?>
Plain Text
복사
: all types ==
<? extends Object>
Plain Text
복사
class Juicer { static Juice makeJuice(FruitBox<? extends Fruit> box) { String tmp = ""; for(Fruit f : box.getList()) tmp += f + " "; return new Juice(tmp); } }
Plain Text
복사
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!
Plain Text
복사

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]); }
Plain Text
복사
이것을 iterable object를 사용하여서 출력해 줄 수도 있다.
String[] numbers = {"one", "two", "three"}; for(String number: numbers) { System.out.println(number); }
Plain Text
복사
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)); } }
Plain Text
복사

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); } }
Plain Text
복사
단 차이점이 하나 존재하는데 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));
Plain Text
복사
이게 귀찮다고 한다면 만약 컴파일러가 차이를 파악할 수 있다면 구지 안적어주어도 된다. 물론 지금과 같은 경우는 파라미터를 전달하기 때문이다.
System.out.println(Juicer.makeJuice(fruitBox)); System.out.println(Juicer.makeJuice(appleBox));
Plain Text
복사

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>
Plain Text
복사
이 때에도 Wild Card를 사용하는 방법이 존재한다.
Box<? extends Object> objBox = new Box<String>(); // OK
Plain Text
복사
위와 같이 Object 타입의 subtype이면 다 된다 하면 어떠한 타입이라도 type casting이 가능하다.
하지만 다음과 같은 문제가 생길 수 있으니 주의하여야 한다.
FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); FruitBox<Apple> appleBox = (FruitBox<Apple>)box;
Plain Text
복사
위와 같이 type casting을 한다고 할때 box가 만약 Fruit 클래스가 아니였다면 컴파일에러는 생기지 않으나 문제가(Warning) 생긴다.

Erasure of Generic Type

한가지 더 알아 두어야 하는 것이 Generic Type은 컴파일러가 컴파일할때까지만 존재하고 이후에는 없어진다는 것이다.
그렇다면 어짜피 컴파일러가 다 지워버릴 텐데 뭐하려고 Generic Type을 쓰냐고 질문 할 수 있다.
바로 type casting을 바로 해주기 때문이다.
위와 같이 일반적인 타입으로 필요한 조치를 취하여 준다. 그렇기 때문에 프로그래머의 귀찮은 부분을 해결해준다는 장점이 존재한다.