본문 바로가기
개념

[JAVA의 정석] Chapter07_연습문제

by cook_code 2024. 12. 10.

 

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

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

단, 섯다카드는 1부터 10까지의 숫자가 적힌 카드가 한 쌍씩 있고,

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

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

 

1K,2,3K,4,5,6,7,8K,9,10,1,2,3,4,5,6,7,8,9,10,

 

class SutdaDeck {
    final int CARD_NUM = 20;
    SutdaCard[] cards = new SutdaCard[CARD_NUM];
    SutdaDeck() {
        for(int i=0;i < cards.length;i++) {
            int num = i%10+1;
            boolean isKwang = (i < 10) && (num==1||num==3||num==8);
            cards[i] = new SutdaCard(num,isKwang);
        }
    }
}
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]+",");
    }
}
 

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

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

 

1. 메서드명 : shuffle

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

반환타입 : 없음

매개변수 : 없음

 

2. 메서드명 : pick

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

반환타입 : SutdaCard

매개변수 : int index - 위치

 

3. 메서드명 : pick

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

반환타입 : SutdaCard

매개변수 : 없음

1K

7

 

2,6,10,1K,7,3,10,5,7,8,5,1,2,9,6,9,4,8K,4,3K,

2

 

class SutdaDeck2 {
    final int CARD_NUM = 20;
    SutdaCard2[] cards = new SutdaCard2[CARD_NUM];

    SutdaDeck2() {
        /*
         * (1) 배열 SutdaCard를 적절히 초기화 하시오.
         */
        for(int i=0; i<cards.length; i++) {
            int num = (i+1)%10; // num은 1~9까지 수
            if(num==0) num=10;	//num이 0일 경우 10으로 변환. num은 1~10까지의 수
            cards[i] = new SutdaCard2(num, (i<10)&&((num==1)||(num==3)||(num==8))); // 조건식 자체를 boolean으로 반환
        }
    }

    /*
     * (1) 위에 정의된 세 개의 메서드를 작성하시오.
     */
    void shuffle() {	
        int randomIdx = (int)(Math.random()*cards.length); //random은 0부터 카드개수-1까지 임의 정수 반환
        for(int i=0; i<cards.length; i++) {
            SutdaCard2 tmp = cards[i];	//임시 참조변수 tmp에 cards 배열의 i번째 요소 담기
            cards[i] = cards[randomIdx]; //cards[i]의 공간에 cards[랜덤인덱스] 담기
            cards[randomIdx] = tmp;
        }
    }

    SutdaCard2 pick(int index) {	
        return cards[index];
    }

    SutdaCard2 pick() {		
        int randomIdx = (int)(Math.random()*cards.length); // 사실 랜덤인덱스 지역변수는 인스턴스 변수로 빼면 중복을 줄일 수 있다.
        return cards[randomIdx];
    }

} // SutdaDeck

class SutdaCard2 {
    int num;
    boolean isKwang;

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

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

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

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

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

모범답안

class SutdaDeck2 {
    final int CARD_NUM = 20;
    SutdaCard2[] cards = new SutdaCard2[CARD_NUM];

    SutdaDeck2() {
        for (int i = 0; i < cards.length; i++) {
            int num = (i + 1) % 10;
            if (num == 0) num = 10;
            cards[i] = new SutdaCard2(num, (i < 10) && (num == 1 || num == 3 || num == 8));
        }
    }

    void shuffle() {
        for (int i = 0; i < cards.length; i++) {
            int randomIdx = (int) (Math.random() * cards.length);
            SutdaCard2 tmp = cards[i];
            cards[i] = cards[randomIdx];
            cards[randomIdx] = tmp;
        }
    }

    SutdaCard2 pick(int index) {
        return cards[index];
    }

