제한된 지네릭 클래스
- 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법?
-> 지네릭 타입에 'extends'를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한가능
ex) class FruitBox<T extends Fruit> {} ( Fruit의 자손만 타입으로 지정가능 )
한 종류의 타입만 담을 수 있지만, Fruit클래스의 자손들만 담을 수 있다는 제한이 추가됨
- 클래스가 아닌 인터페이스를 구현해야한다는 제약이 필요하다면, 이때도 'extends'사용 ( 'implements' X )
ex)
interface Eatable {}
class FruitBox<T extends Eatable> {}
- 클래스의 상속도 받고, 인터페이스도 구현해야한다면 '&'기호로 연결
ex)
class FruitBox<T extends Fruit & Eatable> {}
-> Fruit자손이면서 Eatable을 구현한 클래스만 타입 매개변수 T에 대입가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import java.util.ArrayList;
interface Eatable {}
class Fruit implements Eatable { public String toString() { return "Fruit"; }}
class Apple extends Fruit { public String toString() { return "Apple"; }}
class Grape extends Fruit { public String toString() { return "Grape"; }}
class Toy { public String toString() { return "Toy"; }}
// 제네릭 클래스
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item); }
T get(int i) { return list.get(i); }
public String toString() { return list.toString(); }
}
// Box<T>클래스의 상속을 받고, 타입변수로는 Fruit클래스의 자손과 Eatable인터페이스 구현된 클래스만 대입가능
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
public class FruitBoxEx2 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // Fruit의 자손이므로 ok
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
// FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // 에러, 타입불일치
// FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러, 타입불일치
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); // 에러, 타입불일치 grape는 apple의 자손이 아니라 add불가
grapeBox.add(new Grape());
System.out.println("fruitBox - " + fruitBox);
System.out.println("appleBox - " + appleBox);
System.out.println("grapeBox - " + grapeBox);
}
}
|
cs |
출력
fruitBox - [Fruit, Apple, Grape]
appleBox - [Apple]
grapeBox - [Grape]
와일드카드
- 기호 '?'로 표현
- 어떠한 타입도 될 수 있음
- 'extends'와 'super'로 상한과 하한을 제한 가능
<? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> 제한 없음. 모든 타입이 가능 <? extends Object>와 동일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
package wildcard;
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 {
// 와일드카드
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp ="";
for(Fruit f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
}
// 제네릭 클래스의 상속을 받고, 타입변수를 Fruit형의 자손만 대입가능하도록 제한
class FruitBox<T extends Fruit> extends Box<T> {}
// 제네릭 클래스
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(); }
}
public class FruitBoxEx3 {
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));
}
}
|
cs |
출력
apple grape Juice
apple apple Juice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
class Fruit {
String name;
int weight;
Fruit(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String toString() { return name+"("+weight+")"; }
}
class Apple extends Fruit {
Apple(String name, int weight) {
super(name, weight);
}
}
class Grape extends Fruit {
Grape(String name, int weight) {
super(name, weight);
}
}
// 와일드카드 적용, Apple의 조상만 대입가능, Apple의 조상만 비교가능
class AppleComp implements Comparator<Apple> {
@Override
public int compare(Apple t1, Apple t2) {
return t2.weight - t1.weight;
}
}
// 와일드카드 적용, Grape의 조상만 대입가능, Grape의 조상만 비교가능
class GrapeComp implements Comparator<Grape> {
@Override
public int compare(Grape t1, Grape t2) {
return t2.weight - t1.weight;
}
}
// 와일드카드 적용, Fruit의 조상만 대입가능, Fruit의 조상만 비교가능 ( Apple, Grape이 Fruit의 상속을 받으므로 ok )
class FruitComp implements Comparator<Fruit> {
@Override
public int compare(Fruit t1, Fruit t2) {
return t1.weight - t2.weight;
}
}
// 제네릭 클래스의 상속을 받고, 타입변수를 Fruit형의 자손만 대입가능하도록 제한
class FruitBox<T extends Fruit> extends Box<T> {}
// 제네릭 클래스
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(); }
}
public class FruitBoxEx4 {
public static void main(String[] args) {
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
appleBox.add(new Apple("GreenApple", 300));
appleBox.add(new Apple("GreenApple", 100));
appleBox.add(new Apple("GreenApple", 200));
grapeBox.add(new Grape("GreenGrape", 400));
grapeBox.add(new Grape("GreenGrape", 300));
grapeBox.add(new Grape("GreenGrape", 200));
// 첫번째 매개변수는 정렬대상, 두번째 매개변수는 정렬할 방법이 정의된 Comparator
Collections.sort(appleBox.getList(), new AppleComp());
Collections.sort(grapeBox.getList(), new GrapeComp());
System.out.println(appleBox);
System.out.println(grapeBox);
System.out.println();
Collections.sort(appleBox.getList(), new FruitComp());
Collections.sort(grapeBox.getList(), new FruitComp());
System.out.println(appleBox);
System.out.println(grapeBox);
}
}
|
cs |
출력
[GreenApple(300), GreenApple(200), GreenApple(100)]
[GreenGrape(400), GreenGrape(300), GreenGrape(200)]
[GreenApple(100), GreenApple(200), GreenApple(300)]
[GreenGrape(200), GreenGrape(300), GreenGrape(400)]
28, 35, 42행
Collections.sort()의 선언부를 살펴보면
static <T> void sort(List<T> list, Comparator<? super T> c)로 선언되어있다.
타입 매개변수 T에 Apple이나 Grape이 대입되면 Apple/Grape를 담은 list를 정렬하기 위해선 Apple/Grape이 대입된 Comparator가 필요하다 ( T의 조상만 대입이 가능 )
Apple과 Grape이 Fruit의 상속을 받기때문에 Fruit로 타입 매개변수가 대입되어도, Apple과 Grape참조가능
지네릭 메소드
- 메소드 선언부에 지네릭 타입이 선언된 메소드
- 지네릭 타입의 선언 위치는 반환타입 바로 앞
- 위 예제의 Collections.sort()가 지네릭 메소드
- 클래스에 선언된 타입 매개변수와 메소드에 선언된 타입 매개변수는 서로 다름
- static멤버에 타입 매개변수 사용불가, 메소드에 지네릭 타입을 선언하고 사용하는 것은 가능
1
2
3
4
5
6
7
8
9
|
class Juicer {
// 와일드카드
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp ="";
for(Fruit f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
}
|
cs |
↓ 지네릭 메소드로 변환
1
2
3
4
5
6
7
8
9
|
class Juicer {
// 지네릭 메소드
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
String tmp ="";
for(Fruit f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
}
|
cs |
메소드 호출 방법
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));
지네릭 메소드 호출 시, 클래스이름이나 참조변수 생략 불가
'아카이브 > 자바의 정석' 카테고리의 다른 글
12장 지네릭스,열거형,애노테이션 20201113 (0) | 2020.11.13 |
---|---|
12장 지네릭스,열거형,애노테이션 20201112 (0) | 2020.11.12 |
12장 지네릭스,열거형,애노테이션 20201110 (0) | 2020.11.10 |
11장 컬렉션프레임워크 20201109 (0) | 2020.11.09 |
11장 컬렉션프레임워크 20201106 (0) | 2020.11.06 |