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

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

 

메서드명 : gotoPrevChannel

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

반환 타입 : 없음

매개변수 : 없음

class MyTv2 {
	boolean isPowerOn;
	int channel;
	int volume;
	int prechannel;
	final int MAX_VOLUME = 100;
	final int MIN_VOLUME = 0;
	final int MAX_CHANNEL = 100;
	final int MIN_CHANNEL = 1;

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

	public int getVolume() {
		return volume;
	}

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

	public int getChannel() {
		return channel;
	}
	
	public void gotoPrevChannel() {
		setChannel(prechannel);
	}
	
}

class ExerciseEx7_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 다음 중 접근 제어자에 대한 설명으로 옳지 않은 것은?(모두 고르시오) c

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

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

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

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

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

 

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

 

 

 

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

Math클래스의 모든 메서드가 static메서드이고 인스턴스 변수가 존재하지 않기 때문에 객체를 생성할 필요가 없기 때문이다.

 

 

 

7-14 문제 7-1에 나오는 섰다 카드의 숫자와 종류(isKwang)는 사실 한번 값이 지정되면 변경되어서는 안 되는 값이다. 카드 숫자가 한번 잘못 바뀌면 똑같은 카드가 두장이 될 수도 있기 때문이다. 이러한 문제점이 발생하지 않도록 아래의 SutdaCard를 수정하시오.

class SutdaCard {
	final 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 클래스가 다음과 같이 정의되어 있을 때, 형 변환을 올바르게 하지 않은 것은? e

	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;

 

e. 조상 타입의 인스턴스를 자손 타입으로 형 변환할 수 없다.

 

 

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

	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 Abulance는 FireEngine과 아무 관계도 없다.

 

 

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() { /* .*/ } //선택된 대상을 내린다
}
public class JavaExercise7_17 {
		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 다음과 같은 실행결과를 얻도록 코드를 완성하시오.

메서드명 : action

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

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

              SingleRobot인 경우, sing()를 호출하고,

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

반환 타입 : 없음

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

public class JavaExercise7_18 {
	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("그림을 그립니다");
	}
}
춤을 춥니다
노래를 합니다
그림을 그립니다

 

 

 

public class JavaExercise7_18 {
	public static void action(Robot r) {
		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

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

   반환타입 : 없음

   매개변수 : 없음

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 . 가진 돈과 물건의 가격을 비교해서 가진 돈이 적으면 메서드를 종료한다
if(money < p.price) {
System.out.println(" "+ p +" / ."); 잔액이 부족하여 을 를 살수 없습니다
return;
}
잔액이 부족하여 을 를 살수 없습니다 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) {
		if (money < p.price)
			return;
		else if (true) {
			money -= p.price;
			add(p);
		}
		cart[i++] = p;
	}

	void add(Product p) {
		if (i >= cart.length) {
			Product[] tmp = new Product[cart.length * 2];
			System.arraycopy(cart, 0, tmp, 0, cart.length);
			cart = tmp;
		}
	}

	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);
		System.out.println(" :" + sum);
		System.out.println(" :" + money);
	} 
}

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

내부 클래스는 클래스 내에 선언된다는 점을 제외하고는 일반 클래스와 다르지 않다.

 

내부 클래스는 클래스 내에 선언된 클래스이다. 클래스에 다른 클래스를 선언하는 이유는 두 클래스가 긴밀한 관계에 있기 때문이다.

한 클래스를 다른 클래스의 내부 클래스로 선언하면 두 클래스의 멤버들 간에 서로 쉽게 접근할 수 있다는 장점과 외부에는 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점을 얻을 수 있다.

내부 클래스의 장점
 - 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
 - 코드의 복잡성을 줄일 수 있다(캡슐화).

내부 클래스는 바로 바깥에 있는 외부 클래스를 제외하고는 다른 클래스에서 잘 사용되지 않는 것이어야 한다.

 

 

 

내부 클래스의 종류와 특징

내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다. 내부 클래스는 마치 변수를 선언하는 것과 같은 위치에 선언할 수 있으며, 변수의 선언 위치에 따라 인스턴스 변수, 클래스 변수(static변수), 지역변수로 구분되는 것과 같이 내부 클래스도 선언 위치에 따라 다음과 같이 구분되어진다.

 

내부 클래스 특 징
인스턴스 클래스
(instance class)
외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스멤버처럼 다루어진다. 주로 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언된다.
스태틱 클래스
(static class)
외부 클래스의 멤버변수 선언위치에 선언하며 외부 클래스의 static멤버처럼 다루어진다. 주로 외부 클래스의 static멤버, 특히 static메서드에서 사용될 목적으로 선언된다.
지역 클래스
(local class)
외부 클래스의 메서드나 초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다.
익명 클래스
(anonymous class)
클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)

 

 

 

내부 클래스의 선언

변수가 선언된 위치에 따라 인스턴스 변수, 클래스 변수(static변수), 지역변수로 나뉘듯이 내부 클래스도 이와 마찬가지로 선언된 위치에 따라 나뉜다. 그리고, 각 내부 클래스의 선언 위치에 따라 같은 선언 위치의 변수와 동일한 유효 범위(scope)와 접근성(accessibility)을 갖는다.

 

 

 

내부 클래스의 제어자와 접근성

아래 코드에서 인스턴스 클래스(InstanceInner)와 스태틱 클래스(StaticInner)는 외부 클래스(Outer)의 멤버 변수(i인스턴스 변수와 클래스 변수)와 같은 위치에 선언되며, 또한 멤버 변수와 같은 성질을 갖는다. 따라서 내부 클래스가 외부 클래스의 멤버와 같이 간주되고, 인스턴스 멤버와 static멤버 간의 규칙이 내부 클래스에도 똑같이 적용된다.

내부 클래스 중에서 스태틱 클래스(StaticInner)만 static멤버를 가질 수 있다. 드문 경우지만 내부 클래스에 static변수를 선언해야 한다면 스태틱 클래스로 선언해야 한다.

다만 final과 static이 동시에 붙은 변수는 상수(constant)이므로 모든 내부 클래스에서 정의가 가능하다.

인스턴스 멤버는 같은 클래스에 있는 인스턴스 멤버와 static멤버 모두 직접 호출이 가능하지만, static멤버는 인스턴스 멤버를 직접 호출할 수 없는 것처럼, 인스턴스클래스는 외부 클래스의 인스턴스멤버를 객체 생성 없이 바로 사용할 수 있지만, 스태틱 클래스는 외부클래스의 인스턴스멤버를 객체생성 없이 사용할 수 있다.

 

 

익명 클래스(anonymous class)

익명 클래스는 특이하게도 다른 내부 클래스들과는 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한 번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.

public class InnerEx6 {
	Object iv = new Object () { void method() {} };
	static Object cv = new Object() { void method() {} };
	
	void myMethod() {
		Object lv = new Object() { void method() {} };
	}
}

이 예제를 컴파일하면 다음과 같이 4개의 클래스 파일이 생성된다.

InnerEx6.class

InnerEx6$1.class > 익명 클래스

InnerEx6$2.class > 익명 클래스

InnerEx6$3.class > 익명 클래스

익명 클래스는 이름이 없기 때문에 '외부 클래스명$숫자.class'의 형식으로 클래스파일명이 결정된다.

예제 7-1

public class CaptionTvTest {
	public static void main(String[] args) {
		CaptionTv ctv = new CaptionTv();
		ctv.power();
		ctv.channel = 10;
		ctv.channelUp();
		System.out.println(ctv.channel);
		ctv.display("Hello World");
		ctv.caption = true;
		ctv.display("Hello World2");
	}

}

class Tv {
	boolean power;
	int channel;
	
	void power() {power = !power;}
	void channelUp() {channel++;}
	void channelDown() {channel--;}
}
class CaptionTv extends Tv {
	boolean caption;
	void display(String text) {
		if(caption) {
			System.out.println(text);
		}
	}
}
11
Hello World2

 

 

 

예제7-2

public class DrawShape {
	public static void main(String[] args) {
		Point[] p = {	
				new Point(100, 100),
				new Point(140, 50),
				new Point(200, 100)
		};
		Triangle t = new Triangle(p);
		Circle c = new Circle(new Point(150, 150), 50);
		
		t.draw();
		c.draw();
	}

}

class Shape {
	String color = "black";
	void draw() {
		System.out.printf("[color=%s]%n", color);
	}
}

class Point {
	int x;
	int y;

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

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

