본문 바로가기

Java의 정석 : 3rd Edition

[Java의 정석 - 연습문제] Chapter07. 객체지향 프로그래밍 II(OOP)

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

 

 

 

 

[7-1] 섯다카드 20장을 포함하는 섯다카드 한 벌(SutdaDeck 클래스)을 정의한 것이다.

섯다카드 20장을 담는 SutdaCard배열을 초기화하시오.

단, 섯다카드는 1부터 10까지의 숫자가 적힌 카드가 한 쌍씩 있고, 숫자가 1, 3, 8인 경우에는 둘 중의 한 장은 광(Kwang)이어야 한다.

즉, SutdaCard의 인스턴스변수 isKwang의 값이 true이어야 한다.

class SutdaDeck {
    final int CARD_NUM = 20;
    SutdaCard[] cards = new SutdaCard[CARD_NUM];

    SutdaDeck() {
        /*
            (1) 배열 SutdaCard를 적절히 초기화 하시오.
        */
    }
}

class SutdaCard {
    int num;
    boolean isKwang;

    SutdaCard() {
        this(1, true);
    }

    SutdaCard(int num, boolean isKwang) {
        this.num = num;
        this.isKwang = isKwang;
    }

    // info()대신 Object클래스의 toString()을 오버라이딩했다.
    public String toString() {
        return num + (isKwang ? "K" : "");
    }
}

class Exercise7_1 {
    public static void main(String args[]) {
        SutdaDeck deck = new SutdaDeck();

        for(int i = 0; i < deck.cards.length; i++) {
            System.out.print(deck.cards[i] + ",");
        }
    }
 }
 // 1K,2,3K,4,5,6,7,8K,9,10,1,2,3,4,5,6,7,8,9,10,

 

나의 답 : 

1. for문의 제어 변수 i를 사용하지 않고, for문 밖에 변수 num를 0으로 초기화했다.

2. for문 안에서 num의 값이 증가된다.

3. 숫자가 1, 3, 8인 경우 둘 중의 한 장은 광(Kwang)이어야 한다.

 3-1. 둘 중의 한 장만 광이어야 하므로 10 이하의 수(num)에만 생성자에 isKwang(true)를 전달한다.

 3-2. 10보다 크면 num(10을 뺀 값), isKwang(false)을 SutdaCard(int num, boolean isKwang)에 전달한다.

 3-3. 10보다 작다면 1, 3, 8인 경우에만 num, isKwang(true)을 SutdaCard(int num, boolean isKwang)에 전달한다.

SutdaDeck() {
    int num = 0;         // 1

    for (int i = 0; i < cards.length; i++) {
        num++;           // 2

        if (num > 10) {  // 3
            cards[i] = new SutdaCard((num-10), false);
        } else {
            boolean isKwang = (num == 1 || num == 3 || num == 8);
            cards[i] = new SutdaCard(num, isKwang);
        }
    }
}

 

해설 : 

※ 객체배열을 생성할 때, 배열만 생성해 놓고 객체를 생성하지 않는 실수를 하지 않도록 주의해야 한다.

SutdaCard[] cards = new SutdaCard[CARD_NUM];  // 선언 : 인스턴스 생성되지 않은 상태

num의 값을 얻는 방법
num의 값을 얻는 방법

 

1. i의 값이 0~19까지 변하는 동안 num의 값으로 계산식 i%10+1을 사용하면 된다.

 1-2. 계산식 i%10을 이용하면 i가 10이상일 때 일의 숫자만 남게 된다.

 1-1. 섯다카드는 1~10까지의 숫자이므로 계산식 i%10에 1을 더해야 한다.

2. 한 쌍의 카드 중에서 하나는 광(kwang)이어야하므로 조건식이 필요하다.

 2-1. AND(&&)가 OR(II)보다 우선순위가 높기 때문에 괄호를 꼭 사용해야한다.

SutdaDeck() {
    for(int i = 0; i < cards.length; i++) {
        int num = i % 10 + 1;                                              // 1
        boolean isKwang = (i < 10) && (num == 1 || num == 3 || num == 8);  // 2
        
        cards[i] = new SutdaCard(num, isKwang);
    }
}

 

 

 

 

[7-2] 문제7-1의 SutdaDeck클래스에 다음에 정의된 새로운 메서드를 추가하고 테스트 하시오.

[주의] Math.random()을 사용하는 경우 실행결과와 다를 수 있음.

 

1. 메서드명 : shuffle

    기       능 : 배열 cards에 담긴 카드의 위치를 뒤섞는다. (Math.random() 사용)

    반환타입 : 없음

    매개변수 : 없음

 

2. 메서드명 : pick

    기       능 : 배열 cards에서 지정된 위치의 SutdaCard를 반환한다.

    반환타입 : SutdaCard

    매개변수 : int index - 위치

 

3. 메서드명 : pick

    기       능 : 배열 cards에서 임의의 위치의 SutdaCard를 반환한다. (Math.random() 사용)

    반환타입 : SutdaCard

    매개변수 : 없음

class SutdaDeck {
    final int CARD_NUM = 20;
    SutdaCard[] cards = new SutdaCard[CARD_NUM];

    SutdaDeck() {
        /*
            문제 7-1의 답이므로 내용생략
        */
    }

    /*
        (1) 위에 정의된 세 개의 메서드를 작성하시오.
    */
}

class SutdaCard {
    int num;
    boolean isKwang;

    SutdaCard() {
        this(1, true);
    }

    SutdaCard(int num, boolean isKwang) {
        this.num = num;
        this.isKwang = isKwang;
    }

    public String toString() {
        return num + (isKwang ? "K" : "");
    }
}

class Exercise7_2 {
    public static void main(String args[]) {
        SutdaDeck deck = new SutdaDeck();
		
        System.out.println(deck.pick(0));
        System.out.println(deck.pick());
        deck.shuffle();

        for(int i = 0; i < deck.cards.length; i++)
            System.out.print(deck.cards[i] + ",");
            
        System.out.println();
        System.out.println(deck.pick(0));
    }
}
// 1K
// 7
// 2,6,10,1K,7,3,10,5,7,8,5,1,2,9,6,9,4,8K,4,3K,
// 2

 

나의 답 :

1. 임의의 요소(random)와 cards 배열의 요소를 순차적으로 교환한다.

2. cards 배열의 임의의 요소를 for문으로 찾아 해당 요소의 값을 반환한다.

    *해설 참고 : pick(int index)를 호출하여 처리하면 코드의 중복이 줄어든다.

