제어자
클래스, 변수 또는 메소드의 선언부에 함께 사용되어 부가적인 의미를 부여
final
변수에 사용 - 상수가 됨
메소드에 사용 - 오버라이딩을 할 수 없게 됨
클래스에 사용 - 확장할 수 없는 클래스가 됨, 다른 클래스의 부모클래스가 될 수 없음
|
1
2
3
4
5
6
7
8
|
final class FinalTest { // 부모가 될 수 없는 클래스
final int MAX_SIZE = 10; // 값을 변경할 수 없는 멤버변수(상수)
final void getMaxSize() { // 오버라이딩할 수 없는 메소드(변경불가)
final int LV = MAX_SIZE; // 값을 변경할 수 없는 지역변수(상수)
return MAX_SIZE;
}
}
|
cs |
abstract
메소드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상메소드를 선언하는데 사용
추상클래스는 아직 완성되지 않은 메소드가 존재하는 '미완성 설계도'이므로 인스턴스 생성불가
|
1
2
3
|
abstract class AbstractTest { // 추상클래스 (추상메소드를 포함한 클래스)
abstract void move(); // 추상메소드 (구현부가 없는 메소드)
}
|
cs |
제어자의 조합
1. 메소드에 static과 abstract를 함께 사용할 수 없다
2. 클래스에 abstract와 final을 동시에 사용할 수 없다
3. abstract메소드의 접근 제어자가 private일 수 없다
4. 메소드에 private과 final을 같이 사용할 필요는 없다
다형성(polymorphism)
여러가지 형태를 가질 수 있는 능력을 의미
-> 부모클래스 타입의 참조변수로 자식클래스의 인스턴스를 참조할 수 있도록 했다는 의미
ex)
CaptionTv c = new CaptionTv();
Tv t = new CaptionTv(); --> Tv클래스의 멤버들만 사용가능
둘다 같은 타입의 인스턴스지만 참조변수 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다
부모타입의 참조변수로 자식타입의 인스턴스를 참조할 수 있다
반대로 자식타입의 참조변수로 부모타입의 인스턴스를 참조할 수는 없다
참조변수의 형변환
자식타입 -> 부모타입(Up-casting) : 형변환 생략가능
자식타입 <- 부모타입(Down-casting) : 형변환 생략불가
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
class Car {
String color;
int door;
void drive() {
System.out.println("drive~");
}
void stop() {
System.out.println("stop!");
}
}
class FireEngine extends Car {
void water() {
System.out.println("water!!");
}
}
public class CastingTest1 {
public static void main(String[] args) {
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
fe.water();
car = fe; // 형변환, 자식타입 -> 부모타입 (형변환생략)
// car.water(); -> Car형으로는 water()메소드 호출불가
fe2 = (FireEngine)car; // 형변환, 부모타입 -> 자식타입
fe2.water();
}
}
|
cs |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class CastingTest2 {
public static void main(String[] args) {
Car car = new Car();
Car car2 = new FireEngine();
FireEngine fe = null;
car.drive();
// 에러, 부모타입의 인스턴스를 자식타입의 참조변수로 참조하는것 허용 x
// fe = (FireEngine)car;
fe = (FireEngine)car2; // 실행 o
fe.drive();
}
}
|
cs |
서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나,
참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.
그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요!
instanceof연산자
참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof연산자 사용
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class InstanceofTest {
public static void main(String[] args) {
FireEngine fe = new FireEngine();
if(fe instanceof FireEngine) {
System.out.println("This is a Fire Engine 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()); // 클래스의 이름 출력
}
}
|
cs |
출력
This is a Fire Engine instance
This is a Car instance
This is a Object instance
FireEngine
어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 뜻
참조변수와 인스턴스의 연결
멤버변수 - 참조변수의 타입에 따라 달라짐
메소드 - 부모클래스의 메소드를 자식클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메소드(오버라이딩된 메소드)가 호출
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class A {
int x = 100;
void method() {
System.out.println("Parent Method");
}
}
class B extends A {
int x = 200;
void method() { // 오버라이딩
System.out.println("Child Method");
}
}
public class BindingTest {
public static void main(String[] args) {
A a = new B();
B b = new B();
// A클래스의 참조변수이므로 A클래스의 x값 출력
System.out.println("a.x = " + a.x);
a.method(); // 오버라이딩된 B클래스의 method()
// B클래스의 참조변수이므로 B클래스의 x값 출력
System.out.println("b.x = " + b.x);
b.method();
}
}
|
cs |
출력
a.x = 100
Child Method
b.x = 200
Child Method
메소드인 method()는 참조변수의 타입에 관계없이 실제 인스턴스의 타입인 B클래스에 정의된 메소드가 호출되지만,
인스턴스변수인 x는 참조변수의 타입에 따라 달라진다
다형성 활용
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
package polymorphism;
class Product {
// 값, 보너스포인트
// 변수구성
int price;
int bonusPoint;
Product(int price) {
this.price = price;
bonusPoint = price/10;
}
}
class Computer extends Product {
// 생성자통해 값, 보너스포인트 초기화
Computer() {
super(200);
}
public String toString() {
return "Computer";
}
}
class Tv extends Product {
// 생성자통해 값, 보너스포인트 초기화
Tv() { // Tv클래스의 인스턴스 생성되면 Product클래스의 생성자 호출
super(100);
}
public String toString() {
return "TV";
}
}
class Buyer {
// 변수구성
int money = 1000;
int bonusPoint = 0;
// 메소드, 다형성을 이용
// 매개변수가 Product타입의 참조변수라는 것은,
// 매개변수로 Product클래스의 자식타입의 참조변수면 매개변수로 사용가능하다는것
void buy(Product p) {
if(money < p.price) {
System.out.println("돈이 부족해요");
return;
}
money -= p.price;
bonusPoint += p.bonusPoint;
System.out.println(p + "을/를 구입하셨습니다.");
}
}
public class PolyArgumentTest {
public static void main(String[] args) {
Buyer buyer = new Buyer();
// Product클래스의 자식클래스인 Tv, Computer클래스의 인스턴스를 매개변수로 사용
buyer.buy(new Tv());
buyer.buy(new Computer());
System.out.println("남은돈 : " + buyer.money);
System.out.println("보너스포인트 : " + buyer.bonusPoint);
}
}
|
cs |
출력
TV을/를 구입하셨습니다
COMPUTER을/를 구입하셨습니다
남은돈 : 700
보너스포인트 : 30
배열 활용
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
package polymorphism;
class Product {
// 값, 보너스포인트
// 변수구성
int price;
int bonusPoint;
Product(int price) {
this.price = price;
bonusPoint = price/10;
}
}
class Computer extends Product {
// 생성자통해 값, 보너스포인트 초기화
Computer() {
super(200);
}
public String toString() {
return "Computer";
}
}
class Tv extends Product {
// 생성자통해 값, 보너스포인트 초기화
Tv() { // Tv클래스의 인스턴스 생성되면 Product클래스의 생성자 호출
super(100);
}
public String toString() {
return "TV";
}
}
class Buyer {
// 변수구성
int money = 1000;
int bonusPoint = 0;
Product[] item = new Product[10]; // Product형 담을 배열
int i=0; // 배열 인덱스 참조할 변수
// 메소드, 다형성을 이용
// 매개변수가 Product타입의 참조변수라는 것은,
// 매개변수로 Product클래스의 자식타입의 참조변수면 매개변수로 사용가능하다는것
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;
itemList += item[i] + ", ";
sum += item[i].price;
}
System.out.println("구매내역은 : " + itemList);
System.out.println("구매총액은 : " + sum);
}
}
public class PolyArgumentTest {
public static void main(String[] args) {
Buyer buyer = new Buyer();
// Product클래스의 자식클래스인 Tv, Computer클래스의 인스턴스를 매개변수로 사용
buyer.buy(new Tv());
buyer.buy(new Computer());
buyer.summary();
}
}
|
cs |
Buyer클래스에 Product를 담는 배열을 생성해 구매한 항목을 배열에 담고, 구매내역과 구매총액을 배열을 통해 계산했다
ArrayList활용
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
package polymorphism;
class Product {
// 값, 보너스포인트
// 변수구성
int price;
int bonusPoint;
Product(int price) {
this.price = price;
bonusPoint = price/10;
}
}
class Computer extends Product {
// 생성자통해 값, 보너스포인트 초기화
Computer() {
super(200);
}
public String toString() {
return "Computer";
}
}
class Tv extends Product {
// 생성자통해 값, 보너스포인트 초기화
Tv() { // Tv클래스의 인스턴스 생성되면 Product클래스의 생성자 호출
super(100);
}
public String toString() {
return "TV";
}
}
class Buyer {
// 변수구성
int money = 1000;
int bonusPoint = 0;
Product[] item = new Product[10]; // Product형 담을 배열
int i=0; // 배열 인덱스 참조할 변수
// 메소드, 다형성을 이용
// 매개변수가 Product타입의 참조변수라는 것은,
// 매개변수로 Product클래스의 자식타입의 참조변수면 매개변수로 사용가능하다는것
void buy(Product p) {
if(money < p.price) {
System.out.println("돈이 부족해요");
return;
}
money -= p.price;
bonusPoint += p.bonusPoint;
list.add(p); // 리스트에 구매항목 추가
System.out.println(p + "을/를 구입했어요.");
}
void refund(Product p) {
if(list.remove(p)) { // 해당 품목 삭제
// 금액과 보너스포인트 복구
money += p.price;
bonusPoint -= p.bonusPoint;
} else {
System.out.println("해당 품목이 없어요");
}
}
void summary() {
int sum=0;
String itemList = "";
for(int i=0; i<list.size(); i++) {
Product p = (Product)list.get(i);
sum += p.price;
itemList += (i==0) ? "" + p : ", " + p;
}
System.out.println("구매내역은 : " + itemList);
System.out.println("구매총액은 : " + sum);
}
}
public class PolyArgumentTest {
public static void main(String[] args) {
Buyer buyer = new Buyer();
Computer com = new Computer();
Tv tv = new Tv();
Audio audio = new Audio();
// Product클래스의 자식클래스인 Tv, Computer클래스의 인스턴스를 매개변수로 사용
buyer.buy(com);
buyer.buy(tv);
buyer.buy(audio);
buyer.summary();
System.out.println();
buyer.refund(audio);
buyer.summary();
}
}
|
cs |
추상클래스
미완성 셜계도
미완성 메소드(추상메소드)를 포함하고 있다는 의미
추상클래스로 인스턴스 생성 불가
추상클래스는 상속을 통해서 자식클래스에 의해서만 완성될 수 있다
새로운 클래스를 작성하는데 있어서 바탕이 되는 부모클래스로서 중요한 의미를 가짐
새로운 클래스를 작성할 때 아무것도 없는 상태에서 시작하는 것보다
완전하지는 못하더라도 어느 정도 틀을 갖춘 상태에서 시작하는 것이 나음
ex)
서로 다른 TV의 설계도를 따로 그리는 것보다,
이들의 공통부분만을 그린 미완성 설계도를 만들어 놓고 이 설계도를 이용해 각각의 설계도를 완성하는 원리
추상메소드
메소드의 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔것
메소드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 부모 클래스에서 선언부만 작성,
실제 내용은 상속받는 클래스에서 구현
추상클래스로부터 상속받는 자식클래스는 오버라이딩을 통해 부모인 추상클래스의 추상메소드를 모두 구현해야함
만약 부모로부터 상속받은 추상메소드를 모두 구현하지 않는다면, 자식클래스도 추상클래스로 지정해야함
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
abstract class Player { // 추상클래스
abstract void play(int pos)); // 추상메소드
abstract void stop(); // 추상메소드
}
class AudioPlayer extends Player {
void play(int pos) { // 추상메소드 구현
/* 내용생략 */
}
void stop() { // 추상메소드 구현
/* 내용생략 */
}
}
// 추상메소드를 전부 구현하지 않았기때문에 추상클래스로
abstract class AbstractPlayer extends Player {
void play(int pos) { // 추상메소드 구현
/* 내용생략 */
}
|
cs |
추상클래스의 작성
여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 작성
기존의 클래스의 공통적인 부분을 뽑아서 추상클래스로 만들어 상속하도록 하기도 함
상속계층도를 내려갈수록 클래스는 기능이 점점 추가되고 세분화
상속계층도를 올라갈수록 클래스는 추상화의 정도가 심해지고 공통요소만 남게됨
추상화 - 클래스간의 공통점을 찾아내서 공통의 부모를 만드는 작업
구체화 - 상속을 통해 클래스를 구현, 확장하는 작업
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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() {
/* 선택된 대상을 내린다 */
}
}
|
cs |
인터페이스
인터페이스는 일종의 추상클래스
오직 추상메소드와 상수만을 멤버로 가질 수 있음
추상클래스를 부분적으로 완성된 '미완성 설계도'라고 한다면
인터페이스는 구현된 것이 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라고 할 수 있다
다른 클래스를 작성하는데 도움을 줄 목적으로 작성됨
인터페이스 작성
- 모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있다
- 모든 메소드는 public abstract이어야 하며, 이를 생략할 수 있다 (static메소드와 디폴트 메소드는 예외)
|
1
2
3
4
|
interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
public abstract 메소드이름(매개변수목록);
}
|
cs |
ex)
|
1
2
3
4
5
6
7
8
9
|
interface PlayingCard {
public static final int SPADE = 4;
final int DIAMOND = 3; // public static final int DIAMOND = 3;
static int HEART = 2; // public static final int HEART = 2;
int CLOVER = 1; // public static final int CLOVER = 1;
public abstract String getCardNumber();
String getCardKind(); // public abstract String getCardKind();
}
|
cs |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package interfaces;
interface Movable {
void move(int x, int y);
}
interface Attackable {
void attack(Unit u);
}
// Movable, Attackable인터페이스의 상속받음
interface Fightable extends Movable, Attackable {
}
class Unit {
int currentHP;
int x;
int y;
}
// Unit클래스, Fightable인터페이스의 상속받음
class Fighter extends Unit implements Fightable {
// 인터페이스의 메소드 구현
public void move(int x, int y) {
/* 내용 생략 */
}
public void attack(Unit u) {
/* 내용 생략 */
}
}
public class FighterTest {
public static void main(String[] args) {
Fighter f = new Fighter();
if(f instanceof Unit)
System.out.println("f는 Unit클래스의 자손입니다.");
if(f instanceof Fightable)
System.out.println("f는 Fightable인터페이스를 구현했습니다.");
if(f instanceof Movable)
System.out.println("f는 Movable인터페이스를 구현했습니다.");
if(f instanceof Attackable)
System.out.println("f는 Attackable인터페이스를 구현했습니다.");
if(f instanceof Object)
System.out.println("f는 Object클래스의 자손입니다.");
}
}
|
cs |
출력
f는 Unit클래스의 자손입니다.
f는 Fightable인터페이스를 구현했습니다.
f는 Movable인터페이스를 구현했습니다.
f는 Attackable인터페이스를 구현했습니다.
f는 Object클래스의 자손입니다.
인터페이스를 이용한 다중상속
두 클래스로부터 상속을 받을 수 없으므로,
한 쪽만 선택하여 상속받고 나머지 한 쪽은 클래스 내에 포함시켜서 내부적으로 인스턴스를 생성해 사용
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
package interfaces;
class Tv {
protected boolean power;
protected int channel;
protected int volume;
public void power() { power = !power; }
public void channelUp() { ++channel; }
public void channelDown() { --channel; }
public void volumeUp() { ++volume; }
public void volumeDown() { --volume; }
}
class VCR {
protected int counter;
public void play() {
// tape재생
}
public void stop() {
// 재생멈춤
}
public void reset() {
counter = 0;
}
public int getCounter() {
return counter;
}
public void setCounter(int c) {
counter = c;
}
}
// VCR클래스를 인터페이스화
interface IVCR {
public void play();
public void stop();
public void reset();
public int getCounter();
public void setCounter(int c);
}
// Tv클래스, IVCR인터페이스 상속받음
public class TVCR extends Tv implements IVCR {
// VCR클래스를 멤버변수로 포함시켜서 사용
VCR vcr = new VCR();
@Override
public void play() {
vcr.play();
}
@Override
public void stop() {
vcr.stop();
}
@Override
public void reset() {
vcr.reset();
}
@Override
public int getCounter() {
return vcr.getCounter();
}
@Override public void setCounter(int c) {
vcr.setCounter(c);
}
}
|
cs |
인터페이스를 이용한 다형성
인터페이스는 인터페이스를 구현한 클래스의 부모라고 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조 할 수 있고, 인터페이스 타입으로 형변환도 가능하다
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
package interfaces;
interface Parseable {
public abstract void parse(String fileName);
}
// Parseable인터페이스의 상속받음
class XMLParser implements Parseable {
public void parse(String fileName) {
System.out.println(fileName + "- XML parsing completed");
}
}
//Parseable인터페이스의 상속받음
class HTMLParser implements Parseable {
public void parse(String fileName) {
System.out.println(fileName + "- HTML parsing completed");
}
}
class ParserManager {
// 반환형이 Parseable인터페이스 형, 인터페이스를 이용한 다형성
public static Parseable getParser(String type) {
if(type.equals("XML")) {
return new XMLParser();
} else {
Parseable p = new HTMLParser();
return p; // 리턴타입이 인터페이스, 인터페이스를 구현한 클래스의 인스턴스 반환
}
}
}
public class ParserTest {
public static void main(String[] args) {
Parseable parser = ParserManager.getParser("XML"); // Parseable paser = new XMLParser();
parser.parse("document.xml");
parser = ParserManager.getParser("HTML");
parser.parse("document.xml");
}
}
|
cs |
출력
document.xml - XML parsing completed
document.xml - HTML parsing completed
--> 인터페이스를 이용한 다형성을 구현한 예제
XMLParser와 HTMLParser클래스가 Parseable인터페이스를 구현하므로,
Parseable인터페이스가 두 클래스의 부모격이 된다
getParser()의 반환형이 Parseable이므로 인터페이스를 활용해 이를 구현한 클래스들을 참조할 수 있다
'아카이브 > 자바의 정석' 카테고리의 다른 글
| 8장 예외처리 20201009 (0) | 2020.10.09 |
|---|---|
| 7장 20200926 (0) | 2020.09.26 |
| 7장 20200924 (0) | 2020.09.24 |
| 7장 상속 20200921 (0) | 2020.09.21 |
| 6장 객체지향프로그래밍(2) 20200919 (0) | 2020.09.19 |