//카드 덱(무더기)를 나타낸다.
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);
}
}
SutdaCard card = new SutdaCard();
: for문 밖에서 for문의 요소에 해당하는 객체를 만들어주기 위하여 별도의 객체를 생성했다.cards[i]=new SutdaCard(숫자,광)
을 통하여 인스턴스를 바로 SutdaCard
배열에 집어넣을 수 있었다. 심지어 그렇게 풀었는데... 결국 저 인스턴스는 의미없는 코드가 되어 GC가 처리하게 되었다.i%10+1
을 사용한다면 나머지가 0이 나와도 1로써 변환된다. 이를 num
이라는 로컬 변수 안에 저장하는 것이 더 효율적인 방법일 것이다.boolean isKwang = (i < 10)&&(num==1||num==3||num==8);
을 생각하지 못하고 이미 false로 만들어진 card 객체에서 앞부분만 true로 바꾸려고 했던 것이 아쉬웠다. *또한, AND(&&)가 OR(||)보다 우선순위가 높기 때문에 괄호를 꼭 사용해야 한다.class SutdaDeck {
final int CARD_NUM = 20;
SutdaCard[] cards = new SutdaCard[CARD_NUM];
SutdaDeck() {
/*
연습문제7-1의 답이므로 내용생략
*/
}
/*
(1) 위에 정의된 세 개의 메서드를 작성하시오.
*/
} // SutdaDeck
class SutdaCard {
int num;
boolean isKwang;
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) . 를 호출한다
}
if(index < 0 || index >= CARD_NUM)
조건에서는 return null
해줘야 한다.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();
}
}
int price
와 int bonusPoint
가 자동적으로 존재하게 된다는 말이다. Product 클래스와 Tv클래스에서 특별히 이상한 점을 찾아보기 어렵다. (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)
를 호출하게 하는 것이 필요하다.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();
}
}
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());
}
}
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
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
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 인스턴스의 멤버로서 참조할 수 있게 됩니다.
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
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();
}
}
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);
// }
//}
남궁성 - 자바의 정석 : 예제 코드 및 모범답안