3. 매개변수로 전달받은 index의 값을 for문으로 cards 배열에서 찾아 해당 요소의 값을 반환한다.

    *해설 참고 : 반복문 필요없이, 매개변수로 전달받은 index를 배열의 요소([])에 넣어 반환하면 된다.

void shuffle() {            // 1
    for (int i = 0; i < cards.length; i++) {
        int random = (int) (Math.random() * cards.length);

        SutdaCard temp = cards[i];  // 객체의 요소를 담아야하므로 SutdaCard 타입이어야 함.
        cards[i] = cards[random];
        cards[random] = temp;
    }
}

SutdaCard pick() {          // 2
    SutdaCard number = null;

    for (int i = 0; i < cards.length; i++) {
        int random = (int) (Math.random() * cards.length);
        number = cards[random];
        break;
    }
    return number;
}

SutdaCard pick(int index) {  // 3
    SutdaCard number = null;

    for (int i = 0; i < cards.length; i++) {
        number = cards[index];
        break;
    }
    return number;
}

 

나의 답(코드 수정)

1. 해설 코드는 범위 밖일 때 null을 반환하지만, 범위내에 있는 값일 때 요소의 값을 반환하도록 변형하였다.

 1-1. index 범위 : 0~20(CARD_NUM)

void shuffle() {
    for (int i = 0; i < cards.length; i++) {
        int random = (int) (Math.random() * cards.length);

        SutdaCard temp = cards[i];
        cards[i] = cards[random];
        cards[random] = temp;
    }
}

SutdaCard pick() {
    int index = (int) (Math.random() * cards.length);
    return pick(index);                     // pick(int index) 호출
}

SutdaCard pick(int index) {
    if (index >= 0 && index <= CARD_NUM) {  // 1 : 유효성 검사
        return cards[index];
    }
    return null;
}

 

해설 : 

※ 매개변수가 있는 메서드는 반드시 작업 전에 유효성 검사를 해야 한다.

1. Math.random()을 이용하여 cards 배열에 있는 임의의 카드를 꺼내 pick(int index)에 전달한다.

 1-1. 비효율적이지만 코드의 중복을 제거하고 재사용성을 높였다.

2. pick(int index)는 매개변수 index에 대한 유효성 검사가 필요하다.

 2-1. 유효성 검사 수행하지 않으면 배열 index 범위를 넘는 값이 들어올 때 ArrayIndexOutOfBoundsException가 발생된다.

SutdaCard pick() {
    int index = (int)(Math.random() * cards.length);
    return pick(index);                 // 1. pick(int index) 호출
}

SutdaCard pick(int index) {
    if(index < 0 || index >= CARD_NUM)  // 2. index의 유효성 검사
        return null;
    return cards[index];
}

 

 

 

 

[7-3] 오버라이딩의 정의와 필요성에 대해서 설명하시오.

 

나의 답 : 

- 오버라이딩 : 조상 클래스에 정의된 메서드를 상속받은 자손 클래스가 자신에 맞게 재정의하는 것
- 필요성 : 조상 클래스의 메서드를 정의된 그대로 사용할 수 밖에 없다면 자손 클래스에서 추가된 기능을 사용하기 위해 또 다른 메서드를 정의해야 함.(코드 중복)

 

해설 : 

- 오버라이딩(overriding) : 조상 클래스로부터 상속받은 메서드를 자손 클래스에 맞게 재정의하는 것

- 필요성 : 조상 클래스로부터 상속받은 메서드를 자손 클래스에서 그대로 사용할 수 없는 경우가 많기 때문에 오버라이딩이 필요하다.

 

 

 

 

[7-4] 다음 중 오버라이딩의 조건으로 옳지 않은 것은? (모두 고르시오)

a. 조상의 메서드와 이름이 같아야 한다.

b. 매개변수의 수와 타입이 모두 같아야 한다.

c. 접근 제어자는 조상의 메서드보다 좁은 범위로만 변경할 수 있다.

d. 조상의 메서드보다 더 많은 수의 예외를 선언할 수 있다.

 

c : 접근 제어자는 조상 메서드보다 좁은 범위로 변경할 수 없다. (같거나 넓어야 함)
d : 조상 메서드보다 더 많은 수의 예외를 선언할 수 없음.

 

 

 

 

[7-5] 다음의 코드는 컴파일하면 에러가 발생한다.

그 이유를 설명하고 에러를 수정하기 위해서는 코드를 어떻게 바꾸어야 하는가?

class Product {
    int price;       // 제품의 가격
    int bonusPoint;  // 제품구매 시 제공하는 보너스점수

    Product(int price) {
        this.price = price;
        bonusPoint = (int)(price/10.0);
    }
}

class Tv extends Product {
    Tv() {}

    public String toString() {
        return "Tv";
    }
}

class Exercise7_5 {
    public static void main(String[] args) {
        Tv t = new Tv();
    }
}

 

나의 답 : 

- 자손의 생성자(Tv) 첫 줄에는 조상의 생성자를 호출하거나 같은 클래스의 다른 생성자를 호출해야 한다.

- 생성자에 정의된 게 없으니 컴파일러가 조상 생성자를 호출하는 super();를 자동으로 추가한다.

- 조상 클래스에 Product(int price) 생성자가 정의되어 있으므로 해당 생성자를 호출했다.

- Product(int price) → super(가격입력) → super(1000)

※ 기본 생성자를 작성하지 않으면 에러의 원인이 될 수 있으니 항상 기본 생성자를 작성하자.

Tv() {
    // super(); // 컴파일러의 역할 : 조상의 기본 생성자 호출
}
Tv() {
    super(1000); // Product(int price) 호출
}

 

해설 :

- Tv클래스의 인스턴스를 생성할 때, 생성자 Tv()가 호출되고 Tv()는 조상의 기본 생성자 super()를 호출한다.

- Product클래스에 기본 생성자 Product()가 없어 에러가 발생한다. (정의되어 있지 않은 생성자 호출)

- Product클래스에는 Product(int price) 생성자가 정의되어 있으므로 컴파일러가 기본 생성자를 자동으로 추가하지 않는다.

- 직접 Product클래스에 Product() {}를 넣어주면 문제가 해결된다.

Product() {}

 

 

 

 

[7-6] 자손 클래스의 생성자에서 조상 클래스의 생성자를 호출해야하는 이유는 무엇인가?

 

나의 답 : 해당 멤버를 어떻게 초기화해야될지 가장 잘 아는 건 해당 클래스이다.

