자바의 정석 기초(7) 연습문제 : OOP편

NtoZ·2023년 3월 5일
0

Java

목록 보기
15/23
post-thumbnail

자바의 정석 Chapter7 문제풀이


💡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;	//광인지 판별

	//생성자. 번호1, 광은 true로 기본생성
	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_l {
	public static void main(String args[]) {
    	//덱 인스턴스 생성하여 참조변수에 주소 담기
		SutdaDeck deck = new SutdaDeck();
        //i는 카드의 인덱스 번호를 순회하며 printf함.
		for (int i = 0; i < deck.cards.length; i++)
			System.out.print(deck.cards[i] + ",");
	}
}
  • 풀이 접근 : class SutdaDeck에서 주어진 조건에 따라 적절한 배열을 만드는 문제다. 카드 개수는 20개인데 섯다 카드는 1~10의 임의의 수가 한 쌍으로 이뤄져 있으므로 한 쌍을 묶어주는 작업이 필요할 것이다. (예를 들어 0,1 / 2,3 / ... / 순으로 ) 게다가 그 중 카드의 숫자가 1, 3, 8 중의 하나면 둘 중의 한 장은 '광'이 true이어야 한다. 조건문 switch문을 활용할 수 있을 것이다.
    1~10의 숫자를 가진 카드 인스턴스를 각각 2쌍이 되도록 만들고
    1,3,8 숫자의 경우 그 중 한 장의 kwang 값을 true로 변경해주자. (혹은 1,3,8이 아닐 때 false를 기본 값으로 두면 된다.) 일단 객체 배열을 만드는 것에서부터 시작하자.

  • 풀이 오류 : for문의 조건식 i를 이용하는 과정에서 무한루프에 빠지는 오류가 발생했다. 왜냐하면 i값을 카드의 num으로 삼으로고 했기 때문에 i=i%10으로 수행 코드를 작성했기 때문이다. 그럴 때는 i가 아닌 로컬 변수 하나 (예를 들어 num=1)를 만들어 지정해 주는 것이 안정적이다.

<내풀이>
package effort;

//카드 덱(무더기)를 나타낸다.
class SutdaDeck {
	final int CARD_NUM = 20;
	SutdaCard[] cards = new SutdaCard[CARD_NUM];

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

		SutdaCard card = new SutdaCard();
				//i는 카드번호로 둘 것이므로 1부터 시작. but 카드 개수 맞추기위해 '<='사용
		for(int i=1; i<=cards.length; i++) {
			if(i%10==0) {
				card = new SutdaCard(10, false);
				cards[i-1] = card;
				continue;
			}			
			
			//기본적으로 숫자가 i이고 광이 false인 카드 인스턴스를 생성하여 객체 배열에 추가한다.
			//카드 숫자로는 1부터 10까지만 올 수 있기때무네 i%10. 단, i%10이 0이 될 때를 해결해야하는데.
			card = new SutdaCard(i%10, false);
			
			//또한 앞카드 세트 중 1, 3, 8을 골라서 '광'값을 true로 변경해주어야한다. (뒤카드를 고르려면 i>10으로 두면된다.)
			if((i<=10)&&(card.num==1||card.num==3||card.num==8)) {
				card.isKwang=true;
			}
			
			//인덱스는 0부터 시작하므로 i-1
			cards[i-1] = card;🤪
			
			
			
		}
	}
}

//카드 클래스. 설계도를 만들어야한다.
class SutdaCard {
	int num;
	boolean isKwang;	//광인지 판별

