자바의 정석 3판 (12) 연습문제 : 제네릭스, 열거형, 애너테이션

NtoZ·2023년 4월 10일
0

Java

목록 보기
22/23
post-thumbnail

제네릭스, 열거형, 애너테이션 연습문제


12-1. 제네릭스 규칙

  • 문제: [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. 도 오류가 발생한다. Box<String>객체를 통해서 T->String으로 변화했는데, Object 객체는 String 타입에 담기지 않는다. (조상객체 ->자손 참조변수X)
    d. 는 오류가 발생하지 않는다. T-> String 타입으로 바뀌어 정상적으로 item을 "ABC"로 지정할 수 있다.

  • 모범답안:

  [해설]
  // 에러. 대입된 타입이 반드시 같아야 한다.
  a.	Box<Object> b = new Box<String>(); 

  //(타입 불일치) 에러. Object타입을 Box<Object>타 입의 참조변수에 저장불가. 
  b.	Box<Object> b = (Object)new Box<String>(); 

  //에러. 대입된 타입이 String이므로, setItem(T item)의 매개변수 역시, String타입만 허용된다.
  c.	new Box<String>().setItem(new Object()); 

  // OK. 대입된 타입인 String과 일치하는 타입을 매개변수로 지정했기 때문에 OK.
  d.	new Box<String>().setItem("ABC"); 

⭐c와 관련하여
자바에서는 제네릭 타입에서 상속과 형변환을 함께 사용할 수 없습니다.

Box<String>은 Box 클래스의 제네릭 타입 파라미터인 T를 String으로 지정한 것입니다. 그리고 Box<String>Box<Object>의 서브 타입이 아닙니다.

따라서 Box<String>을 Box<Object>로 형변환할 수 없습니다. 올바른 코드는 다음과 같이 Box 클래스의 제네릭 타입 파라미터를 Object로 지정하는 것입니다.


✔️12-2. 지네릭 메서드

  • 문제:
//[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>());
  • 내 풀이:
class Juicer {
    //⭐ T는 Fruit을 상속받는 클래스타입이 와야한다.
    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>()); ❌ (Object는 Fruit을 상속받지 않음)

/*
⭐ 해당 문제는 Juicer 클래스의 지네릭 메서드에 대한 내용이다.
<T extends Fruit>로 지네릭 제약이 걸려있다는 점이 중요하다.
FruitBox<T> box로 전달받은 타입은 Fruit이거나 Fruit을 상속받는 Apple, Grape이어야 한다.
단, 리턴 타입 앞에 있는 타입이 더 조상클래스이어야 한다.❌❌❌ 따라서 a는 성립하지 않는다.
  • 정답:
<정답>
[정답] c, d

 
[해설]
a.	Juicer.<Apple>makeJuice(new FruitBox<Fruit>()); // 에러. 지네릭 메서드에 대입된 타입이 Apple이므로, 
//이 메서드의 매개변수는 'FruitBox<Apple>'타입이 된다. new FruitBox<Fruit>()는 매개변수의 타입과 일치하지 않으며, 자동형변환도 불가능한 타입이 므로 에러이다.
 
b.	Juicer.<Fruit>makeJuice(new FruitBox<Grape>()); // Grape가 Fruit의 자손이라고 해도 라고 해도, 타입이 다르기 때문에 같은 이유로 에러. 
 
c.	Juicer.<Fruit>makeJuice(new FruitBox<Fruit>()); // OK.
d.	Juicer.makeJuice(new FruitBox<Apple>()); // OK. 지네릭 메서드의 타입 호출이 생략된 형태. 생략하지 않았다면, ‘Juicer.<Apple>makeJuice(new FruitBox<Apple>());'과 같다. 대부분의 경우 이처럼 생략한다.
e.	Juicer.makeJuice(new FruitBox<Object>()); // 에러. 지네릭 메서드의 타입 호출이 생략되지 않았다면, ‘Juicer.<Object>makeJuice(new FruitBox<Object>());'과 같다. 
// d 번의 경우와같이 타입이 일치하긴 하지만, <T extends Fruit>로 제한이 걸려있으므로, 타입 T는 Fruit의 자손이어야 한다. Object는 Fruit의 자손이 아니므로 에러.
  • ✔️ <T extends Fruit> 같은 경우 매개변수 타입과 생성자 타입이 일치해야 한다. (와일드 카드가 아니라 지네릭 메서드임을 분명히 인지하자)
  • ✔️ d. Juicer.makeJuice(new FruitBox<Apple>());과 같은 경우에는 지네릭 메서드의 타입 호출이 생략된 형태이다. 생략하지 않았다면, ‘Juicer.<Apple>makeJuice(new FruitBox<Apple>());'과 같다. 대부분의 경우 이처럼 생략한다.

12-3. 지네릭 올바른 사용법

  • 문제:
////[12-3] 다음 중 올바르지 않은 문장을 모두 고르시오.
//
//        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>();
  • 내 풀이:
////[12-3] 다음 중 올바르지 않은 문장을 모두 고르시오.
//
//        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>(); ❌ 참조변수쪽에 <Fruit>을 붙이고 생성자는 생략 가능
//        f.	Box<? extends Fruit> b = new Box<Apple>();
//        g.	Box<? extends Object> b = new Box<? extends Fruit>();
  • 정답:
  • ✔️ Box<?>b = new Box<>(); // OK. new Box<>();는 타입을 생략되므로 일반적으로 참조변수의 타입과 같은 타입으로 간주된다.
  • ✔️ new 연산자는 타입이 명확해야하므로 와일드 카드와 같이 사용불가하다.

12-4. 지네릭 메서드로 두 ArrayList 병합하기

  • 문제
//[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;
        }
  • 정답

12-5. 클래스 내부에 선언된 열거형 사용하기

  • 문제 풀이
//[12-5] 아래는 예제7-3에 열거형 Kind와 Number를 새로 정의하여 적용한 것이다. (1)에 알맞은 코드를 넣어 예제를 완성하시오.
// (Math.random()을 사용했으므로 실행결과가 달라 질 수 있다.)

class DeckTest {
    public static void main(String args[]) {

        Deck d = new Deck();
        Card c = d.pick(0);

// 카드 한 벌(Deck)을 만든다.
// 섞기 전에 제일 위의 카드를 뽑는다.

        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의 카드를 초기화한다.
    */
        int i=0;
        //⭐Card 클래스 내부에 선언된 열거형 Kind 타입 kind를 Card.Kind.values()로 배열을 받아와 꺼내기
        for(Card.Kind kind : Card.Kind.values()) {
            //⭐ Card 내부 열거형 Number타입 num에 Card.Number.values()를 순회해서 받아오기
            for(Card.Number num : Card.Number.values()) {
                cardArr[i++] = new Card(kind, num);
            }
        }
    }

    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;
        }
    }
} // Deck클래스의 끝