- 조상 클래스에 정의된 멤버는 조상 클래스에서 초기화되어야 한다.
- 마찬가지로 자손 클래스에 정의된 멤버는 자손 클래스에서 초기화되어야 한다.

 

해설 : 조상에 정의된 인스턴스 변수들이 초기화되도록 하기 위함.

- 자손 클래스의 인스턴스를 생성하면 조상으로부터 상속받은 인스턴스 변수들도 생성된다.

- 조상의 인스턴스 변수들을 자손 생성자에서 직접 초기화하기보다 조상의 생성자를 호출함으로써 초기화되도록 하는 것이 바람직하다.

- 각 클래스의 생성자는 해당 클래스에 선언된 인스턴스 변수의 초기화만을 담당하고,

  조상 클래스로부터 상속받은 인스턴스 변수의 초기화는 조상 클래스의 생성자가 처리하도록 해야 한다.

 

 

 

 

[7-7] 다음 코드의 실행했을 때 호출되는 생성자의 순서와 실행결과를 적으시오.

class Parent {
    int x = 100;

    Parent() {
        this(200);
    }

    Parent(int x) {
        this.x = x;
    }

    int getX() {
        return x;
    }
}

class Child extends Parent {
    int x = 3000;

    Child() {
        this(1000);
    }

    Child(int x) {
        this.x = x;
    }
}

class Exercise7_7 {
    public static void main(String[] args) {
        Child c = new Child();
        System.out.println("x = " + c.getX());
    }
}

 

나의 답 : 

- 호출되는 생성자 순서 : Child() → Child(int x) → Parent() → Parent(int x) → Object()
- 실행결과 : x = 200

- getX()는 Parent클래스에 정의되어 있으므로 Parent클래스의 인스턴스 변수 x를 반환한다.

※ 컴파일러는 생성자의 첫 줄에 조상/다른 생성자를 호출하지 않으면 조상의 기본 생성자를 호출하는 코드를 넣는다.

class Parent {
    int x = 100;

    Parent() {       // 4. Parent(int x) 호출
        this(200);
    }

    Parent(int x) {  // 5. super() 호출(Object())
        // super(); // 컴파일러의 역할
        this.x = x;
    }

    int getX() {
        return x;
    }
}

class Child extends Parent {
    int x = 3000;

    Child() {       // 2. Child(int x) 호출
        this(1000);
    }

    Child(int x) {  // 3. super() 호출(Parent())
        // super(); // 컴파일러의 역할
        this.x = x;
    }
}

class Exercise7_7 {
    public static void main(String[] args) {
        Child c = new Child();  // 1. Child() 호출
        System.out.println("x = " + c.getX());
    }
}

 

 

 

 

[7-8] 다음 중 접근제어자를 접근범위가 넓은 것에서 좁은 것의 순으로 바르게 나열한 것은?

a. public-protected-(default)-private

b. public-(default)-protected-private

c. (default)-public-protected-private

d. private-protected-(default)-public

 

public > protected > (default) > private

접근제어자의 접근범위(넓은범위&gt;좁은범위)
접근제어자의 접근범위(넓은범위>좁은범위)

 

 

 

 

[7-9] 다음 중 제어자 final을 붙일 수 있는 대상과 붙였을 때 그 의미를 적은 것이다.

옳지 않은 것은? (모두 고르시오)

a. 지역변수 - 값을 변경할 수 없다.

b. 클래스 - 상속을 통해 클래스에 새로운 멤버를 추가할 수 없다.

c. 메서드 - 오버로딩을 할 수 없다.  오버라이딩(overriding)을 할 수 없다.

d. 멤버변수 - 값을 변경할 수 없다.

 

b : 클래스 - 상속을 통해 클래스에 새로운 멤버를 추가할 수 없다.

     (다른 클래스의 조상은 될 수 없지만 자손은 될 수 있지 않을까?)

+) 상속 관계를 통해 다른 자손 클래스에서 새로운(조상) 멤버로 가질 수 없게 된다. (조상 멤버로 물려받을 수 없음)

+) 자손 클래스(조상 멤버(새로운 멤버) + 자신 멤버)

 

 

 

 

[7-10] MyTv2클래스의 멤버변수 isPowerOn, channel, volume을 클래스 외부에서 접근할 수 없도록 제어자를 붙이고

대신 이 멤버변수들의 값을 어디서나 읽고 변경할 수 있도록 getter와 setter메서드를 추가하라.

class MyTv2 {
    boolean isPowerOn;
    int channel;
    int volume;

    final int MAX_VOLUME = 100;
    final int MIN_VOLUME = 0;
    final int MAX_CHANNEL = 100;
    final int MIN_CHANNEL = 1;

    /*
        (1) 알맞은 코드를 넣어 완성하시오.
    */
}

class Exercise7_10 {
    public static void main(String args[]) {
        MyTv2 t = new MyTv2();

        t.setChannel(10);
        System.out.println("CH:" + t.getChannel());
        t.setVolume(20);
        System.out.println("VOL:" + t.getVolume());
    }
}
// CH:10
// VOL:20

 

나의 답 : 

1. setChannel(int channel)

 1-1. 매개변수 channel(lv)의 값이 MAX_CHANNEL보다 크면 channel(iv)에 MAX_CHANNEL의 값을 저장한다.

 1-2. 매개변수 channel(lv)의 값이 MIN_CHANNEL보다 작다면 channel(iv)에 MIN_CHANNEL의 값을 저장한다.

2. setVolume(int volume)

 2-1. 매개변수 volume(lv)의 값이 MAX_VOLUME보다 크면 volume(iv)에 MAX_VOLUME의 값을 저장한다.

 2-2. 매개변수 volume(lv)의 값이 MIN_VOLUME보다 작다면 volume(iv)에 MIN_VOLUME의 값을 저장한다.

class MyTv2 {
    private boolean isPowerOn;
    private int channel;
    private int volume;

    final int MAX_VOLUME = 100;
    final int MIN_VOLUME = 0;
    final int MAX_CHANNEL = 100;
    final int MIN_CHANNEL = 1;

    public void setChannel(int channel) {  // 1
        if (channel > MAX_CHANNEL) {
            this.channel = MAX_CHANNEL;
        } else if (channel < MIN_CHANNEL) {
            this.channel = MIN_CHANNEL;
        } else {
            this.channel = channel;
        }
    }

    public int getChannel() {
        return channel;
    }

    public void setVolume(int volume) {  // 2
        if (volume > MAX_VOLUME) {
            this.volume = MAX_VOLUME;
        } else if (volume < MIN_VOLUME) {
            this.volume = MIN_VOLUME;
        } else {
            this.volume = volume;
        }
    }