	//생성자. 번호1, 광은 true로 기본생성
	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 PNote {
	public static void main(String args[]) {
		//덱 인스턴스 생성하여 참조변수에 주소 담기
		//섯다덱 생성자에 카드 인스턴스 20장을 만들어 배열을 초기화하는 조건이 있다.
		SutdaDeck deck = new SutdaDeck();
		
      //i는 카드의 인덱스 번호를 순회하며 printf함.
		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,
  • 모범답안
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);
  }
}
  • 내 답안 반성하기 :
  1. 💡SutdaCard card = new SutdaCard(); : for문 밖에서 for문의 요소에 해당하는 객체를 만들어주기 위하여 별도의 객체를 생성했다.
    그러나 cards[i]=new SutdaCard(숫자,광)을 통하여 인스턴스를 바로 SutdaCard배열에 집어넣을 수 있었다. 심지어 그렇게 풀었는데... 결국 저 인스턴스는 의미없는 코드가 되어 GC가 처리하게 되었다.
  2. i%10이 0이면 카드의 숫자를 10으로 설정했던 부분.
    이 if문 코드로 세 줄씩이나 낭비할 이유가 없었다. i%10+1을 사용한다면 나머지가 0이 나와도 1로써 변환된다. 이를 num이라는 로컬 변수 안에 저장하는 것이 더 효율적인 방법일 것이다.
  3. 💡boolean isKwang = (i < 10)&&(num==1||num==3||num==8);을 생각하지 못하고 이미 false로 만들어진 card 객체에서 앞부분만 true로 바꾸려고 했던 것이 아쉬웠다. *또한, AND(&&)가 OR(||)보다 우선순위가 높기 때문에 괄호를 꼭 사용해야 한다.
  • ⭐➡️ 객체 배열 요소끼리 일정한 규칙을 보인다면,
    해당 인스턴스를 만들고 난 이후 그 객체에 접근하여 인스턴스 변수를 수정하는 것보다 애초에 배열할 객체를 생성할 때 생성자를 이용한 초기화를 먼저 생각하는 것이 훨씬 바람직하다!
  • ⭐💡 boolean 타입 변수의 경우 조건식 자체를 R밸류로 넘겨주면 해당 조건식일 때 그 변수의 리터럴이 true로 바뀌게 된다.

💡7-2. 연습문제7-1의 클래스에 다음 정의된 새로운 메서드 추가하고 테스트하기

  • 조건:
  1. 메서드명 : shuffle
    • 기 능 : 배열 cards에 담긴 카드의 위치를 뒤섞는다. (Math.random()사용)
    • 반환타입 : 없음
    • 매개변수:없음
  2. • 메서드명 : pick
    • 기 능 : 배열 cards에서 지정된 위치의 SutdaCardfi 반환한다.
    • 반환타입 : 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) 위에 정의된 세 개의 메서드를 작성하시오.
  */
} // SutdaDeck

class SutdaCard {
int num;
boolean isKwang;
  • 풀이 접근 :
    - void shuffle 같은 경우에는 Math.random()을 사용하여 임시 번호 tmp 를 추가하여 물건 뒤바꾸기를 진행해주면 된다.
    - SutdaCard pick(int index)같은 경우에는 SutdaCard 참조변수 하나에 cards[index]의 주소값을 저장하여 return하면 될 것이다.
    - SutdaCard pick()같은 경우에는 cards[(int)(Math.random()*20)]의 요소를 반환해주면 될 것이다.

  • 내 풀이
package effort;

//카드 덱(무더기)를 나타낸다.
class SutdaDeck {
	final int CARD_NUM = 20;
	SutdaCard[] cards = new SutdaCard[CARD_NUM];

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

		for(int i=0; i<cards.length; i++) {
			int cnum = i%10+1;
			boolean isKwang = (i<10)&&(cnum==1||cnum==3||cnum==8);
			cards[i] = new SutdaCard(cnum, isKwang);
			
		}
	}
	//⭐
	void shuffle() {
		
		for(int i=0; i<cards.length; i++) {
			int random = (int)(Math.random()*cards.length);	//random은 0~19
			
			SutdaCard tmpCard = cards[i];
			cards[i] = cards[random];
			cards[random] = tmpCard;
			
		}
	}
	//⭐
	SutdaCard pick(int index) {
		return cards[index];
	}
	//⭐
	SutdaCard pick() {
		return cards[(int)(Math.random()*cards.length)]; //0~(카드숫자-1)까지의 임의 값 반환
	}
	
}

//카드 클래스. 설계도를 만들어야한다.
class SutdaCard {
	int num;
	boolean isKwang;	//광인지 판별