// ⭐ Card클래스
class Card {

//⭐ 열거형 Kind 선언
enum Kind {CLOVER, HEART, DIAMOND, SPADE}

//⭐ 열거형 Number 선언
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() + "]";
    } // toString()의 끝
} // Card클래스의 끝

/*
<실행결과>
[CLOVER,ACE] [HEART,TEN]
 */

/*
<정답>
int i=0;

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

12-7. 애너테이션 사용하기

  • 문제풀이:
//[12-7] 애너테이션 TestInfo가 다음과 같이 정의되어 있을 대, 이 애너테이션이 올바르게 적용되지 않은 것은?
@interface TestInfo {
    int count() default 1;
    String[] value() default "aaa";
}


//a.	@TestInfo	class Exercise12_7 {}
//b.	@TestInfo(1)	class Exercise12_7 {}   ❌ value()가 String 타입이므로 불가능
//c.	@TestInfo("bbb")	class Exercise12_7 {}
//d.	@TestInfo("bbb","ccc") class Exercise12_7 {}
  • 해설:

		[정답] b, d

        [해설]
        a.	@TestInfo	class Exercise12_7 {}
    default값이 지정되어 있는 요소는 애너테이션을 적용할 때값을 생략할 수 있다.
        b.	@TestInfo(1)	class Exercise12_7 {}
    요소의 이름이 value가 아닌 경우에는 요소의 이름을 생략할 수 없다.@TestInfo(count=1)’이라고 써야 맞음.
        c.	@TestInfo("bbb")	class Exercise12_7 {} @TestInfo(count=1, value={"bbb"})의 생략된 형태
       ✔️✔️ d.	@TestInfo("bbb","ccc") class Exercise12_7 {}
    요소의 타입이 배열이고, 지정하려는 값이 여러 개인 경우 괄호{}가 필요함. @TestInfo({"bbb", "ccc"}) 또는 @TestInfo(value={"bbb","ccc"})와 같이 써야함
  • ✔️d. @TestInfo("bbb","ccc") class Exercise12_7 {}
    요소의 타입이 배열이고, 지정하려는 값이 여러 개인 경우 괄호{}가 필요함. @TestInfo({"bbb", "ccc"}) 또는 @TestInfo(value={"bbb","ccc"})와 같이 써야함
profile
9에서 0으로, 백엔드 개발블로그

0개의 댓글