클래스의 자동 타입 변환이 필요한 이유
자식 타입으로 사용하면 될텐데, 부모 타입으로 변환해서 사용하는 이유는??
다형성을 구현하는 기술적 방법 때문이다.
다형성 : 동일한 타입을 사용하지만, 다양한 결과가 나오는 성질
주로 필드의 값을 다양화함으로써 실행 결과가 다르게 나오도록 구현한다.
필드 타입은 변함이 없지만,
실행 도중에 어떤 객체를 필드로 저장하느냐에 따라 실행 결과가 달라질 수 있다. (필드의 다형성)
프로그램은 수 많은 객체들이 서로 연결되고 각자의 역할을 한다.
이 객체들은 다른 객체로 교체될 수 있어야 한다. (부품 처럼!)
ex) 자동차 클래스에 포함된 타이어 클래스
자동차 클래스를 처음 설계할 때 사용한 타이어 객체는 언제든지 성능 좋은 타이어 객체로 교체할 수 있어야 한다.
새로 교체되는 타이어 객체는 기존 타이어와 사용 방법은 동일하지만, 실행 결과는 더 좋게 나와야 한다.
이것을 프로그램으로 구현하기 위해 상속, 오버라이딩, 타입 변환을 이용하는 것이다.
- 상속 : 부모 클래스를 상속하는 자식 클래스는 부모가 가지고 있는 필드와 메소드를 가지고 있으므로
사용 방법이 동일하다.
- 오버라이딩 : 자식 클래스는 부모의 메소드를 오버라이딩(재정의) 해서 메소드의 실행 내용을
변경함으로써 더 우수한 실행 결과가 나올 수 있게 한다.
- 타입 변환 (교체 가능성) : 자식 타입을 부모 타입으로 변환할 수 있다. (다형성)
ex)
Car.java
class Car {
//필드
Tire frontLeftTire = new Tire();
Tire frontRightTire = new Tire();
Tire backLeftTire = new Tire();
Tire backRightTire = new Tire();
//메소드
void run() { . . . }
}
Car 클래스에는 4개의 Tire 타입 필드를 가지고 있다.
Car 클래스로부터 Car 객체를 생성하면, 4 개의 Tire 필드에 각각 하나씩 Tire 객체가 들어가게 된다.
만약 frontRightTire, backLeftTire 를 HankookTire, KumboTire 로 교체할 필요성이 생긴 경우,
아래 코드처럼 교체할 수 있다.
Car myCar = new Car(); // myCar 객체 생성
myCar.frontRightTire = new HankookTire(); // 새로운 객체 HankookTire 로 교체
myCar.backLeftTire = new KumhoTire(); // 새로운 객체 KumhoTire 로 교체
myCar.run();
Tire 클래스 타입인 frontRightTire, backLeftTire 는 원래 Tire 객체가 저장되어야 하지만,
Tire 의 자식 객체가 저장되어도 문제가 없다.
왜냐하면, 자식 타입은 부모 타입으로 자동 타입 변환되기 때문이다.
frontRightTire 와 backLeftTire 에 Tire 자식 객체가 저장되어도 Car 객체는 Tire 클래스에 선언된 필드와 메소드만 사용하므로 전혀 문제가 되지 않는다.
HankookTire 와 KumhoTire 는 부모인 Tire 의 필드와 메소드를 가지고 있기 때문이다. (상속)
Car 객체에 run( ) 메소드가 있고, run( ) 메소드는 각 Tire 객체의 roll( ) 메소드를
아래와 같이 호출한다고 가정해보면,,
void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
frontRightTire 와 backLeftTire 를 교체하기 전에는 Tire 객체의 roll( ) 메소드가 호출되지만,
HankookTire 와 KumhoTire 로 교체된 후에는 HankookTire 와 KumhoTire 객체의 roll( ) 메소드가 호출된다.
만약, HankookTire, KumhoTire 가 roll( ) 메소드를 재정의(오버라이딩) 했다면,
재정의된 roll( ) 메소드가 호출된다.
자동 타입 변환을 이용해서 Tire 필드값을 교체함으로써
Car 의 run( ) 메소드 수정 없이도 다양한 roll( ) 메소드의 실행 결과를 얻게 된다. (필드의 다형성)
위의 내용들을 예제로 작성!!
1. Tire 클래스
- 필드 : maxRotation(최대 회전수), accumulatedRotation(누적 회전수), location(타이어의 위치)
최대 회전수만큼 도달하면 타이어가 펑크난다고 가정한다. (타이어의 수명)
누적 회전수 : 타이어가 1번 회전할 때 마다 1씩 증가되는 필드로
누적 회적수가 최대 회전수값 만큼 도달하면 타이어는 펑크난다.
타이어 위치 : 앞오른쪽, 앞왼쪽, 뒤오른쪽, 뒤왼쪽 구분하는 필드
- 생성자 : 타이어의 위치와 최대 회전수를 매개값으로 받아서 각 필드에 저장한다. (필드 초기화)
- 메소드 : roll( )
타이어를 1회전 시키는 메소드로 1번 실행할 때 마다 누적 회전수를 1씩 증가시킨다.
누적 회전수가 최대 회전수보다 작을 경우, 남은 회전수를 출력하고
최대 회전수가 되면 펑크를 출력시킨다.
리턴 타입 : boolean ( 정상 회전시 true / 펑크 시 false 반환)
Tire.java
public class Tire {
// 필드
public int maxRotation; // 최대 회전수 (타이어 수명)
public int accumulatedRotation; // 누적 회전수
public String location; // 타이어 위치
// 생성자
public Tire(String location, int maxRotation) { // 필드 초기화
this.location = location;
this.maxRotation = maxRotation;
}
// 메소드
public boolean roll() {
++accumulatedRotation; // 누적 회전수 1 증가
if(accumulatedRotation < maxRotation) { // 누적 < 최대 일 경우
System.out.println(location + " Tire 수명 : "+ (maxRotation - accumulatedRotation) + "회");
return true;
} else { // 누적 <= 최대 일 경우
System.out.println("*** " + location + " Tire 펑크 ***");
return false;
}
}
}
2. Car 클래스
- 필드 : 4 개의 타이어 (frontLeftTire, frontRightTire, backLeftTire, backRightTire)
Tire 객체 생성 시 타이어의 위치와 최대 회전수를 생성자의 매개값으로 지정
frontLeftTire 필드일 경우, 최대 회전수 6 을 주어, 6 회전 시 타이어 펑크!
- 생성자 : 기본 생성자
- 메소드 : run( )
4 개의 타이어를 1 회전 시키는 메소드
Tire 객체의 roll( ) 메소드를 호출해서 리턴값이 false (펑크) 이면 stop( ) 메소드를 호출하고,
해당 타이어 번호(위치) 를 리턴한다.
stop( )
타이어가 펑크 날 때 자동차를 멈추는 메소드
Car.java
public class Car {
// 필드
Tire frontLeftTire = new Tire("앞왼쪽", 6); // 위치 이름과 최대회전수로 객체 생성 및 초기화
Tire frontRightTire = new Tire("앞오른쪽", 2);
Tire backLeftTire = new Tire ("뒤왼쪽", 3);
Tire backRightTire = new Tire ("뒤오른쪽", 4);
// 생성자
// 메소드
int run() {
System.out.println("[자동차가 달립니다.]");
//모든 타이어 1회전, 각각 roll() 메소드 호출, 펑크 시 stop() 호출하고 위치 리턴
if(frontLeftTire.roll() == false) { stop(); return 1; }
if(frontRightTire.roll() == false) { stop(); return 2; }
if(backLeftTire.roll() == false) { stop(); return 3; }
if(bakcRightTire.roll() == false) { stop(); return 4; }
return 0;
}
void stop() {
System.out.println("[자동차가 멈춥니다.]");
}
}
3. HankookTire, KumhoTire 클래스
이 두 클래스는 Tire 클래스를 상속받는다.
- 필드 : 없음
- 생성자 : 타이어의 위치, 최대 회전수를 매개값으로 받아서 부모인 Tire 클래스의 생성자 호출 시
넘겨준다.
- 메소드 : roll( ) 메소드 오버라이딩
정상 회전과 펑크 났을 때 출력하는 내용이 다르다.
HankookTire.java
public class HankookTire extends Tire {
// 필드
// 생성자
public HankookTire(String location, int maxRotation) {
super(location, maxRotation);
}
// 메소드
@Override
public boolean roll() {
++accumulatedRotation;
if(accumulatedRotation < maxRotation) {
System.out.println(location " HankookTire 수명: "+ (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + " HankookTire 펑크 ***");
return false;
}
}
}
KumhoTire.java
public class KumhoTire extends Tire {
// 필드
// 생성자
public KumhoTire(String location, int maxRotation) {
super(location, maxRotation);
}
//메소드
@Override
public boolean roll() {
++accumulatedRotation;
if(accumulatedRotation < maxRotation) {
System.out.println(location " KumhoTire 수명: "+ (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + " KumhoTire 펑크 ***");
return false;
}
}
}
CarExample.java : 실행 클래스
public class CarExample {
public static void main(String[] args) {
Car car = new Car(); // Car 객체 생성
for(int i = 1; i < 5; i++) {
int problemLocation = car.run(); //Car 객체의 run() 메소드 5번 반복 실행
switch(problemLocation) {
case 1:
System.out.println("앞왼쪽 HankookTire로 교체");
car.frontLeftTire = new HankookTire("앞왼쪽", 15);
break;
case 2:
System.out.println("앞오른쪽 KumhoTire로 교체");
car.frontRightTire = new KumhoTire("앞오른쪽", 13);
break;
case 3:
System.out.println("뒤왼쪽 HankookTire로 교체");
car.backLeftTire = new HankookTire("뒤왼쪽", 14);
break;
case 4:
System.out.println("뒤오른쪽 KumhoTire로 교체");
car.backRightTire = new KumhoTire("뒤오른쪽", 17);
break;
}
System.out.println("================================================="); // 구분선
}
}
}
Car 객체의 해당 타이어를 새로운 HankookTire, KumhoTire 객체로 교체한다.
car. frontLeftTire = new HankookTire("앞왼쪽", 15); // 자동 타입 변환!!
교체된 이후에는 Car 객체의 run( ) 메소드가 호출될 때
각 객체 HankookTire, KumhoTire 에서 재정의(오버라이딩) 된 roll( ) 메소드가 실행된다.
하나의 배열로 객체 관리
Car 클래스의 4 개의 타이어 객체를 4 개의 필드로 각각 지정했다.
동일한 타입의 값들은 배열로 관리하는 것이 유리하다.
따라서 타이어 객체들도 타이어 배열로 관리하는 것이 코드를 더 깔끔하게 할 수 있다.
class Car {
Tire frontLeftTire = new Tire("앞왼쪽", 6);
Tire frontRightTire = new Tire("앞오른쪽", 2);
Tire backLeftTire = new Tire("뒤왼쪽", 3);
Tire backRightTire = new Tire("뒤오른쪽", 4);
}
class Car {
Tire[] tires = {
new Tire("앞왼쪽", 6),
new Tire("앞오른쪽", 2),
new Tire("뒤왼쪽", 3),
new Tire("뒤오른쪽", 4)
};
}
frontLeftTire = tires[0]
frontRightTire = tires[1]
backLeftTire = tires[2]
backRightTire = tires[3]
으로 각각 표현된다.
ex) 인덱스 1을 이용해서 앞오른쪽 타이어를 KumhoTire로 교체하려면 아래 처럼 코드를 작성한다.
tires[1] = new KumhoTire("앞오른쪽", 13);
// 기존 : Tire frontRightTire = new KumhoTire("앞오른쪽", 13);
tires 배열의 각 항목은 Tire 타입이므로 자식 객체인 KumhoTire 를 대입하면 자동 타입 변환이 발생한다.
배열의 타입은 Tire 이지만, 실제 저장 항목이 Tire의 자식 객체라면 모두 가능하다.
상속 관계에 있는 객체들을 배열로 관리하면 제어문에서 쉽게 사용할 수 있다.
ex) 전체 타이어의 roll( ) 메소드를 호출하는 Car 클래스의 run( ) 메소드는 아래와 같이 for 문으로 작성할 수 있다.
int run() {
System.out.println("[자동차가 달립니다.]");
for(int i = 0; i < tires.length; i++) {
if(tires[i].roll() == false) { // if 문을 여러개 쓸 필요가 없다!!
stop();
return (i + 1);
}
}
return 0;
}
배열 관리로 수정된
Car.java
public class Car {
// 필드
Tire[] tires = {
new Tire("앞왼쪽", 6),
new Tire("앞오른쪽", 2),
new Tire("뒤왼쪽", 3),
new Tire("뒤오른쪽", 4)
};
// 메소드
int run() {
System.out.println("[자동차가 달립니다.]");
for(int i = 0; i < tires.length; i++) {
if(tires[i].roll() == false) {
stop();
return (i + 1);
}
}
return 0;
}
void stop() {
System.out.println("[자동차가 멈춥니다.]");
}
}
CarExample.java : 실행 클래스 아래와 같이 간결하게 바꿀 수 있다.
public class CarExample {
public static void main(String[] args) {
Car car = new Car();
for(int i = 1; i <= 5; i++) {
int problemLocation = car.run();
if(problemLocation != 0) {
System.out.println(car.tires[problemLocation - 1].location + " HankookTire 로 교체");
car.tires[problemLocation - 1] = new HankookTire(car.tires[problemLocation - 1].location, 15);
}
System.out.println(" ---------------------------- ");
}
}
}
problemLocation 은 1 ~ 4 값을 가지므로 (return i + 1)
Car 클래스의 해당 타이어 인덱스는problemLocation 에서 1을 뺀 0 ~ 3 까지이다.
car.tires[problemLocation - 1] = new HankookTire(car.tires[problemLocation - 1].location, 15);
타이어 펑크 위치 정보를 읽어서 Car의 tires 배열 항목으로 HankookTire를 대입한다.
'Java 기본 문법 - 참조 서적 [이것이 자바다 - 한빛미디어] > 5. 상속, 다형성' 카테고리의 다른 글
4. Java 자바 - 타입 변환과 다형성 (1) | 2020.05.24 |
---|---|
3. Java 자바 - protected 접근 제한자 (1) | 2020.05.22 |
2. Java 자바 - 메소드 재정의 @Overrride, final 클래스, final 메소드 (0) | 2020.05.22 |
1. Java 자바 - 상속 inheritance (0) | 2020.05.22 |