    SutdaCard2 pick() {
        int randomIdx = (int) (Math.random() * cards.length);
        return cards[randomIdx];
    }
}

class SutdaCard2 {
    int num;
    boolean isKwang;

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

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

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

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

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

풀이

// SutdaDeck2 클래스
// 멤버 변수
final int CARD_NUM = 20;
SutdaCard2[] cards = new SutdaCard2[CARD_NUM];
/*
CARD_NUM: 카드의 총 개수를 나타내는 상수 (20장).
cards: 20장의 SutdaCard2 객체를 저장하는 배열.
*/

//생성자
SutdaDeck2() {
    for (int i = 0; i < cards.length; i++) {
        int num = (i + 1) % 10;
        if (num == 0) num = 10;
        cards[i] = new SutdaCard2(num, (i < 10) && (num == 1 || num == 3 || num == 8));
    }
}
// 1부터 10까지의 숫자를 가지는 카드 생성.
// isKwang이 true인 카드는 1, 3, 8 숫자를 가지는 첫 10장의 카드.

// 메서드
shuffle()

void shuffle() {
    for (int i = 0; i < cards.length; i++) {
        int randomIdx = (int) (Math.random() * cards.length);
        SutdaCard2 tmp = cards[i];
        cards[i] = cards[randomIdx];
        cards[randomIdx] = tmp;
    }
}
// 카드를 무작위로 섞음.
pick(int index)
SutdaCard2 pick(int index) {
    return cards[index];
}
// 특정 인덱스의 카드를 반환.
pick()
SutdaCard2 pick() {
    int randomIdx = (int) (Math.random() * cards.length);
    return cards[randomIdx];
}
// 무작위로 한 장의 카드를 반환.

// SutdaCard2 클래스
// 멤버 변수
int num;
boolean isKwang;
/*
num: 카드의 숫자.
isKwang: 카드가 광인지 여부.
*/

// 생성자
SutdaCard2() {
    this(1, true);
}

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

// 기본 생성자와 매개변수를 받는 생성자.
toString()
public String toString() {
    return num + (isKwang ? "K" : "");
}

// 카드의 숫자와 광 여부를 문자열로 반환.
// Sol_Exercise7_2 클래스

// main 메서드
public static void main(String args[]) {
    SutdaDeck2 deck = new SutdaDeck2();

    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));
}
// SutdaDeck2 객체를 생성하고, 카드를 선택, 섞기, 다시 선택하는 기능을 테스트.
 

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

 

오버라이딩(overriding)이란,

‘조상 클래스로부터 상속받은 메소드를 자손 클래스에 맞게 재정의 하는 것’을 말한다.

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


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

 

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

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

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

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

 

3,4

 

풀이

3. 접근 제어자는 조상의 메서드보다 좁은 범위로만 변경할 수 있다.
틀림. 오버라이딩된 메서드의 접근 제어자는 조상의 메서드보다 좁은 범위로 변경할 수 없음. 
접근 범위는 더 넓거나 동일해야 함. 예를 들어, 조상 메서드가 protected라면 오버라이딩된 메서드는 protected 또는 public이어야 함.

4. 조상의 메서드보다 더 많은 수의 예외를 선언할 수 있다.
틀림. 오버라이딩된 메서드는 조상의 메서드보다 더 많은 수의 예외를 선언할 수 없음. 
조상의 메서드가 선언한 예외의 일부 또는 그 예외들을 던져야 함. 추가적인 예외를 던질 수 없음.
 

참고

- 조상 클래스의 메소드를 자손 클래스에서 오버라이딩할 때

1. 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.

2. 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.

3. 인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.


[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();
	}
}
 

java: constructor Product in class Product cannot be applied to given types;
  required: int
  found: no arguments
  reason: actual and formal argument lists differ in length
 

기본 생성자가 없어 발생한 에러.

Product() {}를 추가해주어야 한다.


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

 

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

자손클래스의 인스턴스를 생성하면, 조상으로부터 상속받은 인스턴스변수들도 생성 되는데

이 상속받은 인스턴스변수들 역시 적절히 초기되어야 한다.

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

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

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


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

 

 