	//생성자. 번호1, 광은 true로 기본생성
	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 PNote {
	public static void main(String args[]) {
		//Card배열 생성 후 deck에 주소값 저장
		SutdaDeck deck = new SutdaDeck();
		//deck의 0번째 요소 뽑아오기 (1k)
		System.out.println(deck.pick(0));
		//deck의 임의의 요소 뽑아오기
		System.out.println(deck.pick());
		//deck 섞기
		deck.shuffle();
		//deck 내용 출력하기
		for (int i = 0; i < deck.cards.length; i++)
		System.out.print(deck.cards[i] + ",");
		System.out.println();
		//0번째 deck 뽑기
		System.out.println(deck.pick(0));
		
	}
}
  • 모범 답안
void shuffle() {
for(int i=0; i<cards.length;i++) {
int j = (int)(Math.random()*cards.length);
// cards[i] cards[j] . 와 의 값을 서로 바꾼다
SutdaCard tmp = cards[i];
cards[i] = cards[j];
cards[j] = tmp;
}
}

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

SutdaCard pick() {
int index = (int)(Math.random()*cards.length);
return pick(index); // pick(int index) . 를 호출한다
}
  • 💡반성할 점 : 어떤 함수에 매개변수가 존재할 때는 ⭐ int값으로 0~19의 값이 아니라 유효하지 않은 값이 들어올 수 있다. if(index < 0 || index >= CARD_NUM) 조건에서는 return null해줘야 한다.
    메서드를 생성할 때는 유효성 검사가 필요한 상황인지 항상 염두에 두고 있자!

💡7-3. 다음 코드에서 에러가 발생하는 이유?

  • 예제
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_3 {
	public static void main(String[] args) {
		Tv t = new Tv();
	}
}
  • 문제 접근 : Tv는 Product 클래스를 상속받는다. 이는 Product의 생성자, 초기화 블럭을 제외한 멤버(멤버변수, 메서드)를 상속받는다는 말이다. 그렇다면 Tv 클래스는 int priceint bonusPoint가 자동적으로 존재하게 된다는 말이다. Product 클래스와 Tv클래스에서 특별히 이상한 점을 찾아보기 어렵다. (Tv() { }는 기본생성자를 왜 굳이 다시 선언해 놓았을까 의문이긴하지만...)
    문제는 Exercise7_3 클래스에서 Tv 객체를 생성하는 것에서 발생했을 확률 이 높다.

위의 코드에서 Product 클래스에는 생성자가 정의되어 있지만, Tv 클래스에는 생성자가 정의되어 있지 않습니다. 이 경우, ⭐Tv 클래스가 Product 클래스를 상속받았기 때문에 Tv 클래스의 인스턴스를 생성할 때, 상위 클래스인 Product 클래스의 기본 생성자가 자동으로 호출됩니다. 하지만 Product 클래스에는 인자를 받지 않는 기본 생성자가 정의되어 있지 않으므로, Tv 클래스의 인스턴스를 생성할 수 없습니다..

해결 방법으로는, Tv 클래스에 생성자를 추가하여 해결할 수 있습니다. Tv 클래스의 생성자에서는 상위 클래스인 Product 클래스의 생성자를 호출하는 super() 메서드를 호출하여 인스턴스 변수를 초기화해야 합니다.
그리고 Exercise7_3 클래스에서 Tv 인스턴스를 생성할 때, 인자로 가격(price)를 전달해주어야 합니다.

class Tv extends Product {
  Tv(int price) {	//⭐
    super(price);	//⭐ super()가 아닌 super(price)를 호출하게함
  }

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

class Exercise7_3 {
  public static void main(String[] args) {
    Tv t = new Tv(1000);	//⭐
  }
}
  • 💡⭐ 반성할 점:
    자손 클래스의 인스턴스를 생성할 때 자동으로 조상 클래스의 기본 생성자를 호출한다는 사실을 생각하지 못했다. 자손 클래스 생성자에는 보이지 않는 super()이 생략되어 있는 것이다. 이는 컴파일러가 추후 보충한다. 그런데 해당 예제에는 super()는 존재하지 않는다. 이미 super(int price)가 존재하기 때문에 컴파일러가 기본 생성자를 제작할 필요가 없는 것이다.
    자손 생성자Tv()는 존재하지만 super()는 존재하지않으므로, super()를 호출할 수 없게된 것이 컴파일러 에러의 원인이다.
    따라서 ❶조상 클래스의 기본 생성자 Product(){ }를 생성해주거나 Tv(int price)로 매개변수를 변경해주고 ❷ super()가 아닌 super(int price)를 호출하게 하는 것이 필요하다.
  • 해설 : Tv , Tv() Tv() 클래스의 인스턴스를 생성할 때 생성자 가 호출되고 는 조상 생성자 super() . super() 를 호출한다 실제 코드에서는 를 호출하는 곳이 없지만 컴파일러가 자동적으로 추가해 준다. Product Product(int price)라는 생성자가 정의되어 있기 때문에 컴파일러가 자동적으로 추가해 주지도 않으므로 직 접 클래스에 를 넣어주면 문제가 해결된다. Product(){}를 추가해준다.
<다른 답안>
class Product
{
int price; // 제품의 가격
int bonusPoint; // 제품구매 시 제공하는 보너스점수
Product() {}	//⭐ 기본생성자 추가
Product(int price) {
this.price = price;
bonusPoint =(int)(price/10.0);
}
}
class Tv extends Product {
Tv() {}
public String toString() {
return "Tv";
}
}
class Exercise7_3 {
public static void main(String[] args) {
Tv t = new Tv();
}
}

7-4. 외부에서 접근할 수 없도록 접근제어를 붙이고 getter와 setter 추가하기