	String getXY() {
		return "(" + x + "," + y + ")"; // x와 y의 값을 문자열로 반환
	}
}

class Circle extends Shape {
	Point center;
	int r;

	Circle() {
		this(new Point(0, 0), 100);
}
	Circle(Point center, int r) {
		this.center = center;
		this.r = r;
	}
	
	void draw() {
		System.out.printf("[center=(%d, %d), r=%d, color=%s]%n", center.x, center.y, r, color);
	}
}

class Triangle extends Shape {
	Point[] p = new Point[3];
	
	Triangle(Point[] p) {
		this.p = p;
	}
	
	void draw() {
		System.out.printf("[p1=%s, p2=%s, p3=%s, color=%s]%n",
				p[0].getXY(),p[1].getXY(), p[2].getXY(), color);
	}
}
[p1=(100,100), p2=(140,50), p3=(200,100), color=black]
[center=(150, 150), r=50, color=black]

 

 

 

예제7-3

public class DeckTest {
	public static void main(String[] args) {
		Deck d = new Deck();
		Card c = d.pick();
		System.out.println(c);

		d.shuffle();
		c = d.pick(0);
		System.out.println(c);

	}
}

class Deck {
	final int CARD_NUM = 52;
	Card cardArr[] = new Card[CARD_NUM];

	Deck() {
		int i = 0;
		for (int k = Card.KIND_MAX; k > 0; k--) {	// 4
			for (int n = 0; n < Card.NUM_MAX; n++) {	//13
				cardArr[i++] = new Card(k, n + 1);
			}
		}
		
//		for(int i =0 ; i < CARD_NUM ; i++) {
//			cardArr[i++] = new Card(Card.KIND_MAX-i/Card.NUM_MAX, i%Card.NUM_MAX+1);
//		}
	}
		// cardArr[10] ?? 무늬, 숫자
		// diamond J 23
		// heart J 36
		//Clover J 49 50 51

	Card pick(int index) {
		return cardArr[index];
	}

	Card pick() {
		return pick((int) (Math.random() * CARD_NUM));
	}

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

			Card temp = cardArr[i];
			cardArr[i] = cardArr[r];
			cardArr[r] = temp;

		}
	}
}

class Card {
	static final int KIND_MAX = 4;
	static final int NUM_MAX = 13;

	static final int SPADE = 4;
	static final int DIAMOND = 3;
	static final int HEART = 2;
	static final int CLOVER = 1;

	int kind;
	int number;

	Card() {
		this(SPADE, 1);
	}

	Card(int kind, int number) {
		this.kind = kind;
		this.number = number;
	}

	public String toString() {
		String[] kinds = { "", "CLOVER", "HEART", "DIAMOND", "SPADE" };
		String numbers = "0123456789XJQK"; // 숫자 10은 X로 표현
		return "kind : " + kinds[this.kind] + ", number : " + numbers.charAt(this.number);
	} // toString()의 끝
} // Card클래스의 끝
kind : SPADE, number : X
kind : DIAMOND, number : K

 

 

 

예제 7-4

class Tv {
	boolean power;
	int channel;
	
	void power()	{ power = !power; }
	void channelUP() { ++channel; }
	void channelDown() { --channel; }
}

class VCR {
	boolean power;
	int counter = 0;
	void power() {	power = !power; }
		void play() { }
		void stop() { }
		void rew() { }
		void ff() { }
	}

class TVCR extends Tv {
	VCR vcr = new VCR();
	
	void play() {
		vcr.play();
	}
	
	void stop() {
		vcr.stop();
	}
	void rew() {
		vcr.rew();
	}
	void ff() {
		vcr.ff();
	}
}

 

 

 

예제7-5

public class SuperTest {
	public static void main(String[] args) {
		Child c = new Child();
		c.method();
	}
}

class Parent {
	int x=10;
}

class Child extends Parent {
	void method() {
		System.out.println("x=" + x);
		System.out.println("this.x=" + this.x);
		System.out.println("super.x="+ super.x);
	}
}
x=10
this.x=10
super.x=10

 

 

 

예제7-6

public class SuperTest2 {
	public static void main(String[] args) {
		Child c = new Child();
		c.method();
	}
}

class Parent {
	int x=10;
}

class Child extends Parent {
	int x=20;
	void method() {
		System.out.println("x=" + x);
		System.out.println("this.x=" + this.x);
		System.out.println("super.x="+ super.x);
	}
}
x=20
this.x=20
super.x=10

 

 

 

예제7-7

public class PointTest {
	public static void main(String[] args) {
		Point3D p3 = new Point3D(1,2,3);
	}
}

class Point {
	int x, y;
	
	Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
	
	String getLocation() {
		return "x :" + x + ", y :"+ y;
	}
}

class Point3D extends Point {
	int z;
	
	Point3D(int x, int y, int z) {
		
		this.x = x;
		this.y = y;
		this.z = z;
	}
	
	String getLocation() {
		return "x :" + x + ", y :" + y + ", z :" + z;
	}
}
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	Implicit super constructor Point() is undefined. Must explicitly invoke another constructor

	at mypack.Point3D.<init>(PointTest.java:25)
	at mypack.PointTest.main(PointTest.java:5)

 

 

 

예제7-8

class PointTest2 {
	public static void main(String[] args) {
		Point3D p3 = new Point3D();
		System.out.println("p3.x="+ p3.x);
		System.out.println("p3.y="+ p3.y);
		System.out.println("p3.z="+ p3.z);

	}

}

class Point {
	int x = 10;
	int y = 20;

	Point(int x, int y) {

		this.x = x;
		this.y = y;
	}
}

class Point3D extends Point {
	int z = 30;

	Point3D() {
		this(100, 200, 300);
	}

	Point3D(int x, int y, int z) {
		super(x, y);
		this.z = z;
	}
}
p3.x=100
p3.y=200
p3.z=300

 

 

 

예제7-9

pacakge com.codechobo.book;

public class PackageTest {
	public static void main(String[] args) {
		System.out.println("Hello World!");
	}
}

 

 

 

예제7-10

public class ImportTest {
	public static void main(String[] args) {
		Date today = new Date();
		
		SimpleDateFormat date = new SimpleDateFormat("yyyy/mm/dd");
		SimpleDateFormat time = new SimpleDateFormat("hh:mm:ss a");
		
		System.out.println("오늘 날짜는 " + date.format(today));
		System.out.println("현재 시간은 " + time.format(today));
	}
}
오늘 날짜는 2021/30/26
현재 시간은 05:30:19 오전

 

 

 

예제7-11

import static java.lang.System.out;
import static java.lang.Math.*;

public class StaticImportEx1 {
	public static void main(String[] args) {
//		System.out.println(Math.random());
		out.println(random());
		
//		System.out.println("Math.PI :"+Math.PI);
		out.println("Math.PI :" + PI);
	}
}
0.16729015202127595
Math.PI :3.141592653589793

 

 

 

예제7-12

class Card {
	final int NUMBER;
	final String KIND;
	static int width = 100;
	static int height = 250;

	Card(String kind, int num) {
		KIND = kind;
		NUMBER = num;

	}

	Card() {
		this("HEART", 1);
	}

	public String toString() {
		return KIND + " " + NUMBER;
	}
}

class FinalCardTest {
	public static void main(String[] args) {
		Card c = new Card("HEART", 10);

//	c.NUMBER = 5;
		System.out.println(c.KIND);
		System.out.println(c.NUMBER);
		System.out.println(c);
	}
}
HEART
10
HEART 10

 

 

 

예제7-13

public class TimeTest {
	public static void main(String[] args) {
		Time t = new Time(12, 35, 30);
		System.out.println(t);
//		t.hour = 13;
		t.setHour(t.getHour() + 1); // 13을들고오고싶으면 getHour 바꾸고싶으면 setHour
		System.out.println(t);
	}
}

// VO, Component
class Time {
	private int hour, minute, second;
	boolean power;

	public Time(int hour, int minute, int second) {
		super();
		this.hour = hour;
		this.minute = minute;
		this.second = second;
	}
	public boolean isPower() {
		return power;
	}
	public void setPower(boolean power) {
		this.power = power;
	}
	public int getHour() {	return hour; }
	public void setHour(int hour) {
		
		if(hour < 0 || hour > 23) return;
		this.hour = hour;
	}
	