Child() → Child(int x) → Parent() → Parent(int x) → Object()

 

풀이

 

로그 찍어봤을 때 : Parent(x) -> Parent() -> Child(x) -> Child() -> getX(x) -> x=200

왜 답이랑 내가 로그 찍은거랑 호출 순서가 다를까?

ㄴ 문제: super()호출이 자동으로 삽입되었지만, 이를 명확히 로그에 나타내지 않았기 때문!

ㄴ 해결: 상위 클래스의 생성자 호출 순서를 명확히 확인할 수 있도록 super()호출을 명시.

 

자바에서는 다음과 같은 순서로 생성자가 호출된다.

  • 상위 클래스의 생성자 호출
  • 현재 클래스의 생성자 호출

 

1. Child c = new Child(); 호출

Child 클래스의 기본 생성자 Child() 호출

이 생성자는 this(1000);을 호출하여 Child(int x) 생성자로 이동

 

2. Child(int x) 호출

Child(int x) 생성자는 상위 클래스인 Parent의 기본 생성자를 호출하기 전에, 자신의 코드를 실행하기 시작

자식클래스의 생성자에 super()가 적혀있지 않으므로, 컴파일러가 자동으로 super()를 삽입.

생성자의 첫 번째 명령어가 super()이므로 Parent의 기본 생성자 Parent()가 호출됨.

Java에서는 모든 클래스의 생성자는

명시적으로 또는 암시적으로

상위 클래스의 생성자를 호출해야 함

 

만약 상위 클래스의 생성자 호출이

명시적으로 작성되지 않았다면,

컴파일러가 자동으로 super()를 삽입하여

상위 클래스의 기본 생성자를 호출.

출처 입력

 

3. Parent() 호출

Parent() 생성자는 this(200);을 호출하여 Parent(int x) 생성자로 이동

 

4. Parent(int x) 호출

Parent(int x) 생성자는 x를 200으로 설정하고 "Parent(x) -> "를 출력

Parent(int x) 생성자가 끝난 후 Parent() 생성자로 돌아옴

 

5. Parent()로 돌아옴

"Parent() -> "를 출력하고 Parent() 생성자가 끝남

 

6. Child(int x)로 돌아옴

this.x = x;를 통해 x를 1000으로 설정하고 "Child(x) -> "를 출력

Child(int x) 생성자가 끝난 후 Child() 생성자로 돌아옴

 

7. Child()로 돌아옴

"Child() -> "를 출력하고 Child() 생성자가 끝남

 

8. main 메서드로 돌아옴

 

9. c.getX() 호출

 

Parent 클래스에서 상속받은 getX() 메서드를 호출

"getX(x) -> "를 출력하고 x를 반환 (이 x는 Parent 클래스의 x로, 값은 200)

 

참고

생성자 호출 순서 로그 제대로 찍은 소스

class Parent {
	int x = 100;

	Parent() {
		this(200);
		System.out.print("Parent() -> ");
	}

	Parent(int x) {
		this.x = x;
		System.out.print("Parent(int x) -> ");
	}

	int getX() {
		System.out.print("getX() -> ");
		return x;
	}
}

class Child extends Parent {
	int x = 3000;

	Child() {
		this(1000);
		System.out.print("Child() -> ");
	}

	Child(int x) {
		super(); // 명시적으로 추가하여 호출 순서를 더 명확히 함
		this.x = x;
		System.out.print("Child(int x) -> ");
	}
}

public class Exercise7_7 {
	public static void main(String[] args) {
		System.out.println("Creating Child instance...");
		Child c = new Child();
		System.out.println("x=" + c.getX());
	}
}
 
Creating Child instance...
Parent(int x) -> Parent() -> Child(int x) -> Child() -> getX() -> x=200
 

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

 

① public-protected-(default)-private

② public-(default)-protected-private

③ (default)-public-protected-private

④ private-protected-(default)-public

 