  • 조건 : MyTv클래스의 멤버변수 isPowerOn, channel, v이ume을 클래스 외부에서 접근할 수 없도록 제어자를 붙이고 대신 이 멤버변수들의 값을 어디서나 읽고 변경할 수 있도록 getter와 setter메서드를 추가하시오.
class MyTv {
	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_4 {
  public static void main(String args[]) {
    MyTv t = new MyTv();

    t.setChannel(10);
    System.out.println("CH:" + t.getChannel());
    t.setVolume(20);
    System.out.println("VOL:" + t.getVolume());
  }
}
  • 풀이 접근 : 클래스 외부에서 접근하지 못하게 하는 접근 제어자는 private이다. 대신 해당하는 값을 확인할 수 있도록 getter를, 메서드를 호출하는 곳에서 매개변수를 받아 유효성 겁사 후 변경할 수 있도록하는 setter를 public으로 추가한다.
package effort;

class MyTv {
	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;
	/*
	 * (1) 알맞은 코드를 넣어 완성하시오.
	 */
    int getChannel() {
    	return this.channel;
    }
    
    void setChannel(int num) {
    	if(num>=MIN_CHANNEL&&num<=MAX_CHANNEL) {
    		this.channel=num;
    	}
    	else {
    		System.out.println("채널은"+MIN_CHANNEL+"부터"+MAX_CHANNEL+"까지 입니다.");
    		return;
    	}
    }
    
    int getVolume() {
    	return this.volume;
    }
    
    void setVolume(int num) {
    	if(num>=MIN_VOLUME&&num<=MAX_VOLUME) {
    		this.volume=num;
    	}
    	else {
    		System.out.println("볼륨은"+MIN_VOLUME+"부터"+MAX_VOLUME+"까지 입니다.");
    		return;
    	}
    }
    
}

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

    t.setChannel(10);
    System.out.println("CH:" + t.getChannel());
    t.setVolume(20);
    System.out.println("VOL:" + t.getVolume());
  }
}
<결과>
CH:10
VOL:20
  • ⭐매개변수가 있는 메서드는 반드시 작업 전에 넘겨받은 값의 유효성 검사를 해야한다!

7-5. 연습문제 7-4 관련 이전 채널(previous channel)로 이동하는 기능의 메서드 추가

  • 조건:
    메서드명 : gotoPrevChannel
    기 능 : 현재 채널을 이전 채널로 변경한다.
    반환타입 : 없음
    매개변수 : 없음
class MyTv2 {
/*
(1) 연습문제7-4의 MyTv2클래스에 gotoPrevChannel메서드를 추가하여 완성하시오.
*/
}
class Exercise7_5 {
  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
  • 풀이 접근 : 힌트 대로 이전 채널을 저장하는 멤버 변수를 하나 만들 것이다. 이 때 멤버 변수의 유형은 각 인스턴스별로 독립된 공간을 가져야 하니 인스턴스 변수가 올바르다. int channel;로 이미 현재 채널에 대한 정보가 있으므로, int prevCh을 만들어 바꾸기 전 채널을 기록하면 될 것이다. 채널 변경에 관한 모든 메서드에 prevCh = this.channel을 선언하고 난 이후 변경이 이루어지도록 하면 된다. 다행히 setChannel(int num)에서 prevCh =this.channel; //채널변경 전 현재 채널을 기록한다.를 추가해주면 되겠다. (🤔그런데 이 방법을 사용하면 채널 변경 메서드가 추가되면 무조건 해당 코드를 추가해줘야 하는데 자동화 방법은 없을까?)

