- 타입 변환
: 타입을 다른 타입으로 변환하는 행위
: 클래스의 변환은 상속 관계에 있는 클래스 사이에서 발생
: 자식은 부모 타입으로 자동 타입 변환이 가능함
- 자동 타입 변환
부모타입 변수 = 자식타입;
: 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것
: 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있음
ex)
고양이가 동물의 특징과 기능을 상속받았다면
'고양이는 동물이다.'가 성립함
Animal 클래스(부모, 상위) ← Cat 클래스(자식, 하위)
class Animal { ··· } ← (상속) class Cat extends Animal { ··· }
: Cat 클래스로부터 Cat 객체를 생성하고
이것을 Animal 변수에 대입하면 자동 타입 변환이 일어남
Cat cat = new Cat();
Animal animal = cat;
→ Animal animal = new Cat(); 도 가능함
: cat과 animal 변수는 타입만 다를 뿐, 동일한 Cat 객체를 참조함
→ 바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면
자동 타입 변환이 일어날 수 있음
class A {}
class B extends A {}
class C extends A {}
class D extends B {}
class E extends C {}
public class PromotionExample {
public static void main(String[] args) {
B b = new B();
C c = new C();
D d = new D();
E e = new E();
A a1 = b;
A a2 = c;
A a3 = d;
A a4 = e;
B b1 = d;
C c1 = e;
//상속 관계에 있지 않기 때문에 컴파일 에러 발생
//B b3 = e;
//C c2 = d;
}
}
: 부모타입으로 자동 타입 변환된 이후에는
부모 클래스에 선언된 필드와 메소드만 접근이 가능함
: 변수는 자식 객체를 참조하지만
변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정됨
예외는 메소드가 자식 클래스에서 재정의됐다면
자식 클래스의 메소드가 대신 호출됨
→ 다형성과 관련있는 성질
: Child 객체는 method3() 메소드를 가지고 있지만,
Parent 타입으로 변환된 이후에는 method3()을 호출할 수 없음
그러나, method2() 메소드는 부모와 자식 모두에게 있음
이렇게 재정의된 메소드는 타입 변환 이후에도 자식 메소드가 호출됨
👩💻 자동 타입 변환 후의 멤버 접근
// 부모 클래스
class Parent {
public void method1() {
System.out.println("Parent-method1()");
}
public void method2() {
System.out.println("Parent-method2()");
}
}
// 상속받은 자식 클래스
class Child extends Parent {
// method2() 재정의
@Override
public void method2() {
System.out.println("Child-method2()");
}
public void method3() {
System.out.println("Child-method3()");
}
}
// 실행 클래스
public class ChildExample {
public static void main(String[] args) {
Child child = new Child();
Parent parent = child; // 자동 타입 변환
parent.method1();
parent.method2(); // 재정의된 메소드가 호출됨
//parent.method3(); ← 호출 불가능
}
}
💻 결과
Parent-method1()
Child-method2()
: 필드의 타입을 부모 타입으로 선언하면
다양한 자식 객체들이 저장될 수 있기 때문에
필드 사용 결과가 달라질 수 있음
ex)
자동차를 구성하는 부품은 언제든지 교체할 수 있음
부품은 고장이 날 수 도 있고, 성능이 더 좋은 부품으로 교체되기도 함
객체 지향 프로그래밍도 마찬가지로
프로그램은 수많은 객체들이 서로 연결되고 각자의 역할을 하게 됨
이 객체들은 다른 객체로 교체될 수 있어야함
→ 상속과 재정의, 타입 변환을 이용하여 구현
■ 다형성을 구현할 수 있는 기술적 조건
1. 부모 클래스를 상속하는 자식 클래스는
부모가 가지고 있는 필드와 메소드를 가지고 있으니 사용 방법이 동일
2. 자식 클래스는 부모의 메소드를 재정의해서
메소드의 실행 내용을 변경함으로써 더 우수한 실행결과가 나오게 할 수 있음
3. 자식 타입을 부모 타입으로 변환할 수 있음
👩💻 Tire 클래스
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;
}
}
}
👩💻 Tire를 부품으로 가지는 클래스
public class Car {
//필드
//최대 회전수: 6회전 시 타이어에 펑크가 남
Tire frontLeftTire = new Tire("앞왼쪽", 6);
Tire frontRightTire = new Tire("앞오른쪽", 2);
Tire backLeftTire = new Tire("뒤왼쪽", 3);
Tire backRightTire = new Tire("뒤오른쪽", 4);
//생성자
//메소드
int run() {
/* 모든 타이어를 1회 회전시키기 위해
* 각 Tire 객체의 roll() 메소드를 호출
* false를 리턴하는 roll()이 있을 경우
* stop() 메소드를 호출하고
* 해당 타이어 번호를 리턴
*/
System.out.println("[자동차가 달립니다.]");
if(frontLeftTire.roll()==false) {
stop(); return 1;
}
if(frontRightTire.roll()==false) {
stop(); return 2;
}
if(backLeftTire.roll()==false) {
stop(); return 3;
}
if(backRightTire.roll()==false) {
stop(); return 4;
}
return 0;
}
void stop() {
System.out.println("[자동차가 멈춥니다.]");
}
}
👩💻 Tire의 자식 클래스
public class HankookTire extends Tire {
//필드
//생성자
public HankookTire(String location, int maxRotation) {
super(location, maxRotation);
}
//메소드
@Override
public boolean roll() {
++accumulatedRotation; // 누적 회전수 1증가
if(accumulatedRotation < maxRotation) {
System.out.println(location + " HankookTire 수명: " +
(maxRotation - accumulatedRotation) + "회");
return true;
}else {
System.out.println("*** " + location + " HankookTire 펑크 ***");
return false;
}
}
}
👩💻 Tire의 자식 클래스
public class KumhoTire extends Tire{
//필드
//생성자
public KumhoTire(String location, int maxRotation) {
super(location, maxRotation);
}
//메소드
@Override
public boolean roll() {
++accumulatedRotation; // 누적 회전수 1증가
if(accumulatedRotation < maxRotation) {
System.out.println(location + " KumhoTire 수명: " +
(maxRotation - accumulatedRotation) + "회");
return true;
}else {
System.out.println("*** " + location + " KumhoTire 펑크 ***");
return false;
}
}
}
👩💻 CarExample 클래스
public class CarExample {
public static void main(String[] args) {
//Car객체 생성
Car car = new Car();
//Car객체의 run()메소드 5번 반복실행
for(int i=1; i<=5; i++) {
int problemLocation = car.run();
switch(problemLocation) {
//앞왼쪽 타이어가 펑크 났을 때 HankookTire로 교체
case 1:
System.out.println("앞왼쪽 HankookTire로 교체");
car.frontLeftTire = new HankookTire("앞왼쪽", 15);
break;
//앞오른쪽 타이어가 펑크 났을 때 KumhoTire로 교체
case 2:
System.out.println("앞오른쪽 KumhoTire로 교체");
car.frontRightTire = new KumhoTire("앞오른쪽", 13);
break;
//뒤왼쪽 타이어가 펑크 났을 때 HankookTire로 교체
case 3:
System.out.println("뒤왼쪽 HankookTire로 교체");
car.backLeftTire = new HankookTire("뒤왼쪽", 14);
break;
//뒤오른쪽 타이어가 펑크 났을 때 KumhoTire로 교체
case 4:
System.out.println("뒤오른쪽 KumhoTire로 교체");
car.backRightTire = new KumhoTire("뒤오른쪽", 13);
break;
}
System.out.println("----------------------------------");
}
}
}
💻 결과
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 5회
앞오른쪽 Tire 수명: 1회
뒤왼쪽 Tire 수명: 2회
뒤오른쪽 Tire 수명: 3회
----------------------------------
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 4회
*** 앞오른쪽 Tire 펑크 ***
[자동차가 멈춥니다.]
앞오른쪽 KumhoTire로 교체
----------------------------------
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 3회
앞오른쪽 KumhoTire 수명: 12회
뒤왼쪽 Tire 수명: 1회
뒤오른쪽 Tire 수명: 2회
----------------------------------
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 2회
앞오른쪽 KumhoTire 수명: 11회
*** 뒤왼쪽 Tire 펑크 ***
[자동차가 멈춥니다.]
뒤왼쪽 HankookTire로 교체
----------------------------------
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 1회
앞오른쪽 KumhoTire 수명: 10회
뒤왼쪽 HankookTire 수명: 13회
뒤오른쪽 Tire 수명: 1회
----------------------------------
: 자동 타입 변환은 주로 메소드를 호출할 때 많이 발생
: 메소드를 호출할 때는 매개값을 다양화하기 위해
매개 변수에 자식 객체를 지정 할 수도 있음
ex)
Driver 클래스에는 drive() 메소드가 정의되어 있는데
Vehicle타입의 매개 변수가 선언되어 있음
class Driver {
void drive(Vehicle vehicle){
vehicle.run();
}
}
- ★★ drive()메소드를 정상적으로 호출 시 ★★,
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
driver.drive(vehicle);
→ 만약 Vehicle의 자식 클래스인 Bus 객체를
drive()메소드의 매개값으로 넘겨준다면
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
driver.drive(bus);
- drive() 메소드는 Vehicle 타입을 매개 변수로 선언했지만,
Vehicle을 상속받는 Bus 객체가 매개 값으로 사용되면
자동 타입 변환이 발생함
> Vehicle vehicle = bus;
- 매개 변수의 타입이 클래스일 경우,
해당 클래스이 객체뿐만 아니라
자식 객체까지도 매개값으로 사용할 수 있음
→ 즉, 매개값으로 어떤 자식 객체가 제공되느냐에 따라
메소드의 실행결과는 다양해질 수 있음
→ 자식 객체가 부모의 메소드를 재정의했다면
메소드 내부에서 재정의된 메소드를 호출함으로써
메소드의 실행결과는 다양해짐
👩💻 부모 클래스
class Vehicle {
public void run(){
System.out.println("차량이 달립니다.");
}
}
//Vehicle을 이용하는 클래스
class Driver{
public void drive(Vehicle vehicle) {
vehicle.run();
}
}
👩💻 자식클래스
class Bus extends Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
class Taxi extends Vehicle {
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
👩💻 실행 클래스
public class DriverExample {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
// 자동 타입 변환: Vehicle vehicle = bus;
driver.drive(bus);
// 자동 타입 변환: Vehicle vehicle = taxi;
driver.drive(taxi);
}
}
💻 결과
버스가 달립니다.
택시가 달립니다.
부모타입 변수 = new 자식();
자식타입 변수 = (자식타입) 부모타입;
: 부모 타입을 자식 타입으로 변환하는 것
: 자식 타입이 부모 타입으로 자동 타입 변환한 후
다시 자식 타입으로 변환할 때 강제 타입 변환을 사용가능 함
ex)
Child 객체가 Parent 타입으로 자동 변환된 상태에서
원래 Child로 강제 변환할 수 있음
Parent parent = new Child(); //자동 타입 변환
Child child = (Child) parent; //강제 타입 변환
👩💻 강제 타입 변환
//부모 클래스
class Parent2{
public String field1;
public void method1() {
System.out.println("Parent-method1()");
}
public void method2() {
System.out.println("Parent-method2()");
}
}
//자식 클래스
class Child2 extends Parent2{
public String field2;
public void method3() {
System.out.println("Child-method3()");
}
}
// 실행 클래스
public class ChildExample2 {
public static void main(String[] args) {
Parent2 parent = new Child2(); //자동 타입 변환
parent.field1 = "data1";
parent.method1();
parent.method2();
/* 불가능
parent.field2 = “yyy”;
parent.method3();
*/
Child2 child = (Child2) parent; //강제 타입 변환
child.field2 = "yyy"; // 가능
child.method3(); //가능
}
}
💻 결과
Parent-method1()
Parent-method2()
Child-method3()
boolean result = 좌항(객체) instanceof 우항(타입)
→ 좌항의 객체가 우항의 인스턴스이면,
즉, 우항의 타입으로 객체가 생성되었다면 true를 리턴
그렇지않으면 false로 리턴
→ 부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인하는 방법
: 어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해
instanceof 연산자를 사용
: instanceof 연산자는 주로 매개값의 타입을 조사할 때 사용됨
: 메소드 내에서 강제 타입 변환이 필요할 경우 반드시
매개값이 어떤 객체인지 instanceof 연산자로 확인하고
안전하게 강제 타입 변환을 해야함
ex)
public void method(Parent parent){
// Parent 매개 변수가 참조하는
// 객체가 Child인지 조사
if(parent instanceof Child) {
Child child = (Child) parent;
}
}
: 만약 타입을 확인하지 않고 강제 타입 변환을 시도한다면
ClassCastException이 발생함
👩💻 객체 타입 확인
//부모 클래스
class Parent3{
}
//자식 클래스
class Child3 extends Parent3{
}
public class InstanceofExample {
public static void method1(Parent3 parent) {
//Child 타입으로 변환이 가능한지 확인
if(parent instanceof Child3) {
Child3 child = (Child3) parent;
System.out.println("method1 - Child로 변환 성공");
} else {
System.out.println("method1 - Child로 변환되지 않음");
}
}
public static void method2(Parent3 parent) {
// classcastException이 발생할 가능성이 있음
Child3 child = (Child3) parent;
System.out.println("method2 - Child로 변환 성공");
}
public static void main(String[] args) {
Parent3 parentA = new Child3();
//Child 객체를 매개값으로 전달
method1(parentA);
method2(parentA);
Parent3 parentB = new Parent3();
//Parent 객체를 매개값으로 전달
method1(parentB);
method2(parentB);
}
}
💻 결과
method1 - Child로 변환 성공
method2 - Child로 변환 성공
method1 - Child로 변환되지 않음
오류 문구 ··· // 무조건 변환하려고 했기 때문