	public int getMinute() {	return minute; }
	public void setMinute(int minute) {
		
		if(minute < 0 || minute > 59) return;
		this.minute = minute;
	}
	public int getSecond() {	return second;}
	public void setSecond(int second) {
		if (second < 0 || second > 59) return;
		this.second = second;
	}
	public String toString() {
		return hour + ":" + minute + ":" + second;
	}

	}
12:35:30
13:35:30

 

 

 

예제7-14

final class Singleton {
	private static Singleton s = new Singleton();
	
	private Singleton() {
		//...
	}
	
	public static Singleton getInstance() {
		if(s==null)
			s = new Singleton();
		
		return s;
	}
}

class SingletonTest{
	public static void main(String[] args) {
//		Singleton s = new Singleton();
		Singleton s = Singleton.getInstance();
	}
}

 

 

 

예제7-15

public class CastingTest1 {
	public static void main(String[] args) {
		Car car = null;
		FireEngine fe = new FireEngine();
		FireEngine fe2 = null;

		fe.water();
		car = (Car)fe;	//Car타입의 참조형변수라고 명시하지 않아도 된다.
//		car.water();
		fe2 = (FireEngine) car;
		fe2.water();
	}
}

class Car {
	String color;
	int door;

	void drive() {
		System.out.println("drive, brrrr~");
	}

	void stop() {
		System.out.println("stop!!!");
	}
}

class FireEngine extends Car {
	void water() {
		System.out.println("water!!!");
	}
}
water!!!
water!!!

 

예제7-16

class CastingTest2 {
	public static void main(String[] args) {
		Car car = new Car();
		Car car2 = null;
		FireEngine fe = null;
		
		car.drive();
		fe = (FireEngine)car;
		fe.drive();
		car2 = fe;
		car2.drive();
	}
}
drive, brrrr~
Exception in thread "main" java.lang.ClassCastException: a210719.Car cannot be cast to a210719.FireEngine
	at a210719.CastingTest2.main(CastingTest2.java:10)

 

 

예제7-17

public class InstanceofTest {
	public static void main(String[] args) {
		FireEngine fe = new FireEngine();
		
		if(fe instanceof FireEngine) {
			System.out.println("This is a FireEngine instance.");
		}
		
		if(fe instanceof Car) {
			System.out.println("This is a Car instance.");
		}
		
		if(fe instanceof Object) {
			System.out.println("This is an Object instance.");
		}
		System.out.println(fe.getClass().getName());
	}
}// class
class Car {}
class FireEngine extends Car{}
This is a FireEngine instance.
This is a Car instance.
This is an Object instance.
FireEngine

 

 

 

예제7-18

public class BindingTest {
	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

 

 

 

예제7-19

class BindingTest2 {
	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 {
}
p.x = 100
Parent Method
c.x = 100
Parent Method

 

 

 

예제7-20

class BindingTest3 {
	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();
		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 metohd() {
		System.out.println("x=" + x);
		System.out.println("super.x=" + super.x);
		System.out.println("this.x=" + this.x);
	}
}
p.x = 100
Parent Method

c.x = 200
Parent Method

 

 

 

예제7-21

class Product{
	int price;
	int bonusPoint;
	
	Product(int price) {
		this.price = price;
		bonusPoint = (int)(price/10.0);
	}
}
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 Buyer {
	int money = 1000;
	int bonusPoint = 0;
	
	void buy(Product p) {
		if(money < p.price) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
			return;
		}
		
		money -= p.price;
		bonusPoint += p.bonusPoint;
		System.out.println(p + "을/를 구입하셨습니다.");
	}
}
class PolyArgumentTest {
	public static void main(String[] args) {
		Buyer b = new Buyer();
		
		b.buy(new Tv());
		b.buy(new Computer());
		
		System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
		System.out.println("현재 보너스 점수는" + b.bonusPoint + "점입니다.");
		
	}
}
Tv을/를 구입하셨습니다.
computer을/를 구입하셨습니다.
현재 남은 돈은 700만원입니다.
현재 보너스 점수는30점입니다.

 

 

예제7-22

class Product {
	int price;
	int bonusPoint;

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

	Product() {
	}
}

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

class Buyer {
	int money = 1000;
	int bonusPoint = 0;
	Product[] item = new Product[10];
	int i = 0;

	void buy(Product p) {
		if (money < p.price) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
			return;
		}

		money -= p.price;
		bonusPoint += p.bonusPoint;
		item[i++] = p;
		System.out.println(p + "을/를 구입하셨습니다.");
	}

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

		for (int i = 0; i < item.length; i++) {
			if (item[i] == null)
				break;
			sum += item[i].price;
			itemList += item[i] + ", ";
		}

		System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
		System.out.println("구입하신 제품은 " + itemList + "입니다.");
	}
}

class PolyArgumentTest2 {
	public static void main(String args[]) {
		Buyer b = new Buyer();

		b.buy(new Tv());
		b.buy(new Computer());
		b.buy(new Audio());
		b.summary();
	}
}
Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
Audio을/를 구입하셨습니다.
구입하신 물품의 총금액은 350만원입니다.
구입하신 제품은 Tv, Computer, Audio, 입니다.

 

 

 

예제7-23

import java.util.*;
	class Product {
		int price;
		int bonusPoint;
		
		Product(int price) {
			this.price = price;
			bonusPoint = (int)(price/10.0);
		}
		
		Product() {
			price = 0;
			bonusPoint = 0;
		}
	}



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

class Buyer {
	int money = 1000;
	int bonusPoint = 0;
	Vector<Product> item = new Vector<Product>();
	
	void buy(Product p) {
		if(money < p.price) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
			return;
		}
		money -= p.price;
		bonusPoint += p.bonusPoint;
		item.add(p);
		System.out.println(p + "을/를 구입하셨습니다.");
	}
	
	void refund(Product p) {
		if(item.remove(p)) {
			bonusPoint -= p.bonusPoint;
			System.out.println(p + "을/를 반품하셨습니다.");
		} else { System.out.println("구입하신 제품 중 해당 제품이 없습니다.");
	}
}
	
void summary() {
	int sum = 0;
	String itemList ="";
	
	if(item.isEmpty()) {
		System.out.println("구입하신 제품이 없습니다.");
		return;
	}
	
	for(int i = 0; i < item.size();i++) {
		Product p = (Product)item.get(i);
		sum += p.price;
		itemList += (i==0) ? "" + p : ", " + p;
	}
	System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
	System.out.println("구입하신 제품은 "+ itemList + "입니다.");
}
}

public class PolyArgumentTest3 {
	public static void main(String[] args) {
		Buyer b = new Buyer();
		Tv tv = new Tv();
		Computer com = new Computer();
		Audio audio = new Audio();

		b.buy(tv);
		b.buy(com);
		b.buy(audio);
		b.summary();
		System.out.println();
		b.refund(com);
		b.summary();
	}
}
Tv을/를 구입하셨습니다.
computer을/를 구입하셨습니다.
Audio을/를 구입하셨습니다.
구입하신 물품의 총금액은 350만원입니다.
구입하신 제품은 Tv, computer, Audio입니다.

computer을/를 반품하셨습니다.
구입하신 물품의 총금액은 150만원입니다.
구입하신 제품은 Tv, Audio입니다.

 

 

 

예제7-24

public class FighterTest {
	public static void main(String[] args) {
		Fighter f = new Fighter();
		Fightable fightable = new Fighter();
				
		if(f instanceof Unit) {
			System.out.println("f는 Unit의 자손입니다");
		}
		if(f instanceof Fightable) {
			System.out.println("f는 Fightable인터페이스를 구현했습니다");
		}
		if(f instanceof Attackable) {
			System.out.println("f는 Attackable인터페이스를 구현했습니다");
		}
		if(f instanceof Movable) {
			System.out.println("f는 Movable인터페이스를 구현했습니다");
		}
		if(f instanceof Object) {
			System.out.println("f는 Object클래스의 자손입니다");
		}
		
	}
}

class Fighter extends Unit implements Fightable {
	public void attack(Unit u) {
		// 내용생략
	}
	public void move(int x, int y) {
		// 내용생략
	}
}
class Unit {
	int currentHP;
	int x;
	int y;
	
}
interface Fightable extends Movable, Attackable{}
interface Movable { void move(int x, int y); }
interface Attackable { void attack(Unit u); }
f는 Unit의 자손입니다
f는 Fightable인터페이스를 구현했습니다
f는 Attackable인터페이스를 구현했습니다
f는 Movable인터페이스를 구현했습니다
f는 Object클래스의 자손입니다

 

 

 

예제7-25

interface Parseable {
	public abstract void parse(String fileName);
}

