본문 바로가기

Java의 정석 : 3rd Edition

[Java의 정석 - 연습문제] Chapter12. 지네릭스, 열거형, 애너테이션

Java의 정석 : 3rd Edition, 2016을 개인 학습용으로 정리한 내용입니다.
"ppt 파일 자료, 연습문제"를 학습 용도로 배포할 수 있음을 고지하셨습니다.
저자님께 감사드립니다.

 

 

 

 

[12-1] 클래스 Box가 다음과 같이 정의되어 있을 때, 다음 중 컴파일 에러가 발생하는 문장은?

class Box<T> {  // 지네릭 타입 T를 선언
    T item;

    void setItem(T item) { this.item = item; }
    T getItem() { return item; }
}

a. Box<Object> b = new Box<String>();

b. Box<Object> b = (Object)new Box<String>();

c. new Box<String>().setItem(new Object());

d. new Box<String>().setItem("ABC");

 

답 : a, b, c

a : 대입된 타입은 일치해야 한다. Box<Object> != Box<String>(불일치)

     Box<Object> b = new Box<Object>();

     Box<String> b = new Box<String>();

b : Box<Object>타입의 참조 변수에 Object타입을 저장할 수 없다(타입 불일치)

     Box<Object> b = new Box<Object>();

c : 대입된 타입이 <String>이므로, setItem(T item)의 인자로 String타입만 올 수 있다.

     new Box<Object>().setItem(new Object());

     new Box<String>().setItem("ABC");


d : 대입된 타입 <String>과 일치하는 타입을 setItem(T item)의 인자로 지정했다.

 

 

 

 

[12-2] 지네릭 메서드 makeJuice()가 아래와 같이 정의되어 있을 때, 이 메서드를 올바르게 호출한 문장을 모두 고르시오.

(Apple Grape Fruit의 자손)

class Juicer {
    static <T extends Fruit> String makeJuice(FruitBox<T> box) {
        String tmp = "";
        for(Fruit f : box.getList()) tmp += f + " ";
        return tmp;
    }
}

a. Juicer.<Apple>makeJuice(new FruitBox<Fruit>());

b. Juicer.<Fruit>makeJuice(new FruitBox<Grape>());

c. Juicer.<Fruit>makeJuice(new FruitBox<Fruit>());

d. Juicer.makeJuice(new FruitBox<Apple>());

e. Juicer.makeJuice(new FruitBox<Object>());

 

답 : c, d

c : 지네릭 메서드에 대입된 타입과 매개 변수의 타입 일치. <Fruit> == FruitBox<Fruit>

d : 지네릭 메서드에 대입된 타입과 매개 변수의 타입 일치. <Apple> == FruitBox<Apple>

     지네릭 메서드의 타입 호출이 생략된 형태. Juicer.<Apple>makeJuice(new FruitBox<Apple>());과 같다.


a : 지네릭 메서드에 대입된 타입이 <Apple>이므로, 메서드의 매개 변수 타입은 FruitBox<Apple>이어야 한다.

     Juicer.<Apple>makeJuice(new FruitBox<Apple>());

b : 대입된 타입은 상속 관계와 상관없이 항상 일치해야 한다.

     Juicer.<Fruit>makeJuice(new FruitBox<Fruit>());

     Juicer.<Grape>makeJuice(new FruitBox<Grape>());

e : 대입된 타입이 일치하지만, 하한 제한<T extends Fruit>이 걸려있으므로 타입 T는 Fruit 또는 Fruit의 자손이어야 한다.

     ObjectFruit의 조상이므로 에러가 발생한다.

     Juicer.makeJuice(new FruitBox<Fruit>());

     Juicer.makeJuice(new FruitBox<Apple>());

     Juicer.makeJuice(new FruitBox<Grape>());

 

 

 

 

[12-3] 다음 중 올바르지 않은 문장을 모두 고르시오.

class Box<T extends Fruit> {  // 지네릭 타입 T를 선언
    T item;

    void setItem(T item) { this.item = item; }
    T getItem() { return item; }
}

a. Box<?> b = new Box();

b. Box<?> b = new Box<>();

c. Box<?> b = new Box<Object>();

d. Box<Object> b = new Box<Fruit>();

e. Box b = new Box<Fruit>();

f. Box<? extends Fruit> b = new Box<Apple>();

g. Box<? extends Object> b = new Box<? extends Fruit>();

 

답 : c, d, g

c : <?><? extends Object>이지만, Box에 정의된 <T extends Fruit>으로 인해 <? extends Fruit>가 생략된 형태이다.

     Fruit 또는 Fruit의 자손만 대입된 타입으로 올 수 있는데, ObjectFruit의 자손이 아니기 때문에 에러가 발생한다.