  • 내 풀이:

package effort;

class MyTv2 {
	private boolean isPowerOn;
	private int channel;
	private int volume;
	private int prevCh;	//⭐이전채널
    
    final int MAX_VOLUME=100;
    final int MIN_VOLUME = 0;
    final int MAX_CHANNEL=100;
    final int MIN_CHANNEL = 1;
	/*
	 * (1) 알맞은 코드를 넣어 완성하시오.
	 */
    int getChannel() {
    	return this.channel;
    }
    
    void setChannel(int num) {
    	if(num>=MIN_CHANNEL&&num<=MAX_CHANNEL) {
    		prevCh =this.channel; //⭐채널변경 전 현재 채널을 기록한다.
    		this.channel=num;
    	}
    	else {
    		System.out.println("채널은"+MIN_CHANNEL+"부터"+MAX_CHANNEL+"까지 입니다.");
    		return;
    	}
    }
    
    int getVolume() {
    	return this.volume;
    }
    
    void setVolume(int num) {
    	if(num>=MIN_VOLUME&&num<=MAX_VOLUME) {
    		this.volume=num;
    	}
    	else {
    		System.out.println("볼륨은"+MIN_VOLUME+"부터"+MAX_VOLUME+"까지 입니다.");
    		return;
    	}
    }
    