class ParserManager {
	public static Parseable getParser(String type) {
		if (type.equals("XML")) {
			return new XMLParser();
		} else {
			Parseable p = new HTMLParser();
			return p;
		}

	}
}

class XMLParser implements Parseable {
	@Override
	public void parse(String fileName) {
		System.out.println(fileName + "- XML parsing completed.");
	}
}

class HTMLParser implements Parseable {
	@Override
	public void parse(String fileName) {
		System.out.println(fileName + "-HTML parsing completed.");

	}
}

class parserTest {
	public static void main(String args[]) {
		Parseable parser = ParserManager.getParser("XML");
		parser.parse("document.xmml");
		parser = ParserManager.getParser("HTML");
		parser.parse("document2.html");
	}
}

 

 

 

예제7-26

public class RepairableTest {
	public static void main(String[] args) {
		Tank tank = new Tank();
		Dropship dropship = new Dropship();
		
		Marine marine = new Marine();
		SCV scv = new SCV();
		scv.repair(tank);
		scv.repair(dropship);
//		scv.repair(marine);
	}
}

interface Repairable{}

class Unit {
	int hitPoint;
	final int MAX_HP;
	Unit(int hp) {
		MAX_HP = hp;
	}
	//...
}

class GroundUnit extends Unit {
	GroundUnit(int hp) {
		super(hp);
	}
}

class AirUnit extends Unit {
	AirUnit(int hp) {
		super(hp);
	}
}


class Tank extends GroundUnit implements Repairable {
	Tank() {
		super(150);
		hitPoint = MAX_HP;
	}
	
	public String toString() {
		return "Tank";
	}
	//...
}

class Dropship extends AirUnit implements Repairable {
	Dropship() {
		super(125);
		hitPoint = MAX_HP;
	}
	
	public String toString() {
		return "Dropship";
	}
	//...
}

class Marine extends GroundUnit {
	Marine() {
		super(40);
		hitPoint = MAX_HP;
	}
	//...
}

class SCV extends GroundUnit implements Repairable{
	SCV() {
		super(60);
		hitPoint = MAX_HP;
	}
	
	void repair(Repairable r) {
		if (r instanceof Unit) {
			Unit u = (Unit)r;
			while(u.hitPoint!=u.MAX_HP) {
				u.hitPoint++;
			}
			System.out.println( u.toString() + "의 수리가 끝났습니다.");
		}
		//...
	}
}
Tank의 수리가 끝났습니다.
Dropship의 수리가 끝났습니다.

 

 

 

예제7-27

class A {
	public void methodA(B b) {
		b.methodB();
	}
}

class B  {
	public void methodB() {
		System.out.println("methodB()");
	}
}

public class InterfaceTest {
	public static void main(String[] args) {
		A a = new A();
		a.methodA(new B());
	}
}
methodB()

 

 

예제7-28

class A {
	void autoPlay(I i) {
		i.play();
	}
}

interface I {
	public abstract void play();
}

class B implements I {
	public void play() {
		System.out.println("play in B class");
	}
}

class C implements I {
	public void play() {
		System.out.println("play in C class");
	}
}

class InterfaceTest2 {
	public static void main(String[] args) {
		A a = new A();
		a.autoPlay(new B());
		a.autoPlay(new C());
	}
}
play in B class
play in C class

 

 

 

예제7-29

public class InterfaceTest3 {
	public static void main(String[] args) {
		A a = new A();
		a.methodA();
	}
}

class A {
	void methodA() {
		I i = InstanceManager.getInstance();
		i.methodB();
		System.out.println(i.toString());
	}
}

interface I {
	public abstract void methodB();
}

class B implements I {
	public void methodB() {
		System.out.println("methodB in B class");
	}

	public String toString() {
		return "class B";
	}
}

class InstanceManager {
	public static I getInstance() {
		return new B();
	}
}
methodB in B class
class B

 

 

예제7-30

public class DefaultMethodTest {
	public static void main(String[] args) {
		Child c = new Child();
		c.method1();
		c.method2();
		MyInterface.staticMethod();
		MyInterface2.staticMethod();
	}
}

class Child extends Parent implements MyInterface, MyInterface2 {
	public void method1() {
		System.out.println("method1() in Child"); // 오버라이딩
	}
}

class Parent {
	public void method2() {
		System.out.println("method2() in Parent");
	}
}

interface MyInterface {
	default void method1() {
		System.out.println("method1() in MyInterface");
	}

	default void method2() {
		System.out.println("method2() in MyInterface");
	}

	static void staticMethod() {
		System.out.println("staticMethod() in MyInterface");
	}
}

interface MyInterface2 {
	default void method1() {
		System.out.println("method1() in MyInterface2");
	}

	static void staticMethod() {
		System.out.println("staticMethod() in MyInterface2");
	}
}
method1() in Child
method2() in Parent
staticMethod() in MyInterface
staticMethod() in MyInterface2

 

 

 

예제 7-31

class InnerEx1 {
	class InstanceInner {
		int iv = 100;
//		static int cv = 100;
		final static int CONST = 100;
	}

	static class StaticIneer {
		int iv = 200;
		static int cv = 200;
	}

	void myMethod() {
		class LocalInner {
			int iv = 300;
//			static int cv = 300;
			final static int CONST = 300;
		}
	}

	public static void main(String[] args) {
		System.out.println(InstanceInner.CONST);
		System.out.println(StaticIneer.cv);
	}
}
100
200

 

 

 

예제 7-32

class InnerEx2 {
	class InstanceInner {
	}

	static class StaticInner {
	}

	InstanceInner iv = new InstanceInner();
	static StaticInner cv = new StaticInner();

	static void staticMethod() {
//		InstanceInner obj1 = new InstanceInner();
		StaticInner obj2 = new StaticInner();

		InnerEx2 outer = new InnerEx2();
		InstanceInner obj1 = outer.new InstanceInner();
	}

	void instanceMethod() {
		InstanceInner obj1 = new InstanceInner();
		StaticInner obj2 = new StaticInner();
//		LocalInner lv = new LocalInner();
	}

	void myMethod() {
		class LocalInner {
		}
		LocalInner lv = new LocalInner();
	}
}

 

 

 

예제7-33

class InnerEx3 {
	private int outerIv = 0;
	static int outerCv = 0;
	
	class InstanceInner {
		int iiv = outerIv;
		int iiv2 = outerCv;
	}
	
	static class StaticInner {
//		int siv = outerIv;
		static int scv = outerCv;
	}
	
	void myMethod() {
		int lv = 0;
		final int LV = 0;
		
		class LocalInner {
			int liv = outerIv;
			int liv2 = outerCv;
			int liv3 = lv;
			int liv4 = LV;
		}
	}

}

 

 

예제7-34

class Outer {
	class InstanceInner {
		int iv = 100;
	}
	
	static class StaticInner{
		int iv = 200;
		static int cv = 300;
	}
	
	void myMethod() {
		class LocalInner {
			int iv = 400;
		}
	}

}

class InnerEx4 {
	public static void main(String[] args) {
		Outer oc = new Outer();
		Outer.InstanceInner ii = oc.new InstanceInner();
		
		System.out.println("ii.iv : "+ ii.iv);
		System.out.println("Outer.StaticInner.cv : "+ Outer.StaticInner.cv);
		
		Outer.StaticInner si = new Outer.StaticInner();
		System.out.println("si.iv : "+ si.iv);
	}
}
ii.iv : 100
Outer.StaticInner.cv : 300
si.iv : 200

 

 

 

예제 7-35

class Outer {
	int value = 10;

	class Inner {
		int value = 20;

		void method1() {
			int value = 30;
			System.out.println("value :" + value);
			System.out.println("this.value :" + this.value);
			System.out.println("Outer.this.value :" + Outer.this.value);
		}
	}

}

class InnerEx5 {
	public static void main(String[] args) {
		Outer outer = new Outer();
		Outer.Inner inner = outer.new Inner();
		inner.method1();
	}
}
value :30
this.value :20
Outer.this.value :10

 

 

 

예제7-36

public class InnerEx6 {
	Object iv = new Object () { void method() {} };
	static Object cv = new Object() { void method() {} };
	
	void myMethod() {
		Object lv = new Object() { void method() {} };
	}
}

 

 

 

예제7-37

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

class InnerEx7 {
	public static void main(String[] args) {
		Button b = new Button("Start");
		b.addActionListener(new EventHandler());
	}
}
class EventHandler implements ActionListener {
	public void actionPerformed(ActionEvent e) {
		System.out.println("ActionEvent occurred!!!");
	}
}

 

 

 

