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>
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의 자손이어야 한다.
Object는 Fruit의 조상이므로 에러가 발생한다.
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의 자손만 대입된 타입으로 올 수 있는데, Object는 Fruit의 자손이 아니기 때문에 에러가 발생한다.
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 : Apple는 Fruit의 자손이므로 대입 가능.
[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"})의 생략된 형태이다.
'Java의 정석 : 3rd Edition' 카테고리의 다른 글
[Java의 정석 - 연습문제] Chapter14. 람다와 스트림(Lambda & Stream) (0) | 2023.06.23 |
---|---|
[Java의 정석 - 연습문제] Chapter13. 쓰레드(thread) (0) | 2023.06.05 |
[Java의 정석 - 연습문제] Chapter11. Collections Framework (0) | 2023.05.30 |
[Java의 정석 - 연습문제] Chapter10. 날짜와 시간 & 형식화 (0) | 2023.05.11 |
[Java의 정석 - 연습문제] Chapter09. java.lang패키지와 유용한 클래스 (0) | 2023.02.06 |