    void gotoPrevChannel() {
    	setChannel(prevCh);
    }
    
}

class Note {
  public static void main(String args[]) {
	  MyTv2 t = new MyTv2();
	    
	    t.setChannel(15);
	    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:15
CH:20
CH:15
CH:20
  • 풀이과정, 답 정확하게 일치

7-6. Outer 클래스의 내부 클래스 inner의 멤버변수 iv의 값을 출력하시오.

  • 예제
class Outer {
  class Inner {
  	int iv = 100;
  }
}

class Exercise7_6 {
  public static void main(String[] args) {
    /*
    (1) 알맞은 코드를 넣어 완성하시오.
    */
  }
}
  • 풀이접근 : Outer 클래스 외부에서 Outer 클래스의 인스턴스 내부 클래스에 접근하려면 Outer의 객체를 생성해야 한다.
    Outer o = new Outer(); 그리고 나서
    o.Inner i = o.new Inner();로 인스턴스이너클래스의 인스턴스를 생성한다. i.iv를 sysout으로 출력해주면 끝!

  • 💡접근 오류 : o.Inner i = o.new Inner();는 불가능하다. 좌밸류의 타입이 o.Inner가 아니라 Outer.Inner가 타입이 되어야 한다.

    o.Inner는 Inner 클래스의 정적인 멤버가 아니기 때문에, Outer 클래스의 인스턴스 o를 통해서만 접근할 수 있습니다. 따라서, 올바른 접근 방법은 Outer.Inner i = o.new Inner(); 입니다. 이렇게 하면 Outer 클래스에서 Inner 클래스를 참조할 수 있고, o.new Inner()를 통해 Inner 클래스의 인스턴스를 생성할 수 있습니다.

  • ⭐ 이 개념에 대하여:
    인스턴스 내부클래스 Inner는 외부 클래스 Outer의 인스턴스 멤버라고 할 수 있다. 인스턴스 멤버는 인스턴스 생성 후에 접근이 가능하므로 Outer인스턴스 생성 이후 접근하는 방식이 자연스러운 것이다.

  • 💡참고 : o.new Inner()에 대하여

    o.new Inner()는 외부 클래스(Outer)의 인스턴스(o)를 통해 내부 클래스(Inner)의 인스턴스를 생성하는 방법입니다.

    내부 클래스는 외부 클래스의 인스턴스와 강하게 결합되어 있기 때문에, 내부 클래스의 인스턴스를 생성하려면 반드시 외부 클래스의 인스턴스가 먼저 생성되어 있어야 합니다.

    그래서 o.new Inner()에서 o는 Outer 클래스의 인스턴스를 가리키고 있으며, new Inner()는 Inner 클래스의 인스턴스를 생성하는 것입니다. new 연산자를 사용하여 Inner 클래스의 인스턴스를 생성하고, 이를 o 인스턴스의 멤버로서 참조할 수 있게 됩니다.


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

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

class Exercise7_7 {
public static void main(String[] args) {
      /*
      (1) 알맞은 코드를 넣어 완성하시오.
      */
	}
}
  • 풀이 접근 : 스태틱 내부 클래스는 외부클래스의 인스턴스 생성과는 별개로 Outer클래스가 메모리에 로딩될 때 함께 생성된다. 따라서 Outer클래스의 인스턴스를 생성하지 않고 바로 사용할 수 있다.
    Outer.Inner.iv를 sysout을 통해 출력한다.

  • 💡오개념 : Outer.Inner.iv를 sysout에서 바로 접근할 수는 없다. 왜냐하면 Inner클래스가 스태틱 클래스인 것과 별개로 iv는 해당 클래스의 인스턴스 변수이기 때문이다. 따라서 Outer.Inner i = new Outer.Inner()을 생성하고, i.iv를 통해 접근한다.

package effort;

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

class Note {
  public static void main(String args[]) {
	Outer.Inner i = new Outer.Inner();
	System.out.println(i.iv);
  }
}
<결과>
200

7-8. 결과에 부합하는 코드 작성하기

package effort;

class Outer {
	int value = 10;

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


class Note {
  public static void main(String args[]) {
	/*
	 *  (4) 알맞은 코드를 넣어 완성하십시오.
	 * */
	inner.method1();  
	  
  }
}
<나와야 하는 결과>
30
20
10
  • 풀이 접근 : 내부클래스의 인스턴스 메서드 void method1에서 외부의 변수들에 접근하는 방식을 설명하기 위한 예제이다.
    인스턴스 메서드 안의 int value=30;으로 선언되어 있으므로 해당 메서드 내부에서 value는 모두 30이다.
    method1 밖의 내부클래스 인스턴스 변수 value=20;를 꺼내오기 위해서는 말 그대로 내부클래스 인스턴스(this)로 접근해야 한다. this.value = 20.
    마지막으로 외부Outer클래스의 인스턴스 변수를 꺼내오기 위해서는
    외부클래스인스턴스(Outer.this)를 통해 접근해야한다.
    Outer.this.value=30이다.

  • 내 풀이:

package effort;

import effort.Outer.Inner;

class Outer {
	int value = 10;

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


class Note {
  public static void main(String args[]) {
	/*
	 *  (4) 알맞은 코드를 넣어 완성하십시오.
	 * */
	Outer o = new Outer();
	Inner inner = o.new Inner();	//⭐외부클래스의 인스턴스 이너클래스 접근법
	inner.method1();  
	  
  }
}

7-9. EventHandler 익명 클래스(anonymous class)로 변경하기

  • 예제
package effort;

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

class Exercise7_9 {
	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);
	}
}
  • 풀이 접근: 익명클래스의 장점을 설명하는 문제이다. 익명 클래스는 일회성 클래스로서 한 번 사용되고 다시 사용될 일이 없는 클래스에 사용된다.
    이런 방식으로 만드는 이유는 간단하다.

    어딴 조상 클래스나 인터페이스를 매개변수로 하는 메서드에 이를 상속 또는 구현하는 클래스를 만들어서 해당 타입을 제공하려고 봤더니, 한 번 쓰면 다시는 안쓸 것같은 일회용이었다. 그럼 코드나 상속 계층도를 복잡하게 만들지 말고 익명 클래스를 사용하면 되는 것이다. 따라서 익명 클래스는 조상클래스 또는 인터페이스 이름으로 생성한다. new 인터페이스() {}에서 구현부{} 안에 적절한 내용을 담아 이것을 매개변수로 바로 넘긴다.

    문제에서는 Frame 클래스 참조변수 f의 메서드 add.WindowListener에 포함될 매개변수(그 타입은 인터페이스 WindowAdapter이다.)를 이를 상속받는 new EventHandler()로 넘겨버리기 위해서 EventHandler를 만들어서 windowClosing메서드를 작성해 주었지만,
    익명 클래스로 사용한다면 EventHandler()자체가 만들어질 이유가없는 것이다.

    ❗매개변수로 넘겨지는❗ new EventHandler와 익명클래스의 둘 모두 일회성으로 사용될 인스턴스이긴 하지만 본질적인 차이는 익명클래스는 클래스 탄생 자체가 불필요하다는 것이다.

  • 내 풀이

package effort;

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

class Note {
	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);
//	}
//}

예제 출처:

남궁성 - 자바의 정석 : 예제 코드 및 모범답안

profile
9에서 0으로, 백엔드 개발블로그

0개의 댓글