    public int getVolume() {
        return volume;
    }
}

 

해설 : 유효한 값이 아닌 경우 메서드를 종료(return)한다.

public void setChannel(int channel){
    if(channel > MAX_CHANNEL || channel < MIN_CHANNEL)
        return;
    this.channel = channel;
}

public int getChannel(){
    return channel;
}

public void setVolume(int volume){
    if(volume > MAX_VOLUME || volume < MIN_VOLUME)
        return;
    this.volume = volume;
}

public int getVolume(){
    return volume;
}

 

 

 

 

[7-11] 문제7-10에서 작성한 MyTv2클래스에 이전 채널(previous channel)로 이동하는 기능의 메서드를 추가해서 실행결과와 같은 결과를 얻도록 하시오.

[Hint] 이전 채널의 값을 저장할 멤버변수를 정의하라.

 

메서드명 : gotoPrevChannel

기       능 : 현재 채널을 이전 채널로 변경한다.

반환타입 : 없음

매개변수 : 없음

class MyTv2 {
    /*
        (1) 문제7-10의 MyTv2클래스에 gotoPrevChannel메서드를 추가하여 완성하시오.
    */
}

class Exercise7_11 {
    public static void main(String args[]) {
        MyTv2 t = new MyTv2();

        t.setChannel(10);
        System.out.println("CH:" + t.getChannel());
        t.setChannel(20);
        System.out.println("CH:" + t.getChannel());
        t.gotoPrevChannel();
        System.out.println("CH:" + t.getChannel());
        t.gotoPrevChannel();
        System.out.println("CH:" + t.getChannel());
    }
}
// CH:10
// CH:20
// CH:10
// CH:20

 

나의 답 :

1. t.setChannel(10) : prevChannel = this.channel(0), this.channel = int channel(10)

2. t.setChannel(20) : prevChannel = this.channel(10), this.channel = int channel(20)

3. t.gotoPrevChannel() : setChannel(prevChannel(10)) 호출

 3-1. prevChannel = this.channel(20), this.channel = int channel(10)

4. t.gotoPrevChannel() : setChannel(prevChannel(20)) 호출

 4-1. prevChannel = this.channel(10), this.channel = int channel(20)

[7-11] 실행과정
[7-11] 실행과정

t.setChannel(10);                            // 1
System.out.println("CH:" + t.getChannel());
t.setChannel(20);                            // 2
System.out.println("CH:" + t.getChannel());
t.gotoPrevChannel();                         // 3
System.out.println("CH:" + t.getChannel());
t.gotoPrevChannel();                         // 4
System.out.println("CH:" + t.getChannel());
class MyTv2 {
    private int prevChannel;

    public void setChannel(int channel) {
        prevChannel = this.channel;  // 현재 채널을 이전 채널에 저장

        if (channel > 100) {
            this.channel = MAX_CHANNEL;
        } else if (channel < 1) {
            this.channel = MIN_CHANNEL;
        } else {
            this.channel = channel;
        }
    }

    public void gotoPrevChannel() {
        setChannel(prevChannel);    // 현재 채널을 이전 채널로 변경
    }
}

 

 

 

 

[7-12] 다음 중 접근 제어자에 대한 설명으로 옳지 않은 것은? (모두 고르시오)

a. public은 접근제한이 전혀 없는 접근 제어자이다.

b. (default)가 붙으면, 같은 패키지 내에서만 접근이 가능하다.

c. 지역변수에도 접근 제어자를 사용할 수 있다.

d. protected가 붙으면, 같은 패키지 내에서도 접근이 가능하다.

e. protected가 붙으면, 다른 패키지의 자손 클래스에서 접근이 가능하다.

 

c : 지역변수는 메서드 영역내에서만 사용되므로 접근 제어자를 사용할 수 없다.

+) 접근 제어자가 사용될 수 있는 곳 : 클래스, 멤버변수, 메서드, 생성자

 

 

 

 

[7-13] Math클래스의 생성자는 접근 제어자가 private이다. 그 이유는 무엇인가?

 

나의 답 : Math는 수학과 관련된 메서드가 존재하는 클래스로 메서드가 반환해야하는 값이 정해져있음.

해설 : Math클래스의 모든 메서드가 static메서드이고, 인스턴스 변수가 존재하지 않으므로 객체를 생성할 필요 없음.

 

 

 

 

[7-14] 문제7-1에 나오는 섯다카드의 숫자와 종류(isKwang)는 사실 한번 값이 지정되면 변경되어서는 안 되는 값이다.

카드의 숫자가 한번 잘못 바뀌면 똑같은 카드가 두 장이 될 수도 있기 때문이다.

이러한 문제점이 발생하지 않도록 아래의 SutdaCard를 수정하시오.

class SutdaCard {
    int num;
    boolean isKwang;

    SutdaCard() {
        this(1, true);
    }

    SutdaCard(int num, boolean isKwang) {
        this.num = num;
        this.isKwang = isKwang;
    }

    public String toString() {
        return num + (isKwang ? "K" : "");
    }
}

class Exercise7_14 {
    public static void main(String args[]) {
        SutdaCard card = new SutdaCard(1, true);
    }
}

 

나의 답 :

상수는 원래 선언과 동시에 초기화되어야 한다.

상수(인스턴스 변수)는 생성자에서 단 한번 초기화 할 수 있으므로 num, isKwang를 상수로 정의해야 한다.

+) 해설에서는 상수 ISKWANG를 IS_KWANG로 정의했는데 관례인 거 같다.

class SutdaCard {
    final private int NUM;          // 상수 : 대문자로 정의(관례)
    final private boolean ISKWANG;  // 상수 : 대문자로 정의(관례)

    SutdaCard() {
        this(1, true);
    }

    SutdaCard(int NUM, boolean ISKWANG) {
        this.NUM = NUM;
        this.ISKWANG = ISKWANG;
    }

    public String toString() {
        return NUM + (ISKWANG ? "K" : "");
    }
}

 

 

 

 

[7-15] 클래스가 다음과 같이 정의되어 있을 때, 형변환을 올바르게 하지 않은 것은? (모두 고르시오.)

class Unit {}
class AirUnit extends Unit {}
class GroundUnit extends Unit {}
class Tank extends GroundUnit {}
class AirCraft extends AirUnit {}

Unit u = new GroundUnit();
Tank t = new Tank();
AirCraft ac = new AirCraft();