예제7-38

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

class InnerEx8 {
	public static void main(String[] args) {
		Button b = new Button("Start");
		b.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.out.println("ActionEvent occurred!!!");
			}
		});
	}
}

인터페이스는 일종의 추상 클래스이다.

인터페이스는 추상 클래스처럼 추상 메서드를 갖지만 추상 클래스보다 추상화 정도가 높아서 추상 클래스와 달리 몸통을 갖춘 일반 메스도 또는 멤버 변수를 구성원으로 가질 수 없다. 오직 추상 메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.

추상 클래스를 부분적으로만 완성된 미완성 설계도라고 한다면, 인터페이스는 구현된 것은 아무것도 없고 밑그림만 그려져 있는 기본 설계도라 할 수 있다.

인터페이스도 다른 클래스를 작성하는데 도움을 줄 목적으로 작성된다.

 

 

인터페이스의 작성

인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 클래스 대신 interface를 사용한다는 것만 다르다. interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.

interface 인터페이스이름 {
	public static fianl 타입 상수이름 = 값;
	public abstract 매서드이름(매개변수목록);
}

일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항이 있다.

- 모든 멤버변수는 bublic static final 이어야 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abstract이어야 하며, 이를 생략할 수 있다.
단, static메서드와 default메서드는 예외

생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.

 

 

 

인터페이스의 상속

인ㅌ너 페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중 상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다.

클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다.

 

 

인터페이스의 구현

인터페이스도 추상 클래스처럼 그 자체로는 인스턴스를 생성할 수 어 ㅄ으며, 추상 클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 다르지 않다.

다만 클래스는 확장한다는 의미의 키워드 extends를 사용하지만 인터페이스는 구현한다는 의미의 키워드 implements를 사용할 뿐이다.

만일 구현하려는 인터페이스의 케서드 중 일부만 구현한다면, abstract를 붙여서 추상 클래스로 선언해야 한다.

또한 인터페이스는 상속과 구현을 동시에 할 수 있다(다중 상속이 가능하므로)

 

인터페이스의 이름에는 주로 able로 끝나는 것들이 많은데, 그 이유는 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해서이다. 또한 그 인터페이스를 구현한 클래스는 ~를 할 수 있는 능력을 갖추었다는 의미이기도 하다.

인터페이스는 상속 대신 구현이라는 용어를 사용하지만, 인터페이스로부터 상속받은 추상 메서드를 구현하는 것이기 때문에 인터페이스도 조금은 다른 의미의 조상이라고 할 수 있다. 

오버 라이딩할 때는 조상의 메서드보다 넓은 범위의 접근 제어자를 지정해야 한다. 이는 인터페이스도 마찬가지로 적용된다.

 

 

인터페이스를 이용한 다중 상속

자바는 일반적으로 다중 상속을 허용하지 않는다. 인터페이스가 이와 다르게 다중상속이 가능해 다중상속을 위한 것으로 오해를 사게 된다.

인터페이스는 static상수만 정의할 수 있으므로 조상 클래스의 멤버변수와 충돌하는 경우는 거의 없고 충돌된다 하더라도 클래스 이름을 붙여서 구분이 가능하다. 추상메서드는 구현내용이 없으므로 조상클래스의 메서드와 선언부가 일치하는 경우에는 조상 클래스 쪽의 메서드를 상속받으면 되므로 문제 되지 않는다.

그러나, 이렇게 하면 상속받는 멤버의 충돌을 피할 수 있지만, 다중 상속의 장점을 잃게 된다. 만약 두 클래스로부터 상속을 받아야 할 상황이라면, 두 조상 클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽은 클래스 내부에 멤버로 표함 시키는 방법으로 처리하거나 어느 한쪽의 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다.

 

 

인터페이스를 이용한 다형성

자손 클래스의 인스턴스를 조상 타입의 참조 변수로 참조하는것이 가능하다. 인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형 변환도 가능하다. 인터페이스는 매서드의 매개변수의 타입으로 사용될 수 있다.

인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 것이다.

리턴 타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

 

 

인터페이스의 장점

- 개발시간을 단축시킬 수 있다.
- 표준화가 가능하다.
- 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
- 독립적인 프로그래밍이 가능하다.

1. 개발 시간을 단축시킬 수 있다.

인터페이스가 작성되면 메서드를 호출하는 쪽에서 메서드의 내용과 관계없이 선언 부만 알면 프로그램을 작성하는 것이 가능하다.

그리고 동시에 다른 한쪽에서는 인터페이스를 구현하는 클래스를 작성하게 하면, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다.

 

2. 표준화가 가능하다.

프로젝트에 사용되는 기본 틀을 인터페이스로 작성된 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.

 

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

서로 상속관계에 있지도 않고, 같은 조상 클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현함으로써 관계를 맺어 줄 수 있다.

 

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

인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제 구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스 간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.

 

 

 

인터페이스의 이해

인터페이스를 이해하기 위해서는 다음의 두 가지 사항을 염두에 두고 있어야 한다.

- 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
- 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.

 

public class InstanceofTest {
	public static void main(String[] args) {
		FireEngine fe = new FireEngine();
		
		if(fe instanceof FireEngine) {
			System.out.println("This is a FireEngine instance.");
		}
		
		if(fe instanceof Car) {
			System.out.println("This is a Car instance.");
		}
		if(fe instanceof Object) {
			System.out.println("This is an Object instance.");
		}
		System.out.println(fe.getClass().getName());
	}

} // class
class Car{}
class FireEngine extends Car{}

이 경우 클래스 A를 작성하려면 클래스 B가 이미 작성되어 있어야 한다. 그리고 클래스 B의 methodB()의 선언부가 변경되면, 이를 사용하는 클래스 A도 변경되어야 한다.

직접적인 관계의 두 클래스는 한쪽(Provider)이 변경되면 다른 한쪽(User)도 변경돼야 한다는 단점이 있다. 그러나 클래스 A가 클래스 B를 직접 호출하지 않고 인터페이스를 매개체로 해서 클래스 A가 인터페이스를 통해서 클래스 B의 메서드에 접근하도록 하면, 클래스 B에 변경사항이 생기거나 클래스 B와 같은 기능의 다른 클래스로 대체되어도 클래스 A는 전혀 영향을 받지 않도록 하는 것이 가능하다.

두 클래스 간의 관계를 간접적으로 변경하기 위해서는 먼저 인터페이스를 이용해서 클래스 B(Provider)의 선언과 구현을 분리해야 한다.

interface I {
	public abstract void methodB();
}
class B implements I {
	public void methodB() {
		System.out.println("methodB in B class");
	}
}

이제 클래스 A는 클래스 B 대신 인터페이스 I를 사용해서 작성할 수 있다.

class A {
	public void methodA(B b) {
		b.method();
	}
}
class A {
	public void methodA(I i)
		i.method();
	}
}

클래스 A를 작성하는 데 있어서 클래스 B가 사용되지 않았다는 점에 주목해서 보면 클래스 A와 클래스 B는 A-B의 직접적인 관계에서 A-I-B의 간접적인 관계로 바뀌었다.

결국 클래스 A는 여전히 클래스 B의 메서드를 호출하지만, 클래스 A는 인터페이스 I 하고만 직접적인 관계에 있기 때문에 클래스 B의 변경에 영향을 받지 않는다.

인터페이스 I는 실제 구현 내용(클래스 B)을 감싸고 있는 껍데기이며, 클래스 A는 껍데기 안에 어떤 알맹이(클래스)가 들어 있는지 몰라도 된다.

 

 

 

디폴트 메서드와 static메서드

static메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 인터페이스에 추가가 가능하지만 자바의 규칙을 단순화하기 위해 인터페이스의 모든 메서드는 추상 메서드여야 한다는 규칙에 예외를 두지 않았다. 덕분에 인터페이스와 관련된 static메서드는 별도의 클래스에 따로 두어야 했다.

 

디폴트 메서드 

조상 클래스에 새로운 메서드를 추가하는 것은 별 일이 아니지만, 인터페이스의 경우에는 큰 일이다. 인터페이스에 메서드를 추가한다는 것은, 추상 메서드를 구현한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야 하기 때문이다.

이에 default method라는 것을 고안해서 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

default 메서드는 앞에 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통{}이 있어야 한다. 디폴트 메서드 역시 접근 제어자가 public이며, 생략 가능하다.