1

풀이

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

private - 같은 클래스 내에서만 접근이 가능하다.

default - 같은 패키지 내에서만 접근이 가능하다.

protected - 같은 패키지 내 그리고 다른 패키지의 자손클래스에서 접근이 가능하다.

public - 접근 제한이 전혀 없다.


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

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

 

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

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

③ 메소드 - 오버로딩을 할 수 없다.

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

 

3

 

풀이 : final 메소드는 재정의는 안되지만 매개변수만 다르게 하는 것은 가능하다.

3. 메소드 - 오버로딩을 할 수 없다.
final 메서드는 오버라이딩(overriding)을 할 수 없지만, 오버로딩(overloading)은 가능함. 
오버라이딩은 서브 클래스에서 메서드의 구현을 재정의하는 것이고, 오버로딩은 같은 클래스 내에서 같은 이름의 메서드를 매개변수의 개수나 타입을 다르게 하여 여러 번 정의하는 것임. 
final은 오버라이딩을 방지하지만, 오버로딩에는 영향을 미치지 않음.
 

[7-10] MyTv2클래스의 멤버변수 isPowerOn, channel, volume을

클래스 외부에서 접근할 수 없도록 제어자를 붙이고

대신 이 멤버변수들의 값을 어디서나 읽고 변경할 수 있도록

getter와 setter메서드를 추가하라.

 

CH:10

VOL:20

 

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 boolean isPowerOn() {
		return isPowerOn;
	}

	public void setPowerOn(boolean powerOn) {
		isPowerOn = powerOn;
	}

	public int getChannel() {
		return channel;
	}

	public void setChannel(int channel) {
		this.channel = channel;
	}

	public int getVolume() {
		return volume;
	}

	public void setVolume(int volume) {
		this.volume = volume;
	}
}
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());
	}
}
 

인텔리제이 기준 alt+insert 누르면 생성자, 게터세터 등... 자동 완성 가능


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

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

 

메서드명 : gotoPrevChannel

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

반환타입 : 없음

매개변수 : 없음

 

CH:10

CH:20

CH:10

CH:20

 

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;

	// 이전 채널을 저장하는 변수
	private int prevChannel;

	// 이전 채널의 getter 메서드
	public int getPrevChannel() {
		return prevChannel;
	}

	// 이전 채널의 setter 메서드
	public void setPrevChannel(int prevChannel) {
		this.prevChannel = prevChannel;
	}

	// 전원 상태의 getter 메서드
	public boolean isPowerOn() {
		return isPowerOn;
	}

	// 전원 상태의 setter 메서드
	public void setPowerOn(boolean powerOn) {
		isPowerOn = powerOn;
	}

	// 현재 채널의 getter 메서드
	public int getChannel() {
		return channel;
	}

	// 현재 채널의 setter 메서드
	public void setChannel(int channel) {
		if (channel < MIN_CHANNEL || channel > MAX_CHANNEL) {
			return;
		}
		prevChannel = this.channel; // 현재 채널을 이전 채널에 저장
		this.channel = channel;
	}

	// 현재 볼륨의 getter 메서드
	public int getVolume() {
		return volume;
	}

	// 현재 볼륨의 setter 메서드
	public void setVolume(int volume) {
		if (volume < MIN_VOLUME || volume > MAX_VOLUME) {
			return;
		}
		this.volume = volume;
	}

	// 이전 채널로 이동하는 메서드
	public void gotoPrevChannel() {
		setChannel(prevChannel); // 현재 채널을 이전 채널로 변경
	}
}

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());
	}
}
 

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

 

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

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

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

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

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

 

답 : 지역변수는 접근 제어자 쓸 수 없음

3


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

 

Math 클래스는 유틸리티 클래스로 정적 메서드(static)와 상수만을 제공하므로 인스턴스 생성 불필요.

따라서 생성자가 private으로 선언되어 있음.

 