Box<?> b = new Box<>();        // OK
Box<?> b = new Box<Object>();  // 에러
Box<?> b = new Box<Fruit>();   // OK

 

d : 대입된 타입은 항상 일치해야 한다. <Object> != <Fruit> 타입 불일치

g : new연산자를 사용할 때는 대입될 타입이 뭔지 정확히 알아야하므로 와일드 카드를 사용할 수 없다.

     new연산자는 컴파일 시점에 메모리(heap)를 확보하는데, 얼마만큼의 공간이 필요한지를 알려면 타입을 알아야한다.


a : Box<?>Box<? extends Object>를 줄여쓴 형태이다.

     객체를 생성할 때 지네릭 타입을 지정하지 않았지만, 생성에는 문제가 되지 않는다.

     하지만, 타입을 알 수 없는 와일드 카드가 지정된 인스턴스를 생성하는 건 바람직하지 않다.

     컴파일러가 어떤 타입이 들어오는지 알 수 없기 때문에 임의의 타입 변수 capture# of ?를 할당한다(Wildcard Capture)

     <Object>는 어떤 객체든 저장 가능하지만, <?>는 객체를 capture 형태로 저장할 수 없으므로 null만 가능하다.

     +) 지네릭 클래스와 넌지네릭 클래스를 섞어쓰는 건 권장되지 않는다. new Box(); → new Box<>();를 권장

public class Exercise12_3 {
    public static void main(String[] args) {
        Box<?> b = new Boxs();
        b.setItem(null);            // <?>에는 null만 할당 가능
        // b.setItem(new Fruit());  // 컴파일 에러
    }
}

class Box<T extends Fruit> {  // 지네릭 타입 T를 선언
    T item;
    
    void setItem(T item) { this.item = item; }
    T getItem() { return item; }
}

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"; }}

 

b : new Box<>();는 a처럼 타입을 생략한 형태이다. 일반적으로 참조 변수의 타입과 같은 타입으로 간주된다.

e : 지네릭 클래스와 넌지네릭 클래스를 섞어쓰는 건 문제가 되진 않지만 바람직하지 않음. Box b Box<?> b를 권장

f : AppleFruit의 자손이므로 대입 가능.

 

 

 

 

[12-4] 아래의 메서드는 두 개의 ArrayList를 매개변수로 받아서, 하나의 새로운 ArrayList로 병합하는 메서드이다.

이를 지네릭 메서드로 변경하시오.

public static ArrayList<? extends Product> merge(
    ArrayList<? extends Product> list, ArrayList<? extends Product> list2) {
        ArrayList<? extends Product> newList = new ArrayList<>(list);

        newList.addAll(list2);

        return newList;
}

 

답 : 메서드의 반환 타입 앞에 어떤 지네릭 타입을 사용할 건지 작성해야 한다.

       매개 변수 타입마다 타입을 제한한 걸 지네릭 메서드로 변경하면 T로 치환 가능하다(가독성 향상)

       지네릭 클래스의 <T>와 메서드의 <T>는 별개이다(iv vs. lv 관계와 동일)

       선언된 지네릭 타입은 메서드 내에서만 유효하다.

public static <T extends Product> ArrayList<T> merge(
    ArrayList<T> list, ArrayList<T> list2) {
        ArrayList<T> newList = new ArrayList<>(list);

        newList.addAll(list2);

        return newList;
}

 

 

 

 

 

[12-5] 아래는 예제7-3에 열거형 Kind와 Number를 새로 정의하여 적용한 것이다. (1)에 알맞은 코드를 넣어 예제를 완성하시오.

(Math.random()을 사용했으므로 실행결과가 달라질 수 있다.)

class DeckTest {
    public static void main(String args[]) {
        Deck d = new Deck();    // 카드 한 벌(Deck)을 만든다.
        Card c = d.pick(0);     // 섞기 전에 제일 위의 카드를 뽑는다.
        System.out.println(c);  // System.out.println(c.toString());

        d.shuffle();            // 카드를 섞는다.
        c = d.pick(0);          // 섞은 후에 제일 위의 카드를 뽑는다.
        System.out.println(c);
    }
}

class Deck {
    final int CARD_NUM = Card.Kind.values().length * Card.Number.values().length;  // 카드의 개수
    Card cardArr[] = new Card[CARD_NUM];  // Card객체 배열을 포함

    Deck () {
        /*
            (1) 알맞은 코드를 넣어서 완성하시오.
                Deck의 카드를 초기화한다.
        */
    }

    Card pick(int index) {  // 지정된 위치(index)에 있는 카드 하나를 꺼내서 반환
        return cardArr[index];
    }