인터페이스 메서드에 디폴트 메서드를 추가하면, 조상 클래스에 새로운 메서드를 추가한 것과 동일해진다. 그러나 새로 추가된 디폴트 메서드가 기존 메서드와 이름이 중복되어 충돌하는 겨우가 발생하는데, 이 충돌을 해결하는 규칙은 다음과 같다.

1. 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
2. 디폴트 메서드와 조상 클래스 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다(사용하지 않도록 주의하자)

 

public class DefaultMethodTest {
	public static void main(String[] args) {
		Child c = new Child();
		c.method1();
		c.method2();
		MyInterface.staticMethod();
		MyInterface2.staticMethod();
	}
}

class Child extends Parent implements MyInterface, MyInterface2 {
	public void method1() {
		System.out.println("method1() in Child"); // 오버라이딩
	}
}

class Parent {
	public void method2() {
		System.out.println("method2() in Parent");
	}
}

interface MyInterface {
	default void method1() {
		System.out.println("method1() in MyInterface");
	}

	default void method2() {
		System.out.println("method2() in MyInterface");
	}

	static void staticMethod() {
		System.out.println("staticMethod() in MyInterface");
	}
}

interface MyInterface2 {
	default void method1() {
		System.out.println("method1() in MyInterface2");
	}

	static void staticMethod() {
		System.out.println("staticMethod() in MyInterface2");
	}
}
method1() in Child
method2() in Parent
staticMethod() in MyInterface
staticMethod() in MyInterface2

 

추상 클래스는 미완성 설계도로 볼 수 있다.

즉 추상클래스로 인스턴스는 생성할 수 없다. 추상 클래스는 상속을 통해서 자손 클래스에 의해서만 완성될 수 있다.

추상 클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는 데 있어서 바탕이 되는 조상 클래스로서 중요한 의미를 갖는다.

예를 들면 같은 크기의 Tv를 모델의 차이에 따라 나눌 때 Tv의 기본설계도를 두고 옵션에 따라 다른 클래스에 상속해서 사용하는 용도이다. 추상 클래스를 이용하면 중복되는 코드를 줄이고, 보기 편하게 만들 수 있다.

추상 클래스는 추상 메서드를 포함하고 있다는 것을 제외하고는 일반 클래스와 전혀 다르지 않다.

추상 클래스에도 생성자가 있으며, 멤버 변수와 메서드도 가질 수 있다.

추상 메서드를 포함하고 있지 않은 클래스에도 키워드 abstract를 붙여서 추상 클래스로 지정할 수도 있다. 추상메서드가 없는 완성된 클래스라 할지라도 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없다.

 

추상 메서드(abstract method)

메서드는 선언부와 구현부(몸통)로 구성되어 있다고 했다. 선언 부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상 메서드이다. 즉, 설계만 해 놓고 실제 수행될 내용은 작성하지 않았기 때문에 미완성 메서드인 것이다.

메서드를 이와 같이 미완성 상태로 남겨 놓는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는 선언 부만을 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워놓는 것이다.

/* 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다 */
abstract 리턴타입 매서드이름();

추상 클래스로부터 상속받는 자손 클래스는 오버 라이딩을 통해 조상인 추상 클래스의 추상 메서드를 모두 구현해주어야 한다. 만일 조상으로부터 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손 클래스 역시 추상 클래스로 지정해 주어야 한다.

메서드의 이름과 매개변수, 리턴 타입은 반드시 주석에 넣도록 하자.

 

추상 클래스의 작성

여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고, 기존의 클래스의 공통적인 부분을 뽑아서 추상 클래스로 만들어 상속하도록 하는 경우도 있다.

상속계층도를 따라 내려갈수록 (자손 클래스로 갈수록) 기능이 추가되어 세분화되며, 상속계층도를 따라 올라갈수록(조상 클래스로 갈수록) 공통 요소만 남게 된다.

추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
구체화 : 상속을 통해 클래스를 구현, 확장하는 작업

다형성에서 배웠듯이 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하는 것이 가능하기 때문에 이처럼 조상 클래스타입의 배열에 자손 클래스의 인스턴스를 담을 수 있는 것이다.

만일 이들 클래스 간의 공통조상이 없었다면 이처럼 하나의 배열로 다룰 수 없을 것이다.

객체지향 개념에서 다형성이란 여러 가지 형태를 가질 수 있는 능력을 의미하며, 자바에서는 한 타입의 참조 변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.

조상 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하였다.

보통 인스턴스의 타입과 참조변수의 타입이 일치하는 것이 보통이지만, 조상 클래스와 자손 클래스처럼 상속관계에 있을 경우, 조상 클 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.

하지만 반대로 자손타입의 참조 변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하다. 

그 이유는 실제 인스턴스인 조상클래스의 멤버 개수보다 자손 타입의 참조 변수가 사용할 수 있는 멤버 개수가 더 많기 때문이다.

클래스는 상속을 통해서 확장될 수는 있어도 축소될 수는 없어서, 조상 인스턴스의 멤버 개수는 자손 인스턴스의 멤버 개수보다 항상 적거나 같다.

조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 없다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.

 

 

 

참조 변수의 형 변환

기본형 변수와 같이 참조 변수도 형 변환이 가능하다. 단, 서로 상속관계에 있는 클래스사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로 형변환만 가능하다.

바로 윗 조상이나 자손이 아닌, 조상의 조상으로도 형 변환이 가능하다. 따라서 모든 참조변수는 모든 클래스의 조상인 Object클래스 타입으로 형변환이 가능하다.

기본형 변수의 형 변환에서 작은 자료형에서 큰 자료형의 형 변환은 생략이 가능하듯이, 참조형 변수의 형 변환에서는 자손 타입의 참조 변수를 조상 타입으로 형 변환하는 경우에는 형 변환을 생략할 수 있다.

자손타입 > 조상타입(Up casting) : 형변환 생략가능
자손타입 < 조상타입(down castring) : 형변환 생략불가

참조 변수 간의 형 변환 역시 캐스트 연산자를 사용하며, 괄호() 안에 변환하고자 하는 타입의 이름(클래스명)을 적어주면 된다.

class Car {
	String color;
	int door;
	void drive() {
		System.out.println("drive, brrr~");
	}
	void stop() {
		System.out.println("Stop!!!");
	}
}
class FireEngine extends Car {
	void water() {
		System.out.println("water!!");
	}
}
class Ambulance extends Car {
	void siren() {
		System.out.println("siren~~");
	}
}

Car클래스는 FireEngine클래스와 Ambulance클래스의 조상이다.

하지만 FireEngine클래스와 Ambulance클래스는 서로 아무런 관계가 없다.

따라서 FireEngine클래스와 Ambulance 간의 형 변환은 불가능하다.

 

형 변환은 참조 변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.

 단지 참조변수의 형 변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것뿐이다.

서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 
참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.
그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

 

 

instanceof연산자

참조 변수가 참조하고 있는 인스턴스의 실재 타입을 알아보기 위해 instanceof연산자를 사용한다. 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조 변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치한다. 그리고 연산의 결과로 boolean값인 true와 false중의 하나를 반환한다.

instanceof를 이용한 연산결과로 true가 나왔다면 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 의미한다.

값이 null인 참조변수에 대해 instanceof연산을 수행하면 false를 결과로 얻는다.

public class InstanceofTest {
	public static void main(String[] args) {
		FireEngine fe = new FireEngine();

		if (fe instanceof FireEngine) {
			System.out.println("This is a FireEngine instance.");
		}
		if (fe instanceof Car) {
			System.out.println("This is a Car instance.");
		}
		if (fe instanceof Object) {
			System.out.println("This is a Object instance.");
		}
		System.out.println(fe.getClass().getName());
		System.out.println(fe.getClass().getSimpleName());
	}
}
This is a FireEngine instance.
This is a Car instance.
This is a Object instance.
a210719.FireEngine
FireEngine

실제 인스턴스와 같은 타입의 instanceof연산 이외에 조상타입의 instanceof연산에도 true를 결과로 얻으며, instanceof연산의 결과가 true라는 것은 검사한 타입으로의 형변환을 해도 아무런 문제가 없다.

어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

 

 

 

참조변수와 인스턴스의 연결

조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우는 다른 결과를 얻는다.

메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.

static메서드는 static변수처럼 참조변수의 타입에 영향을 받는다. 참조변수의 타입에 영향을 받지 않는 것은 인스턴스메서드 뿐이다. 그래서 static메서드는 반드시 참조변수가 아닌 클래스이름.메서드()로 호출해야 한다.

 

멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.

 중복 저의되지 않은 경우, 조상타입의 참조변수를 사용했을 때와 자손타입의 참조변수를 사용했을 때의 차이는 없다.

 

 

 

매개변수의 다형성

참조변수의 다형적인 특징은 매개변수에도 적용된다. 

class Product{
	int price;
	int bonusPoint;
	
	Product(int price) {
		this.price = price;
		bonusPoint = (int)(price/10.0);
	}
}
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 Buyer {
	int money = 1000;
	int bonusPoint = 0;
	
	void buy(Product p) {
		if(money < p.price) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
			return;
		}
		
		money -= p.price;
		bonusPoint += p.bonusPoint;
		System.out.println(p + "을/를 구입하셨습니다.");
	}
}
class PolyArgumentTest {
	public static void main(String[] args) {
		Buyer b = new Buyer();
		
		b.buy(new Tv());
		b.buy(new Computer());
		
		System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
		System.out.println("현재 보너스 점수는" + b.bonusPoint + "점입니다.");
		
	}
}
Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
현재 남은 돈은 700만원입니다.
현재 보너스 점수는30점입니다.

고객(Buyer)이 buy(Product p)메서드를 이용해서 Tv와 Computer를 구입하고, 고객의 잔고와 보너스점수를 출력하는 예제이다.

 

 

 

여러 종류의 객체를 배열로 다루기

조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다. 또는 묶어서 다루고싶은 객체드르이 상속관계를 따져서 가장 가까운 공통조상 클래스 타입의 참조변수 배열을 생성해서 객체들을 저장하면 된다.

import java.util.*;
	class Product {
		int price;
		int bonusPoint;
		
		Product(int price) {
			this.price = price;
			bonusPoint = (int)(price/10.0);
		}
		
		Product() {
			price = 0;
			bonusPoint = 0;
		}
	}



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

class Buyer {
	int money = 1000;
	int bonusPoint = 0;
	Vector<Product> item = new Vector<Product>();
	
	void buy(Product p) {
		if(money < p.price) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
			return;
		}
		money -= p.price;
		bonusPoint += p.bonusPoint;
		item.add(p);
		System.out.println(p + "을/를 구입하셨습니다.");
	}
	
	void refund(Product p) {
		if(item.remove(p)) {
			bonusPoint -= p.bonusPoint;
			System.out.println(p + "을/를 반품하셨습니다.");
		} else { System.out.println("구입하신 제품 중 해당 제품이 없습니다.");
	}
}
	
void summary() {
	int sum = 0;
	String itemList ="";
	
	if(item.isEmpty()) {
		System.out.println("구입하신 제품이 없습니다.");
		return;
	}
	
	for(int i = 0; i < item.size();i++) {
		Product p = (Product)item.get(i);
		sum += p.price;
		itemList += (i==0) ? "" + p : ", " + p;
	}
	System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
	System.out.println("구입하신 제품은 "+ itemList + "입니다.");
}
}

public class PolyArgumentTest3 {
	public static void main(String[] args) {
		Buyer b = new Buyer();
		Tv tv = new Tv();
		Computer com = new Computer();
		Audio audio = new Audio();

		b.buy(tv);
		b.buy(com);
		b.buy(audio);
		b.summary();
		System.out.println();
		b.refund(com);
		b.summary();
	}
}
Tv을/를 구입하셨습니다.
computer을/를 구입하셨습니다.
Audio을/를 구입하셨습니다.
구입하신 물품의 총금액은 350만원입니다.
구입하신 제품은 Tv, computer, Audio입니다.

computer을/를 반품하셨습니다.
구입하신 물품의 총금액은 150만원입니다.
구입하신 제품은 Tv, Audio입니다.

문자여과 참조변수의 덧셈(결합연산)은 참조변수에 toString()을 호출해서 문자열을 얻어 결합한다. 그래서 위 예제에 나오는 "+p"는 +p.toString()이 되고, 만일 p.toString()의 결과가 Audio라면 +Audio가 되어 Audio가 추가된다.

제어자(modifier)는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다.

제어자의 종류는 크게 접근제어자와 그 외의 제어자로 나눌 수 있다.

접근 제어자 public, protected, default, private
그		외 static, final, abstract, native, transient, synchronized, volatile, strictfp

제어자는 클래스나 멤버 변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하다.

단, 접근제어자는 네 가지 중 하나만 선택해서 사용할 수 있다.

제어자들 간의 순서는 관계없지만 주로 접근 제어자를 제일 왼쪽에 놓는 경향이 있다.

 

static - 클래스의, 공통적인

static은 클래스의 또는 공통적인 의미를 가지고 있다. 인스턴스 변수는 하나의 클래스로부터 생성되었더라도 각기 다른 값을 유지하지만 클래스 변수(static멤버 변수)는 인스턴스에 관계없이 같은 값을 가진다.

하나의 변수를 모든 인스턴스가 공유하기 때문이다.

 static이 붙은 변수와 메서드, 그리고 초기화 블록은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다.

인스턴스 메서드와 static메서드의 근본적인 차이는 메서드 내에서 인스턴스 멤버를 사용하는가의 여부에 있다.

static이 사용될 수 있는 곳 - 멤버변수, 메서드, 초기화블럭

 

제어자 대상 의미
static 멤버변수 - 모든 인스턴스에 공통적으로 사용되는 클래스변수가 된다.
- 클래스변수는 인스턴스를 생성하지 않고도 사용 가능하다.
- 클래스가 메모리에 로드될 때 생성된다.
메서드 - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다.
- static메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없다.

 

인스턴스 멤버를 사용하지 않는 메서드는 static을 붙여서 static메서드로 선언하는 것을 고려해보도록 하자.

가능하다면 static메서드로 하는 것이 인스턴스를 생성하지 않고도 호출이 가능해서 더 편리하고 속도도 더 빠르다.

static초기화 블록은 클래스가 메모리에 로드될 때 단 한 번만 수행되며, 주로 클래스 변수(static변수)를 초기화하는데 주로 사용된다.

 

 

final - 마지막의, 변경될 수 없는

final은 마지막의 또는 변경될 수 없는의 의미를 가지고 있으며 거의 모든 대상에 사용될 수 있다.

변수에 사용되면 값을 변경할 수 없는 상소가 되며, 메서드에 사용되면 오버 라이딩을 할 수 없게 되고 클래스에 사용되면 자신을 확장하는 자손 클래스를 정의하지 못하게 된다.

final이 사용될 수 있는 곳 - 클래스, 메서드, 멤버변수, 지역변수

 

제어자 대상 의미
final 클래스 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다.
그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다.
메서드 변경될 수 없는 메서드. final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.
멤버변수 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다.
지역변수

대표적인 final클래스로는 String과 Math가 있다.

 

final class FinalTest {	// 조상이 될 수 없는 클래스
	final int MAX_SIZE = 10;	// 값을 변경할 수 없는 멤버변수(상수)

	final void getMaxSize() {	// 오버라이딩할 수 없는 메서드(변경불가)
		final int LV = MAX_SIZE;	// 값을 변경할 수 없는 지역변수(상수)
		return MAX_SIZE;
	}
}

 

 

생성자를 이용한 final멤버 변수의 초기화

final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스 변수의 경우 생성자에서 초기화되도록 할 수 있다.

클래스 내에 매개변수를 갖는 갖는 생성자를 선언하여, 인스턴스를 생성할 때 final이 붙은 멤버 변수를 초기화하는데 필요한 값을 생성자의 매개변수로부터 제공받는 것이다.

 이 기능을 활용하면 각 인스턴스마다 final이 붙은 인스턴스변수는 모든 인스턴스에서 같은 값을 가져야만 할 것이다.

 

 

abstract - 추상의, 미완성의

abstract는 메서드의 선언부만 작성하고 실제 수행 내용은 구현하지 않은 추상 메서드를 선언하는 데 사용된다.

그리고 클래스에 사용되어 클래스 내에 추상 메서드가 존재한다는 것을 쉽게 알 수 있게 한다.

abstract가 사용될 수 있는 곳 - 클래스, 메서드
제어자 대상 의미
abstract 클래스 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
메서드 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.

추상 클래스는 아직 완성되지 않은 메서드가 존재하는 미완성 설계도 이므로 인스턴스를 생성할 수 없다.

 