1. 객체 생성 방지. 메모리 사용 최적화 및 불필요한 인스턴스 생성 방지.

2. 클래스의 의도를 명확히 표현. 정적 메서드만을 제공하는 유틸리티 클래스임을 명확히 표현.

3. 기본 생성자의 자동 생성 방지. 클래스 외부에서 인스턴스 생성 불가.

 


[7-14] 문제 Q1에 나오는 섯다카드의 숫자와 종류(isKwang)는

사실 한번 값이 지정되면 변경되어서는 안 되는 값이다.

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

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

 

class SutdaCard {
    int num;
    final 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);
    }
}
 

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

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();
 

 

① u = (Unit)ac;

② u = ac;

③ GroundUnit gu = (GroundUnit)u;

④ AirUnit au = ac;

⑤ t = (Tank)u;

⑥ GroundUnit gu = t;

 

5

 

풀이

5. t = (Tank)u;

u는 현재 GroundUnit 타입의 객체를 참조하고 있기 때문에, 
이를 Tank 타입으로 형변환하려 하면 ClassCastException이 발생한다. 
u가 실제로 Tank 타입의 객체를 참조하고 있을 때만 Tank 타입으로 형변환이 가능.

----------------------------

1. u = (Unit)ac;
ac는 AirCraft 타입이고, AirCraft는 Unit의 하위 클래스.
상위 클래스 타입으로의 형변환은 자동으로 가능.
올바른 형변환.

2. u = ac;
ac는 AirCraft 타입이고, AirCraft는 Unit의 하위 클래스.
상위 클래스 타입으로의 형변환은 자동으로 가능.
올바른 형변환.

3. GroundUnit gu = (GroundUnit)u;
u는 현재 GroundUnit 타입의 객체를 참조하고 있음.
GroundUnit 타입으로의 형변환은 가능.
올바른 형변환.

4. AirUnit au = ac;
ac는 AirCraft 타입이고, AirCraft는 AirUnit의 하위 클래스.
상위 클래스 타입으로의 형변환은 자동으로 가능.
올바른 형변환.

6. GroundUnit gu = t;
t는 Tank 타입이고, Tank는 GroundUnit의 하위 클래스.
상위 클래스 타입으로의 형변환은 자동으로 가능.
올바른 형변환.
 

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

class Car {}
class FireEngine extends Car implements Movable {}
class Ambulance extends Car {}
 
FireEngine fe = new FireEngine();
 

① fe instanceof FireEngine

② fe instanceof Movable

③ fe instanceof Object

④ fe instanceof Car

⑤ fe instanceof Ambulance

 

5

 

풀이

5. fe instanceof Ambulance
fe는 FireEngine 타입의 객체이고, FireEngine은 Ambulance 클래스와 아무런 상속 관계가 없음.
false.

----------------------------

1. fe instanceof FireEngine
fe는 FireEngine 타입의 객체.
true.

2. fe instanceof Movable
fe는 FireEngine 타입의 객체이고, FireEngine은 Movable 인터페이스를 구현.
true.

3. fe instanceof Object
모든 객체는 Object 클래스의 인스턴스.
true.

4. fe instanceof Car
fe는 FireEngine 타입의 객체이고, FireEngine은 Car 클래스의 하위 클래스.
true.
 

[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() { /* 선택된 대상을 내린다.*/ }
}
 

  • 공통 부분
    • 멤버 변수 x, y
    • 메서드 move(int x, int y)
    • 메서드 stop()
  • Unit 클래스 정의
  • Marine, Tank, Dropship 클래스가 Unit 클래스를 상속받도록 변경
// 공통 부분을 포함하는 Unit 클래스 정의
class Unit {
    int x, y; // 현재 위치

    void move(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.println("Moved to position (" + x + ", " + y + ")");
    }

    void stop() {
        System.out.println("Stopped at position (" + x + ", " + y + ")");
    }
}