a. u = (Unit)ac;                                 자손 → 조상(형변환 생략 가능)

b. u = ac;                                          자손 → 조상(형변환 생략 가능)

c. GroundUnit gu = (GroundUnit)u;  조상 → 자손(형변환 생략 불가)

d. AirUnit au = ac;                             자손 → 조상(형변환 생략 가능)

e. t = (Tank)u;                                  ← 조상타입의 인스턴스를 자손타입으로 형변환 할 수 없다.

f. GroundUnit gu = t;                         자손 → 조상(형변환 생략 가능)

 

[7-15] 클래스간 상속관계

c. GroundUnit gu = (GroundUnit)u;

    Unit u = new GroundUnit();  // GroundUnit 객체로 생성했으므로 원래의 GroundUnit 인스턴스로 형변환 가능

e. t = (Tank)u; 

    Unit u = new GroundUnit();  // GroundUnit 객체로 생성했으므로 Tank 인스턴스로 형변환 불가능

    Unit u = new Tank();             // 해결방법

 

*조상 타입의 참조변수(자손 인스턴스 가리킴)로 자손 타입의 인스턴스 참조 가능

  (ex. Tv tv = new SmartTv() → SmartTv 참조O)

*조상 타입의 참조변수(조상 인스턴스 가리킴)로 자손 타입의 인스턴스 참조 불가

  (ex. Tv tv = new Tv() SmartTv 참조X)

// 상속관계 : Car(조상), FireEngine(자손)
Car car = new Car();
FireEngine fe = null;

fe = (FireEngine)car;  // 에러 : 참조변수(조상 인스턴스 참조)로 자손 인스턴스 참조 불가
Car car = new FireEngine();  // 해결방법 : 자손 타입으로 객체 생성
FireEngine fe = null;

fe = (FireEngine)car;

 

 

 

 

[7-16] 다음 중 연산결과가 true가 아닌 것은? (모두 고르시오)

class Car {}
class FireEngine extends Car implements Movable {}
class Ambulance extends Car {}

FireEngine fe = new FireEngine();

a. fe instanceof FireEngine

b. fe instanceof Movable

c. fe instanceof Object

d. fe instanceof Car

e. fe instanceof Ambulance

 

e : FireEngine과 Ambulance는 아무런 관계가 아니므로 false를 반환한다.

※ 참조변수 형변환을 수행하기 전 instanceof연산자로 형변환 가능한지 미리 확인해 보는 것이 좋다.

- instanceof연산자는 실제 인스턴스의 모든 조상이나 구현한 인터페이스에 대해 true를 반환한다.

- 어떤 타입의 instanceof 연산결과가 true라는건 해당 타입으로 형변환 가능하다는 것을 의미한다.

[7-16] 클래스간 상속계층도
[7-16] 클래스간 상속계층도

 

 

 

 

[7-17] 아래 세 개의 클래스로부터 공통부분을 뽑아 Unit이라는 클래스를 만들고,

이 클래스를 상속받도록 코드를 변경하시오.

class Marine { // 보병
    int x, y;                    // 현재 위치
    void move(int x, int y) { }  // 지정된 위치로 이동
    void stop() { }              // 현재 위치에 정지
    void stimPack() { }          // 스팀팩을 사용한다.
}

class Tank { // 탱크
    int x, y;                    // 현재 위치
    void move(int x, int y) { }  // 지정된 위치로 이동
    void stop() { }              // 현재 위치에 정지
    void changeMode() { }        // 공격모드를 변환한다.
}

class Dropship { // 수송선
    int x, y;                    // 현재 위치
    void move(int x, int y) { }  // 지정된 위치로 이동
    void stop() { }              // 현재 위치에 정지
    void load() { }              // 선택된 대상을 태운다.
    void unload() { }            // 선택된 대상을 내린다.
}

 

나의 답 : 각 클래스마다 이동하는 방법은 다르므로 move()는 추상메서드로 정의하였다.

abstract class Unit {
    int x, y;                          // 현재 위치
    abstract void move(int x, int y);  // 지정된 위치로 이동(추상 메서드)
    void stop() { }                    // 현재 위치에 정지
}

class Marine extends Unit { // 보병
    void move(int x, int y) { }       // 지정된 위치로 이동
    void stimPack() { }               // 스팀팩을 사용한다.
}

class Tank extends Unit { // 탱크
    void move(int x, int y) { }       // 지정된 위치로 이동
    void changeMode() { }             // 공격모드를 변환한다.
}

class Dropship extends Unit { // 수송선
    void move(int x, int y) { }       // 지정된 위치로 이동
    void load() { }                   // 선택된 대상을 태운다.
    void unload() { }                 // 선택된 대상을 내린다.
}

 

 

 

 

[7-18] 다음과 같은 실행결과를 얻도록 코드를 완성하시오.

[Hint] instanceof연산자를 사용해서 형변환한다.

 

메서드명 : action

기       능 : 주어진 객체의 메서드를 호출한다.

                 DanceRobot인 경우, dance()를 호출하고,

                 SingRobot인 경우, sing()을 호출하고,

                 DrawRobot인 경우, draw()를 호출한다.

반환타입 : 없음

매개변수 : Robot r - Robot인스턴스 또는 Robot의 자손 인스턴스

class Exercise7_18 {
    /*
        (1) action메서드를 작성하시오.
    */

    public static void main(String[] args) {
        Robot[] arr = {new DanceRobot(), new SingRobot(), new DrawRobot()};

        for(int i = 0; i < arr.length; i++)
            action(arr[i]);
    } // main
}

class Robot {}

class DanceRobot extends Robot {
    void dance() {
        System.out.println("춤을 춥니다.");
    }
}

class SingRobot extends Robot {
    void sing() {
        System.out.println("노래를 합니다."); 
    }
}

class DrawRobot extends Robot {
    void draw() {
        System.out.println("그림을 그립니다.");
    }
}
// 춤을 춥니다.
// 노래를 합니다.
// 그림을 그립니다

 

나의 답 : 

action()는 매개변수의 타입이 Robot클래스이거나 그 자손 클래스의 인스턴스일 것이라는 것만 알 수 있다.
- 매개변수의 실제 인스턴스가 정확히 어떤 것인지는 알 수 없다.

- instanceof 연산자를 이용해야만 실제 인스턴스의 타입을 확인할 수 있다.