드물지만 추상 메서드가 없는 클래스, 즉 완성된 클래스도 abstract를 붙여서 추상 클래스로 만드는 경우도 있다.

이 클래스 자체로는 쓸모가 없지만, 다른 클래스가 이 클래스를 상속받아서 일부의 원하는 메서드만 오버 라이딩해도 된다는 장점이 있다.

 

 

접근 제어자(access modifier)

접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다.

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

private	같은 클래스 내에서만 접근이 가능하다.
default	같은 패키지 내에서만 접근이 가능하다.
protected 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능하다.
public	접근 제한이 전혀 없다.
제어자 같은 클래스 같은 패키지 자손클래스 전체
클래스 public, (default)
메서드 public, protected, (default), private
멤버변수
지역변수 없 음

 

접근 제어자를 이용한 캡슐화

클래스나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유는 클래스의 내부에 선언된 데이터를 보호하기 위해서이다.

데이터가 유효한 값을 유지하도록, 또는 비밀번호와 같은 데이터를 외부에서 함부로 변경하지 못하도록 하기 위해서는 외부로부터의 접근을 제한하는 것이 필요하다.

이것을 데이터 감추기(data hiding)라고 하며, 객체지향 개념의 캡슐화(encapsulation)에 해당하는 내용이다.

또 다른 이유는 클래스 내에서만 사용되는, 내부 작업을 위해 임시로 사용되는 멤버 변수나 부분 작업을 처리하기 위한 메서드 등의 멤버들을 클래스 내부에 감추기 위해서이다.

외부에서 접근할 필요가 없는 멤버들을 private으로 지정하여 외부에 노출시키지 않음으로써 복잡성을 줄일 수 있다.

 

접근 제어자를 사용하는 이유

- 외부로부터 데이터를 보호하기 위해서
- 외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해서

 

get으로 시작하는 메서드는 단순히 멤버변수의 값을 반환하는 일을 하고, set으로 시작하는 메서드는 매개변수에 지저 된 값을 검사하여 조건에 맞는 값일 때만 멤버 변수의 값을 변경하도록 작성되어 있다.

 만일 상속을 통해 확장될 것이 예상되는 클래스라면 멤버에 접근 제한을 주되 자손 클래스에서 접근하는 것이 가능하도록 하기 위해 private대신 protected를 사용한다. private이 붙은 멤버는 자손 클래스에서도 접근이 불가능하기 때문이다.

보통 멤버변수의 값을 읽는 메서드의 이름을 get멤버 변수 이름으로 하고, 멤버 변수의 값을 변경하는 메서드의 이름을 set멤버 변수로 한다.

get으로 시작ㅎ나는 메서드를 getter라고 하고 set으로 시작하는 메서드를 setter라고 한다.

 

 

생성자의 접근 제어자

생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다.

 생성자의 접근제어자를 private으로 지정하면 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 된다. 그래도 클래스 내부에서는 인스턴스를 생성할 수 있다.

대신 인스턴스를 생성해서 반환해주는 public메서드를 제공함으로써 외부에서 이 클래스의 인스턴스를 사용하도록 할 수 있다. 이 메서드는 public인 동시에 static 이어야 한다.

이처럼 생성자를 통해 직접 인스턴스를 생성하지 못하게 하고, public메서드를 통해 인스턴스에 접근하게 함으로써 사용할 수 있는 인스턴스의 개수를 제한할 수 있다.

또한 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없다. 왜냐하면, 자손 클래스의 인스턴스를 생성할 때 조상 클래스의 생성자를 호출해야만 하는데, 생성자의 접근 제어자가 private이므로 자손 클래스에서 호출하는 것이 불가능하기 때문이다.

 그래서 클래스 앞에 final을 추가하여 상속하지 못하는 클래스를 알리는 것이 좋다.

 

 

 

 

제어자(modifier)의 조합

대상 사용가능한 제어자
클래스 public, (default), final, abstract
메서드 모든 접근제어자, final, abstract, static
멤버변수 모든 접근 제어자, final, static
지역변수 final

 

제어자를 조합해서 사용할 때 주의해야 할 사항에 대해 정리해 보았다.

1. 메서드에 static과 abstract를 함께 사용할 수 없다.
static메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다.

2. 클래스에 abstract와 final을 동시에 사용할 수 없다.
클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 
abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문이다.

3. abstract메서드의 접근 제어자가 private일 수 없다.
abstract메서드는 자손클래스에서 구현해주어야 하는데 접근 제어자가 private이면,
자손클래스에서 접근할 수 없기 때문이다.

4. 메서드에 private과 final을 같이 사용할 필요는 없다.
접근제어자가 private인 메서드는 오버라이딩될 수 없기 때문이다. 이 둘 중 하나만 사용해도 의미가 충분하다

 

패키지(package)

패키지란, 클래스의 묶음이다. 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있으며, 서로 관련된 클래스끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리할 수 있다.

클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉터리이다.

디렉터리가 하위 디렉터리를 가질 수 있는 것처럼, 패키지도 다른 패키지를 포함할 수 있으며 점'.'으로 구분한다.

예를 들면 java.lang패키지에서 lang패키지는 java패키지의 하위 패키지이다.

- 하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용한다.
- 모든 클래스는 반드시 하나의 패키지에 속해야 한다.
- 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.

 

 

패키지의 선언

패키지를 선언하는 것은 아주 간단하다. 클래스나 인터페이스의 소스파일의 맨 위에 패키지라고 적어주면 된다.

package 패키지명;

위와 같은 패키지 선언문은 반드시 소스파일에서 주석과 공백을 제외한 첫 번째 문장이어야 하며, 하나의 소스파일에서 단 한 번만 선언될 수 있다. 해당 소스파일에 포함된 모든 클래스나 인터페이스는 선언된 패키지에 속하게 된다. 

 패키 지명은 대소문자를 모두 허용하지만, 클래스명과 쉽게 구분하기 위해서 소문자로 하는 것을 원칙으로 하고 있다.

큰 프로젝트나 Java API와 같은 클래스 라이브러리를 작성하는 경우에는 미리 패키지를 구성하여 적용해야 한다.

 

 

import문

소스코드를 작성할 때 다른 패키지의 클래스를 사용하려면 패키 지명이 포함된 클래스 이름을 사용해야 한다.

하지만, 매번 패키 지명을 붙여서 작성하는 것이 불편하므로 클래스의 코드를 사용하기 전에 import문으로 사용하고자 하는 클래스의 패키지를 미리 명시해주면 소스코드에 사용되는 클래스 이름에서 패키 지명은 생략할 수 있다.

import문의 역할은 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 공개하는 것이다.

컴파일러는 import문을 통해 소스파일에 사용된 클래스들의 패키지를 알아낸 다음, 모든 클래스 이름 앞에 패키 지명을 붙여준다.

이클립스는 ctrl+shift+o를 누르면 자동으로 import문을 추가해준다.

 

 

import문의 선언

모든 소스파일(. java)에서 import문은 package문 다음에, 클래스 선언문 이전에 위치해야 한다. import문은 package문과 달리 한 소스파일에 여러 번 선언할 수 있다.

일반적인 소스파일(*. java)의 구성은 다음의 순서로 되어 있다.

1. package문
2. import문
3. 클래스 선언

import문을 선언하는 방법은 다음과 같다.

import 패키지명.클래스명;
	또는
import 패키지명.*

키워드 import와 패키 지명을 생략하고자 하는 클래스의 이름을 패키 지명과 함께 써주면 된다.

'패키 지명.*'은 같은 패키지에서 여러 개의 클래스가 사용될 때 유용하다.

실행 시 성능상의 차이는 전혀 없다.

단, 이 경우에는 하위 패키지는 포함하지 않는다.

import java.util.*;

import java.text.*;

즉 위의 두 패키지를 합친다고 아래와 같이 표현하면 안 된다.

import java.*; (이렇게 표현 X)

 

static import문

static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다. 특정 클래스의 static멤버를 자주 사용할 때 편리하다.

import static java.lang.Integer.*;
import static java.lang.Math.random;
import static java.lang.System.out;

 

System.out.println(Math.random()); > out.println(random());으로 사용이 가능하다.

import static java.lang.System.out;
import static java.lang.Math.*;

public class StaticImportEx1 {
	public static void main(String[] args) {
//		System.out.println(Math.random());
		out.println(random());
		
//		System.out.println("Math.PI :"+Math.PI);
		out.println("Math.PI :" + PI);
	}
}
0.9214332841721639
Math.PI :3.141592653589793

+ Recent posts