// Marine 클래스가 Unit 클래스를 상속받도록 변경
class Marine extends Unit { // 보병
    void stimPack() {
        System.out.println("Using stim pack");
    }
}

// Tank 클래스가 Unit 클래스를 상속받도록 변경
class Tank extends Unit { // 탱크
    void changeMode() {
        System.out.println("Changing attack mode");
    }
}

// Dropship 클래스가 Unit 클래스를 상속받도록 변경
class Dropship extends Unit { // 수송선
    void load() {
        System.out.println("Loading selected target");
    }

    void unload() {
        System.out.println("Unloading selected target");
    }
}

// 테스트 클래스
public class Main {
    public static void main(String[] args) {
        Marine marine = new Marine();
        Tank tank = new Tank();
        Dropship dropship = new Dropship();

        // 각 유닛 이동 및 정지 테스트
        marine.move(10, 20);
        marine.stop();
        marine.stimPack();

        tank.move(30, 40);
        tank.stop();
        tank.changeMode();

        dropship.move(50, 60);
        dropship.stop();
        dropship.load();
        dropship.unload();
    }
}
 

참고 : Exercise7_17 상속 관계도


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

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

 

메서드명 : action

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

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

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

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

반환타입 : 없음

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

 

답 : 메소드의 매개변수가 부모클래스이면, 그 자식 클래스들의 인스턴스를 모두 매개변수로 받을 수 있다.

class Exercise7_18 {
	// 로봇에 따라 움직임을 결정
	public static void action(Robot r) {
		/*
		* action메소드의 매개변수가 Robot타입이므로 Robot클래스의 자손클래스인 DanceRobot, SingRobot, DrawRobot의 인스턴스를 모두 매개변수로 가능하다.
		* */
		if(r instanceof DanceRobot) {
			DanceRobot dr = (DanceRobot)r;
			dr.dance();
		} else if(r instanceof SingRobot) {
			SingRobot sr = (SingRobot)r;
			sr.sing();
		} else if(r instanceof DrawRobot) {
			DrawRobot dr = (DrawRobot)r;
			dr.draw();
		}
	}

	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("그림을 그립니다.");
	}
}
 

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

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

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

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

 

1. 메서드명 : buy

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

장바구니(cart)에 담는다.

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

반환타입 : 없음

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

 

 

2. 메서드명 : add

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

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

반환타입 : 없음

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

 

 

3. 메서드명 : summary

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

반환타입 : 없음

매개변수 : 없음

 

잔액이 부족하여 Computer을/를 살수 없습니다.

구입한 물건:Tv,Computer,Tv,Audio,Computer,Computer,

사용한 금액:850

남은 금액:150

 

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메서드 호출)
		 */
        // 1.1 . 가진 돈과 물건의 가격을 비교해서 가진 돈이 적으면 메소드를 종료한다
        if (money < p.price) {
            System.out.println("잔액이 부족하여" + p + " 을/를 살 수 없습니다.");
            return;
        }
//1.2 가진 돈이 충분하면, 제품의 가격을 가진 돈에서 빼고
        money -= p.price;
//1.3 장바구니에 구입한 물건을 담는다 (add메소드 호출)
        add(p);
    }

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

    void summary() {
		/*
	(3) 아래의 로직에 맞게 코드를 작성하시오.
	1.1 장바구니에 담긴 물건들의 목록을 만들어 출력한다.
	1.2 장바구니에 담긴 물건들의 가격을 모두 더해서 출력한다.
	1.3 물건을 사고 남은 금액(money)를 출력한다.
		 */
        String itemList = "";
        int sum = 0;
        for (int i = 0; i < cart.length; i++) {
            if (cart[i] == null)
                break;
            // 1.1 장바구니에 담긴 물건들의 목록을 만들어 출력한다.
            itemList += cart[i] + ",";
            // 1.2 장바구니에 담긴 물건들의 가격을 모두 더해서 출력한다.
            sum += cart[i].price;
        }
// 1.3 물건을 사고 남은 금액 (money)를 출력한다.
        System.out.println("구입한 물건:" + itemList); //구입한 물건
        System.out.println("사용한 금액:" + sum); //사용한 금액
        System.out.println("남은 금액:" + 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";
    }
}
 