    Card pick() {  // Deck에서 카드 하나를 선택한다.
        int index = (int)(Math.random() * CARD_NUM);
        return pick(index);
    }

    void shuffle() {  // 카드의 순서를 섞는다.
        for(int i = 0; i < cardArr.length; i++) {
            int r = (int)(Math.random() * CARD_NUM);

            Card temp = cardArr[i];
            cardArr[i] = cardArr[r];
            cardArr[r] = temp;
        }
    }
}

class Card {
    enum Kind { CLOVER, HEART, DIAMOND, SPADE }
    enum Number {
        ACE, TWO, THREE, FOUR, FIVE,
        SIX, SEVEN, EIGHT, NINE, TEN,
        JACK, QUEEN, KING
    }

    Kind kind;
    Number num;

    Card() {
        this(Kind.SPADE, Number.ACE);
    }

    Card(Kind kind, Number num) {
        this.kind = kind;
        this.num = num;
    }

    public String toString() {
        return "[" + kind.name() + ", " + num.name() + "]";
    }
}
// [CLOVER, ACE]
// [HEART, TEN]

 

답 : values()를 통해 열거형의 모든 상수를 배열로 받아 향상된 for문을 이용하여 배열의 각 요소를 cardArr[]에 저장한다.

Deck () {
    int i = 0;

    for(Card.Kind kind : Card.Kind.values()) {
        for(Card.Number number : Card.Number.values()) {
            cardArr[i++] = new Card(kind, number);
        }
    }
}

 

 

 

 

 

[12-6] 다음 중 메타 애너테이션이 아닌 것을 모두 고르시오.

a. Documented

b. Target

c. Native

d. Inherited

 

답 : c

메타 애너테이션 : 애너테이션을 정의할 때 사용하는 애너테이션

a : 애너테이션에 대한 정보를 javadoc으로 작성한 문서에 포함시킬 때 사용한다.

b : 해당 애너테이션을 적용 할 수 있는 대상을 지정하는데 사용된다.

d : 애너테이션이 자손 클래스에도 상속되도록 한다. 조상 클래스에 붙이면, 자손 클래스도 붙은 것으로 인식된다.

     자손 클래스가 부모 클래스에 선언된 어노테이션을 상속 받는다.


c : native method에 의해 참조되는 상수 필드(constant field)에 붙이는 애너테이션이다.

     native method : JVM이 설치된 OS의 메서드.

                             : 보통 C언어로 작성되어 있으며, 자바에서는 메서드의 선언부만 정의하고 구현은 하지 않는다.

자바에서 기본적으로 제공하는 표준 애너테이션(*가 붙은 것은 메타 애너테이션)
자바에서 기본적으로 제공하는 표준 애너테이션(*가 붙은 것은 메타 애너테이션)

 

 

 

 

[12-7] 애너테이션 TestInfo가 다음과 같이 정의되어 있을 때, 이 애너테이션이 올바르게 적용되지 않은 것은?

@interface TestInfo {
    int count() default 1;
    String[] value() default "aaa";
}

a. @TestInfo                     class Exercise12_7 {}

b. @TestInfo(1)                class Exercise12_7 {}

c. @TestInfo("bbb")          class Exercise12_7 {}

d. @TestInfo("bbb", "ccc") class Exercise12_7 {}

 

답 : b, d

b : 요소의 이름이 value가 아닌 경우에는 값을 지정할 때 요소의 이름을 생략할 수 없다.

     @TestInfo(count=1)

d : 요소의 타입이 배열이라 지정하려는 값이 여러 개인 경우 괄호{}를 사용하여 값을 지정해야 한다.

     기본값을 여러 개 지정할 때도 괄호{}를 사용해야 한다.

     요소들의 값은 빠짐없이 지정해줘야 한다. 요소의 타입이 배열인데, 기본값을 지정하지 않았다면 {}만 입력해도 된다.

     @TestInfo({"bbb", "ccc"})

     @TestInfo(value={"bbb", "ccc"})

@interface TestInfo {
    String[] info() default {"aaa", "bbb"};  // 기본값이 여러 개인 경우
    String[] info2() default "ccc";          // 기본값이 하나인 경우(괄호 생략 가능)
    String[] info3();                        // 기본값을 지정하지 않음
}

@TestInfo({})  // 기본값을 지정하지 않았을 경우 괄호{}를 반드시 써줘야 한다.

a : default(기본)값이 지정된 요소는 애너테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용된다(생략 가능)

     기본값은 null을 제외한 모든 리터럴이 가능하다.

c : 요소의 이름이 value인 경우, 요소의 이름을 생략하고 값만 적어도 된다.

     @TestInfo("bbb")@TestInfo(count=1, value={"bbb"})의 생략된 형태이다.