public static void action(Robot r) {
    if (r instanceof DanceRobot) {
        DanceRobot dr = (DanceRobot)r;  // 조상 → 자손(형변환 생략 불가)
        dr.dance();
    }

    if (r instanceof SingRobot) {
        SingRobot sr = (SingRobot)r;   // 조상 → 자손(형변환 생략 불가)
        sr.sing();
    }

    if (r instanceof DrawRobot) {
        DrawRobot dwr = (DrawRobot)r;  // 조상 → 자손(형변환 생략 불가)
        dwr.draw();
    }
}

 

 

 

 

[7-19] 다음은 물건을 구입하는 사람을 정의한 Buyer클래스이다.

이 클래스는 멤버변수로 돈(money)과 장바구니(cart)를 가지고 있다.

제품을 구입하는 기능의 buy메서드와 장바구니에 구입한 물건을 추가하는 add메서드,

구입한 물건의 목록과 사용금액, 그리고 남은 금액을 출력하는 summary메서드를 완성하시오.

 

1. 메서드명 : buy

    기       능 : 지정된 물건을 구입한다. 가진 돈(money)에서 물건의 가격을 빼고, 장바구니(cart)에 담는다.

                     만일 가진 돈이 물건의 가격보다 적다면 바로 종료한다.

    반환타입 : 없음

    매개변수 : Product p - 구입할 물건

 

2. 메서드명 : add

    기       능 : 지정된 물건을 장바구니에 담는다.

                     만일 장바구니에 담을 공간이 없으면, 장바구니의 크기를 2배로 늘린 다음에 담는다.

    반환타입 : 없음

    매개변수 : Product p - 구입할 물건

 

3. 메서드명 : summary

    기       능 : 구입한 물건의 목록과 사용금액, 남은 금액을 출력한다.

    반환타입 : 없음

    매개변수 : 없음

class Exercise7_19 {
    public static void main(String args[]) {
        Buyer b = new Buyer();
        b.buy(new Tv());
        b.buy(new Computer());
        b.buy(new Tv());
        b.buy(new Audio());
        b.buy(new Computer());
        b.buy(new Computer());
        b.buy(new Computer());
        
        b.summary();
    }
}

class Buyer {
    int money = 1000;
    Product[] cart = new Product[3];  // 구입한 제품을 저장하기 위한 배열
    int i = 0;                        // Product배열 cart에 사용될 index

    void buy(Product p) {
        /*
            (1) 아래의 로직에 맞게 코드를 작성하시오.
            1.1 가진 돈과 물건의 가격을 비교해서 가진 돈이 적으면 메서드를 종료한다.
            1.2 가진 돈이 충분하면, 제품의 가격을 가진 돈에서 빼고
            1.3 장바구니에 구입한 물건을 담는다. (add메서드 호출)
        */
    }
    
    void add(Product p) {
        /*
            (2) 아래의 로직에 맞게 코드를 작성하시오.
            1.1 i의 값이 장바구니의 크기보다 같거나 크면
              1.1.1 기존 장바구니보다 2배 큰 새로운 배열을 생성한다.
              1.1.2 기존 장바구니의 내용을 새로운 배열에 복사한다.
              1.1.3 새로운 장바구니와 기존의 장바구니를 바꾼다.
            1.2 물건을 장바구니(cart)에 저장한다. 그리고 i의 값을 1 증가시킨다.
        */
    } // add(Product p)

    void summary() {
        /*
            (3) 아래의 로직에 맞게 코드를 작성하시오.
            1.1 장바구니에 담긴 물건들의 목록을 만들어 출력한다.
            1.2 장바구니에 담긴 물건들의 가격을 모두 더해서 출력한다.
            1.3 물건을 사고 남은 금액(money)를 출력한다.
        */
    } // summary()
}

class Product {
    int price; // 제품의 가격
    
    Product(int price) {
        this.price = price;
    }
}

class Tv extends Product {
    Tv() { super(100); }
    
    public String toString() { return "Tv"; }
}

class Computer extends Product {
    Computer() { super(200); }

    public String toString() { return "Computer"; }
}

class Audio extends Product {
    Audio() { super(50); }
    
    public String toString() { return "Audio"; }
}
// 잔액이 부족하여 Computer을/를 살수 없습니다.
// 구입한 물건:Tv,Computer,Tv,Audio,Computer,Computer,
// 사용한 금액:850
// 남은 금액:150

 

나의 답 : 

처음 (2)1.1.2 로직(기존 장바구니 내용을 새로운 배열에 복사)을 구현할 때 Arrays.copyOf()를 사용했다.

예제에서 배열을 복사할 때 봤던 메서드였는데, ArrayIndexOutOfBoundsException가 발생되었다.

코드초보스터디 카페에서 문의한 결과 명쾌한 해답을 얻을 수 있게 되었다.

 +) Arrays.copyOf는 새로 만들어둔 배열(temp)에 원본 배열(cart)을 복사하는 게 아니라

 +) Arrays.copyOf(배열, 길이) : 인자로 넘겨준 배열(cart)의 길이(cart.length)만큼 복사한 새로운 배열을 반환한다.

 +) 즉, 본래 cart 배열과 길이(cart.length)가 같은 새로운 배열이 반환된다.

void add(Product p) {
    if (i >= cart.length) {
        Product[] temp = new Product[cart.length * 2];
        temp = Arrays.copyOf(cart, cart.length);  // 에러 : ArrayIndexOutOfBoundsException
        // System.arraycopy(cart, 0, temp, 0, cart.length);
        cart = temp;
    }
    cart[i++] = p;
}

 

void buy(Product p) {
    /*
        (1) 아래의 로직에 맞게 코드를 작성하시오.
        1.1 가진 돈과 물건의 가격을 비교해서 가진 돈이 적으면 메서드를 종료한다.
        1.2 가진 돈이 충분하면, 제품의 가격을 가진 돈에서 빼고
        1.3 장바구니에 구입한 물건을 담는다. (add메서드 호출)
    */
    if (money < p.price) {  // (1)1.1
        System.out.println("잔액이 부족하여 " + p + "을/를 살 수 없습니다.");
        return;
    } else {
        money -= p.price;   // (1)1.2
        add(p);             // (1)1.3
    }
}

void add(Product p) {
    /*
        (2) 아래의 로직에 맞게 코드를 작성하시오.
        1.1 i의 값이 장바구니의 크기보다 같거나 크면
            1.1.1 기존의 장바구니보다 2배 큰 새로운 배열을 생성한다.
            1.1.2 기존의 장바구니의 내용을 새로운 배열에 복사한다.
            1.1.3 새로운 장바구니와 기존의 장바구니를 바꾼다.
        1.2 물건을 장바구니(cart)에 저장한다. 그리고 i의 값을 1 증가시킨다.
    */
    if (i >= cart.length) {                               // (2)1.1
        Product[] temp = new Product[cart.length * 2];    // (2)1.1.1
        System.arraycopy(cart, 0, temp, 0, cart.length);  // (2)1.1.2
        cart = temp;                                      // (2)1.1.3
    }
    cart[i++] = p;                                        // (2)1.2
}