[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.x = 100
Child Method
c.x = 200
Child Method

Process finished with exit code 0
 

풀이

  • 필드 접근: 컴파일 타임에 변수의 타입을 기준으로 함.
  • 메서드 호출: 런타임에 객체의 실제 타입을 기준으로 함 (동적 바인딩).
// 1. 객체 생성 및 변수 할당
Parent p = new Child();
Child c = new Child();
/*
Parent 타입 변수 p에 Child 객체를 할당.
Child 타입 변수 c에 Child 객체를 할당.
p는 Parent 타입이지만, 실제로는 Child 객체를 참조.
*/

// 2. 필드 접근
System.out.println("p.x = " + p.x);
/*
p.x는 Parent 클래스의 x 필드를 참조하므로 100이 출력.
필드 접근은 변수의 타입을 기준으로 함. p는 Parent 타입이므로 Parent 클래스의 x 필드를 참조.
*/
System.out.println("c.x = " + c.x);
/*
c.x는 Child 클래스의 x 필드를 참조하므로 200이 출력.
c는 Child 타입이므로 Child 클래스의 x 필드를 참조.
*/
3. 메서드 호출
p.method();
/*
p는 Parent 타입이지만, 실제로는 Child 객체를 참조.
메서드 호출은 객체의 실제 타입을 기준으로 함. 따라서 Child 클래스의 method()가 호출되어 Child Method가 출력.
*/
c.method();
/*
c는 Child 타입이고, 실제로도 Child 객체를 참조.
Child 클래스의 method()가 호출되어 Child Method가 출력.
*/
 

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

// 틀만 있고 실체가 없음!
interface Movable {
	void move(int x, int y);
}
void attack(Movable f) {
	/* 내용 생략 */
}
 

 

null, Movable 인터페이스를 구현한 클래스 또는 그 자손의 인스턴스만 매개변수로 사용이 가능하다.


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

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

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

 

(1) 클래스명 : Circle

조상클래스 : Shape

멤버변수 : double r - 반지름

 

(2) 클래스명 : Rectangle

조상클래스 : Shape

멤버변수 : double width - 폭

double height - 높이

메서드 :

1. 메서드명 : isSquare

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

반환타입 : boolean

매개변수 : 없음

 

1. 조상 클래스 정의:

Shape 클래스는 공통 속성과 메서드를 정의하고, calcArea()라는 추상 메서드를 선언.

Shape 클래스는 Point 객체를 통해 도형의 위치를 관리.

추상 메서드는 구체적인 도형 클래스에서 반드시 구현하도록 강제.

 

2. 상속받는 클래스 정의:

Rect 클래스와 Circle 클래스는 Shape 클래스를 상속받아 정의.

extends Shape를 사용하여 Shape 클래스를 상속.

상속받은 추상 메서드 calcArea()를 각각의 클래스에서 구현.

Rect 클래스는 너비와 높이를 이용해 면적을 계산하고, Circle 클래스는 반지름을 이용해 면적을 계산.

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

class Rect extends Shape {
    double width;
    double height;

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

    Rect(Point p, double width, double height) {
        super(p); // 조상의 멤버는 조상의 생성자가 초기화하도록 한다.
        this.width = width;
        this.height = height;
    }

    boolean isSquare() {
        // width나 height기 0이 아니고 width와 height가 같으면 true를 반환한다.
        return width * height != 0 && width == height;
    }

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

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

    Circle(double r) {
        this(new Point(0, 0), r); // Circle(Point p, double r)를 호출
    }

    Circle(Point p, double r) {
        super(p); // 조상의 멤버는 조상의 생성자가 초기화하도록 한다.
        this.r = r;
    }

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

참고 : Exercise7_22 상속 관계도


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

1. 메서드명 : sumArea

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

반환타입 : double

매개변수 : Shape[] arr

 

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

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() {
        // width나 height기 0이 아니고 width와 height가 같으면 true를 반환한다.
        return width * height != 0 && width == height;
    }

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

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

    Circle(double r) {
        this(new Point(0, 0), r); // Circle(Point p, double r)를 호출
    }

    Circle(Point p, double r) {
        super(p); // 조상의 멤버는 조상의 생성자가 초기화하도록 한다.
        this.r = r;
    }

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

class Exercise7_23 {
    static double sumArea(Shape[] arr) {
        double sum = 0;

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

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

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

 

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

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

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

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

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

 

5


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

 

100

class Outer {
	class Inner {
		int iv=100;
	}
}
class Exercise7_25 {
	public static void main(String[] args) {
		Outer o = new Outer();
		// 한번더 생성 및 멤버변수 호출
		int iv = o.new Inner().iv;
		System.out.println(iv);
	}
}
 

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

 

static으로 선언된 이너클래스는 인스턴스 클래스와 달리 외부 클래스의 인스턴스를 생성하지 않고도 사용가능

class Outer {
	static class Inner {
		int iv=200;
	}
}
class Exercise7_26 {
	public static void main(String[] args) {
		Outer.Inner inner = new Outer.Inner();
		System.out.println(inner.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

 

class Outer {
	int value=10;
	class Inner {
		int value=20;
		void method1() {
			int value=30;
			System.out.println(value);
			System.out.println(new Inner().value);
			System.out.println(new Outer().value);
		}
	} // Inner클래스의 끝
} // Outer클래스의 끝
class Exercise7_27 {
	public static void main(String args[]) {
		Outer outer = new Outer();
		Outer.Inner inner = outer.new Inner();
		inner.method1();
	}
}
 

[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() {
            public void windowClosing(WindowEvent e) {
                e.getWindow().setVisible(false);
                e.getWindow().dispose();
                System.exit(0);
            }
        });
    }
}
/*
// 상속 활용
class EventHandler extends WindowAdapter
{
    public void windowClosing(WindowEvent e) {
        e.getWindow().setVisible(false);
        e.getWindow().dispose();
        System.exit(0);
    }*/
 

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

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

 

1. 생명 주기 차이

지역 변수: 메서드 호출 시 생성되고, 메서드 종료 시 소멸되는 생명 주기를 가짐. 즉, 메서드 실행 중에만 존재.

지역 클래스: 메서드 내에서 정의되지만, 인스턴스가 메서드 종료 후에도 존재할 수 있음. 따라서, 지역 클래스의 인스턴스가 지역 변수를 참조하면, 해당 메서드가 종료된 후에도 지역 변수를 참조할 수 있어야 함.

 

2. 메모리 관리와 안정성

지역 클래스가 지역 변수를 참조하는 경우, 지역 변수를 "상수화"하여 복사본을 생성. 이를 통해 메모리 누수를 방지하고, 참조의 일관성을 유지.

만약 지역 변수가 final이 아니라면, 메서드 종료 후 지역 클래스가 참조할 때 변경된 값을 참조해야 할 가능성이 생기고, 이는 메모리 관리와 참조 일관성 측면에서 문제가 될 수 있음.

 

3. 불변성 보장

지역 변수를 final로 선언하면, 해당 변수가 메서드 내에서 변경되지 않음을 보장할 수 있음. 이는 컴파일러가 메모리 관리를 효율적으로 할 수 있도록 도움.

final로 선언된 지역 변수는 지역 클래스에서 안전하게 읽을 수 있으며, 변경되지 않으므로 데이터 일관성을 유지할 수 있음.

 

결론

지역 클래스에서 지역 변수를 final로 선언된 상수만 접근할 수 있는 이유는 메모리 관리와 데이터 일관성을 보장하기 위해서이다.

반응형