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] 지네릭 메서드 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] 다음 중 올바르지 않은 문장을 모두 고르시오.
//
// 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<>();는 타입을 생략되므로 일반적으로 참조변수의 타입과 같은 타입으로 간주된다.//[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] 아래는 예제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] 애너테이션 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"})와 같이 써야함