void summary() {
    /*
        (3) 아래의 로직에 맞게 코드를 작성하시오.
        1.1 장바구니에 담긴 물건들의 목록을 만들어 출력한다.
        1.2 장바구니에 담긴 물건들의 가격을 모두 더해서 출력한다.
        1.3 물건을 사고 남은 금액(money)를 출력한다.
    */
    int sum = 0;
    String cartList = "";

    for (int i = 0; i < cart.length; i++) {
        sum += cart[i].price;
        cartList += (i == 0) ? "" + cart[i] : ", " + cart[i];
    }
    System.out.println("구입한 물건 : " + cartList);  // (3)1.1
    System.out.println("사용한 금액 : " + sum);       // (3)1.2
    System.out.println("남은 금액 : " + money);       // (3)1.3
}

 

해설 : 

주어진 로직대로 코드를 구현할 수 있는 능력을 향상시키기 위한 문제이다.

이 문제가 쉽게 느껴지는 사람은 로직(주석)을 안보고 코드를 다시 작성해보기 바란다.

자바의 정석 책을 완독한 후 로직(주석)을 보지 않고 구현해봐야겠다. (배열을 복사할 수 있는 또 다른 방법 고민)

void summary() {
    String itemList = "";
    int sum = 0;

    for(int i = 0; i < cart.length; i++) {
        if(cart[i] == null)         // 유효성 검사
            break;
        itemList += cart[i] + ",";
        sum += cart[i].price;
    }

    System.out.println("구입한 물건 : " + itemList);  // 1.1
    System.out.println("사용한 금액 : " + sum);       // 1.2
    System.out.println("남은 금액 : " + money);       // 1.3
}

 

 

 

 

[7-20] 다음의 코드를 실행한 결과를 적으시오.

class Exercise7_20 {
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();

        System.out.println("p.x = " + p.x);
        p.method();

        System.out.println("c.x = " + c.x);
        c.method();
    }
}

class Parent {
    int x = 100;

    void method() {
        System.out.println("Parent Method");
    }
}

class Child extends Parent {
    int x = 200;
    
    void method() {
        System.out.println("Child Method");
    }
}

 

나의 답 : 

타입은 다르지만, 참조변수 p, c 모두 Child인스턴스를 참조하고 있다. (서로 같은 멤버들을 정의하고 있다.)

- 인스턴스 변수 : 참조변수의 타입에 따라 달라진다.(p = Parent, c = Child)

- 메서드 : 참조변수의 타입에 관계없이 항상 실제 인스턴스(Child클래스)에 정의된 메서드가 호출된다.

              : 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드) 호출

p.x = 100          // Parent클래스의 x
Child Method    // Child클래스의 method()
c.x = 200          // Child클래스의 x
Child Method    // Child클래스의 method()

 

 

 

 

[7-21] 다음과 같이 attack메서드가 정의되어 있을 때, 이 메서드의 매개변수로 가능한 것 두 가지를 적으시오.

interface Movable {
    void move(int x, int y);
}

void attack(Movable f) {
    /* 내용 생략 */
}

 

나의 답 : Movable 인터페이스, Movable 인터페이스를 구현한 클래스

 

해설 :

매개변수의 다형성을 잘 이해하고 있는지를 확인하는 문제이다.

매개변수의 타입이 인터페이스라는 게 어떤 의미인지 이해하는건 매우 중요하다.

null, Movable 인터페이스를 구현한 클래스 또는 그 자손의 인스턴스

 

 

 

 

[7-22] 아래는 도형을 정의한 Shape클래스이다.

이 클래스를 조상으로 하는 Circle클래스와 Rectangle클래스를 작성하시오.

이 때, 생성자도 각 클래스에 맞게 적절히 추가해야 한다.

 

(1) 클래스명     : Circle

     조상클래스  : Shape

     멤버변수     : double r - 반지름

 

(2) 클래스명     : Rectangle

     조상클래스  : Shape

     멤버변수     : double width - 폭

                          double height - 높이

     메서드        :

       1. 메서드명 : isSquare

           기       능 : 정사각형인지 아닌지를 알려준다.

           반환타입 : boolean

           매개변수 : 없음

abstract class Shape {
    Point p;

    Shape() {
        this(new Point(0, 0));
    }

    Shape(Point p) {
        this.p = p;
    }

    abstract double calcArea();  // 도형의 면적을 계산해서 반환하는 메서드

    Point getPosition() {
        return p;
    }

    void setPosition(Point p) {
        this.p = p;
    }
}

class Point {
    int x;
    int y;

    Point() {
        this(0, 0);
    }

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public String toString() {
        return "[" + x + "," + y + "]";
    }
}

 

나의 답 : 

1. Circle클래스의 calcArea() : 반지름×반지름×원주율(원의 넓이를 계산하는 공식)

2. Rectangle클래스의 calcArea() : 가로×세로(사각형의 넓이를 계산하는 공식)

3. isSquare () : 폭과 넓이의 길이가 0이 아니고, 두 길이가 서로 같을 때 true를 반환한다.

 3-1. 정사각형은 가로와 세로의 길이가 같아야 함.

class Circle extends Shape {
    double r;  // 반지름

    Circle(double r) {
        this(new Point(0, 0), r);
    }

    Circle(Point p, double r) {
        super(p);
        this.r = r;
    }

    double calcArea() {  // 1
        return r * r * Math.PI;
    }
}

class Rectangle extends Shape {
    double width;   // 폭
    double height;  // 높이

    Rectangle(double width, double height) {
        this(new Point(0, 0), width, height);
    }

    Rectangle(Point p, double width, double height) {
        super(p);
        this.width = width;
        this.height = height;
    }

    boolean isSquare() {  // 3
        return width != 0 && height != 0 && width == height;
    }

    double calcArea() {  // 2
        return width * height;
    }
}

 

 

 

 

[7-23] 문제7-22에서 정의한 클래스들의 면적을 구하는 메서드를 작성하고 테스트 하시오.

 

1. 메서드명 : sumArea

    기       능 : 주어진 배열에 담긴 도형들의 넓이를 모두 더해서 반환한다.

    반환타입 : double

    매개변수 : Shape[] arr

