본문 바로가기
아카이브/자바의 정석

12장 지네릭스,열거형,애노테이션 20201111

by nineteen 2020. 11. 11.
반응형

제한된 지네릭 클래스

- 타입 매개변수 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<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<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<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 <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));

 

지네릭 메소드 호출 시, 클래스이름이나 참조변수 생략 불가