class Exercise7_23 {
    /*
        (1) sumArea메서드를 작성하시오.
    */

    public static void main(String[] args) {
        Shape[] arr = {new Circle(5.0), new Rectangle(3,4), new Circle(1)};
        System.out.println("면적의 합:" + sumArea(arr));
    }
}
// 면적의 합:93.68140899333463

 

나의 답 :

객체배열(arr) 객체들의 calcArea()를 호출하여 반환받은 면적을 total에 누적한 후 이를 반환한다.

Shape클래스의 추상메서드인 calcArea()를 호출해도 실제로는 각 인스턴스에 완전히 구현된 calcArea()가 호출된다.

static double sumArea(Shape[] arr) {
    double total = 0;

    for (int i = 0; i < arr.length; i++) {
        total += arr[i].calcArea();
    }
    return total;
}

 

 

 

 

[7-24] 다음 중 인터페이스의 장점이 아닌 것은?

a. 표준화를 가능하게 해준다.

b. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.

c. 독립적인 프로그래밍이 가능하다.

d. 다중상속을 가능하게 해준다.

e. 패키지간의 연결을 도와준다.

 

 

 

 

[7-25] Outer클래스의 내부 클래스 Inner의 멤버변수 iv의 값을 출력하시오.

class Outer {
    class Inner {
        int iv = 100;
    }
}

class Exercise7_25 {
    public static void main(String[] args) {
        /*
            (1) 알맞은 코드를 넣어 완성하시오.
        */
    }
}
// 100

 

나의 답 : 

내부 클래스(인스턴스 클래스)의 인스턴스를 생성하기 위해서는 먼저 외부클래스의 인스턴스를 생성해야한다.

인스턴스 클래스는 외부 클래스의 인스턴스 변수와 같으므로 외부 클래스의 인스턴스가 생성되어야 사용할 수 있다.

Outer ot = new Outer();
Outer.Inner ii = ot.new Inner();
System.out.println(ii.iv);

 

 

 

 

[7-26] Outer클래스의 내부 클래스 Inner의 멤버변수 iv의 값을 출력하시오.

class Outer {
    static class Inner {
        int iv = 200;
    }
}

class Exercise7_26 {
    public static void main(String[] args) {
        /*
            (1) 알맞은 코드를 넣어 완성하시오.
        */
    }
}
// 200

 

나의 답 : 

인스턴스 클래스와 달리 스태틱 클래스(static inner class)는 외부 클래스의 인스턴스를 생성하지 않고도 사용 가능하다.

static멤버를 인스턴스 생성없이 사용하는 것과 동일하다.

 

 

Outer.Inner si = new Outer.Inner();
System.out.println(si.iv);

 

 

 

 

[7-27] 다음과 같은 실행결과를 얻도록 (1)~(4)의 코드를 완성하시오.

class Outer {
    int value = 10;

    class Inner {
        int value = 20;
        void method1() {
            int value = 30;
            
            System.out.println(/* (1) */);
            System.out.println(/* (2) */);
            System.out.println(/* (3) */);
        }
    }  // Inner클래스의 끝
}      // Outer클래스의 끝

class Exercise7_27 {
    public static void main(String args[]) {
        /*
            (4) 알맞은 코드를 넣어 완성하시오.
        */
        
        inner.method1();
    }
}
// 30
// 20
// 10

 

나의 답 :

외부 클래스의 인스턴스 변수는 내부 클래스에서 '외부클래스이름.this.변수이름’로 접근할 수 있다.

(1) value
(2) this.value
(3) Outer.this.value
(4) Outer outer = new Outer();
   Outer.Inner inner = outer.new Inner();

class Outer {
    int value = 10;          // Outer.this.value

    class Inner {
        int value = 20;      // this.value
        void method1() {
            int value = 30;  // value
        }
    }  // Inner클래스의 끝
}      // Outer클래스의 끝

 

 

 

 

[7-28] 아래의 EventHandler를 익명 클래스(anonymous class)로 변경하시오.

import java.awt.*;
import java.awt.event.*;

class Exercise7_28 {
    public static void main(String[] args) {
        Frame f = new Frame();
        f.addWindowListener(new EventHandler());
    }
}

class EventHandler extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
        e.getWindow().setVisible(false);
        e.getWindow().dispose();
        System.exit(0);
    }
}

 

나의 답 :

import java.awt.*;
import java.awt.event.*;

class Exercise7_28 {
    public static void main(String[] args) {
        Frame f = new Frame();
        f.addWindowListener(new WindowAdapter(){  // new 조상클래스이름
            public void windowClosing(WindowEvent e) {
                e.getWindow().setVisible(false);
                e.getWindow().dispose();
                System.exit(0);
            }
        });
    }
}

 

 

 

 

[7-29] 지역 클래스에서 외부 클래스의 인스턴스 멤버와 static멤버에 모두 접근할 수 있지만,

지역변수는 final이 붙은 상수만 접근할 수 있는 이유 무엇인가?

 

나의 답 : 

지역변수는 메서드가 종료되면 함께 소멸된다.

메서드가 종료된 후 외부 클래스 객체가 지역 변수(소멸)를 참조할 수 없으므로 상수만 접근 가능하다.
상수는 constant pool에서 관리되므로 메서드가 종료되어도 사용 가능하다.

 

해설 :

메서드가 수행을 마쳐서 지역변수가 소멸된 시점에도,

지역 클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생할 수 있기 때문이다.

지역변수는 메서드가 종료되면 함께 사라지지만,

상수의 경우 이미 constant pool(상수를 따로 모아서 저장해 놓는 곳)에 저장되어 있기 때문에 사용할 수 있는 것이다.

예) 쓰레드 : main메서드가 종료된 이후에도 지역변수 VALUE의 값을 사용하고 있다.

import java.awt.*;
import java.awt.event.*;

class Exercise7_29 {
    public static void main(String[] args) {
        final int VALUE = 10;                    // 외부 클래스의 지역변수
        
        Thread t = new Thread(new Runnable() {   // 익명 클래스(내부 클래스)
            public void run() {
                for(int i = 0; i < 10; i++) {    // 10번 반복
                    try {
                        Thread.sleep(1 * 1000);  // 1초간 정지
                    } catch(InterruptedException e) {}
                
                    System.out.println(VALUE);   // 외부 클래스의 지역변수
                }
            } // run()
        });

        t.start(); // 쓰레드 시작
        System.out.println("main() - 종료.");
    } // main
}
// main() - . 종료
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10