[Java] 타입 변환과 필드의 다형성
안녕하세요 Limky 입니다.
이번 시간은 객체지향에서 정말 중요한 "다형성"에 대해서 알아보겠습니다.
다형성은 같은 타입이지만 실행 결과가 다른 것을 뜻합니다.
즉 동일한 타입에 다양한 객체를 이용하여 다양한 결과를 만들 수 있습니다.
이런 다양성을 지원하기 위해 자바에서는 부모 클래스 타입에 모든 자식 객체가 대입 될 수 있도록 자동 타입 변환을 시켜줍니다.
자동 타입 변환은 부모 클래스를 상속받은 자식클래스들의 타입을 부모 클래스 타입인 변수가 대입 받을 수 있습니다.
부모클래스 타입 변수 = new 자식클래스();
이 과정에서 자바는 자동적으로 부모클래스 타입의 변수가 자식클래스 타입의 인스턴스를 대입 받을 수 있도록 명시적으로 타입변환을 선언하지 않아도 내부적으로 타입변환을 시켜줍니다.
예를 들어 Animal 부모클래스를 상속받은 Cat 클래스의 인스턴스는 굳이 Cat 클래스 타입이 아니여도 상속 관계에 있는 부모클래스 타입의 변수라면, 대입 될 수 있습니다.
Animal animal = cat; 이 줄에서 자동적으로 타입 변환이 발생합니다.
그 관계를 JVM 메모리에선 어떻게 참조되는 지 위 그림에서 확인 할 수 있습니다.
이렇게 자동 타입 변환이 발생하여 자식클래스의 인스턴스를 대입 받은 부모클래스 타입의 변수는 몇 가지 특징을 가집니다.
1. 부모클래스에 선언된 필드와 메소드만 접근이 가능합니다.
-> 비록 변수는 자식 인스턴스를 참조하지만, 변수로 접근 가능한 멤버는 부모클래스 멤버로만 한정됩니다.
2. 만약 부모클래스 메서드를 자식클래스에서 오버라이딩 한 경우라면, 자식클래스 메서드가 대신 호출됩니다.
이 2가지 특징 중에 가장 중요한 것이 바로 2번 입니다.
부모클래스에서 미리 정의한 메서드 중에서 자식클래스가 오버라이딩하여 재정의 한 메서드가 대신 호출되는 것이야 말로 다형성의 효과를 극대화 할 수 있습니다.
예제를 보면서 필드의 다형성에 대해 더 알아보겠습니다.
해당 예제는 "이것이 자바다" 에서 발췌 했습니다.
Tire.java
package JavaDiversityExample; public class Tire { //필드 public int maxRotation; //최대 회전수 (타이어 수명) public int accmulatedRotation; //누적 회전수 public String location; //타이어의 위치 //생성자 public Tire(String location, int maxRotation) { //필드 초기화 this.location = location; this.maxRotation = maxRotation; } //메서드 public boolean roll() { ++accmulatedRotation;//누적 회전수를 증가시킨다. //누적회전수가 타이어의 최대회전수보다 많아지는 경우 타이어는 펑크난다. if(accmulatedRotation < maxRotation) { System.out.println(location + "Tire 수명 : " + (maxRotation - accmulatedRotation) + "회"); return true; }else { System.out.println("*** " + location + "Tire 펑크 ***"); return false; } } }Car.java
package JavaDiversityExample; 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(">>> 자동차가 달립니다. >>>"); if(!frontLeftTire.roll()) {stop(); return 1;} if(!frontRightTire.roll()) {stop(); return 2;} if(!backLeftTire.roll()) {stop(); return 3;} if(!backRightTire.roll()) {stop(); return 4;} return 0; } void stop() { System.out.println("xxx 자동차가 멈춥니다. xxx"); } }
HanKooKTire.java
package JavaDiversityExample; import javax.jws.soap.SOAPBinding; public class HanKooKTire extends Tire{ public HanKooKTire(String location, int maxRotation) { super(location, maxRotation); // TODO Auto-generated constructor stub } //메소드 /** tire의 roll() 메서드 재정의 구현 **/ @Override public boolean roll() { ++accmulatedRotation; if(accmulatedRotation < maxRotation) { System.out.println(location + " HanKooKTire 수명 : " + (maxRotation - accmulatedRotation) + "회"); return true; }else { System.out.println("*** "+ location +" HanKooKTire 평크 ***"); return false; } } }
KumhoTire.java
package JavaDiversityExample; public class KumhoTire extends Tire { public KumhoTire(String location, int maxRotation) { super(location, maxRotation); // TODO Auto-generated constructor stub } //메소드 /** tire의 roll() 메서드 재정의 구현 **/ @Override public boolean roll() { ++accmulatedRotation; if(accmulatedRotation < maxRotation) { System.out.println(location + " KumhoTire 수명 : " + (maxRotation - accmulatedRotation) + "회"); return true; }else { System.out.println("*** "+ location +" KumhoTire 평크 ***"); return false; } } }
CarExample.java
package JavaDiversityExample; public class CarExample { public static void main(String[] args) { // TODO Auto-generated method stub Car car = new Car(); for(int i=1; i<=5; i++) { int problemLocation = car.run(); 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; default: break; } System.out.println("=================" + i + " 회전 ================="); } } }
결과
>>> 자동차가 달립니다. >>>
앞 왼쪽Tire 수명 : 5회
앞 오른쪽Tire 수명 : 1회
뒤 왼쪽Tire 수명 : 2회
뒤 오른쪽Tire 수명 : 3회
=================1 회전 =================
>>> 자동차가 달립니다. >>>
앞 왼쪽Tire 수명 : 4회
*** 앞 오른쪽Tire 펑크 ***
xxx 자동차가 멈춥니다. xxx
앞 오른쪽 KumhoTire로 교체
=================2 회전 =================
>>> 자동차가 달립니다. >>>
앞 왼쪽Tire 수명 : 3회
앞 오른쪽 KumhoTire 수명 : 12회
뒤 왼쪽Tire 수명 : 1회
뒤 오른쪽Tire 수명 : 2회
=================3 회전 =================
>>> 자동차가 달립니다. >>>
앞 왼쪽Tire 수명 : 2회
앞 오른쪽 KumhoTire 수명 : 11회
*** 뒤 왼쪽Tire 펑크 ***
xxx 자동차가 멈춥니다. xxx
뒤 왼쪽 HanKooKTire로 교체
=================4 회전 =================
>>> 자동차가 달립니다. >>>
앞 왼쪽Tire 수명 : 1회
앞 오른쪽 KumhoTire 수명 : 10회
뒤 왼쪽 HanKooKTire 수명 : 13회
뒤 오른쪽Tire 수명 : 1회
=================5 회전 =================
Tire의 수명이 다 되기 전에는 기존에 있는 Tire의 roll() 메서드가 동작하지만,
타이어의 수명이 다 되면 Tire 타입에 Tire를 상속받은 KumhoTire, HanKooKTire클래스 인스턴스로 교체 시킵니다. 즉 부모클래스 Tire의 타입에 자식클래스 인스턴스를 대입 받는 것이지요.
이제 교체된 Tire에선 새로 대입 된 자식클래스 roll() 메서드가 동작합니다.
KumhoTire, HanKooKTire 클래스가 roll() 메서드를 오버라이딩해서 각자의 입맛에 맞게 재정의 했기 때문에 더 이상 부모클래스의 roll() 메서드가 동작하지 않고 재정의된 roll() 메서드가 호출되는 것이지요. 이것이 바로 필드의 다형성입니다.
다형성은 객체지향에서도 정말 중요한 개념입니다.
실무에서도 확장 가능하면서 다양한 결과를 내야할 때 반드시 다형성을 이용해야 합니다. 그렇지 않으면 유지보수는 과정이 너무 힘들기 때문이죠...
만약 A사의 카메라와 연동하여 셀카를 찍을 수 있는 안드로이드 앱을 개발 했다고 칩시다. 이 앱은 무조건 A사의 카메라만 연동이 가능하기 때문에 B사의 카메라와 연동하는 앱을 만들기 위해선 처음부터 다시 설계하고 개발 해야 합니다. 또한 이 제품을 가지고 영업을 할 때도 불리합니다.
왜냐면 우리는 A사 카메라가 아니고선 서비스가 되지 않습니다. 라는 말이거든요....
만약 확장 가능한 설계를 했다면, 우리 제품은 카메라 SDK만 지원한다면 어떤 카메라와도 연동할 수 있는 앱 입니다. 라고 영업할 수 있습니다. 느낌이 확 오시나요..? 그 만큼 다형성은 실무에서 더욱 중요한 개념이기에 꼭 곱씹으면서 완벽하게 알아둬야 합니다.
다음 시간에는 매개 변수의 다형성, 강제 타입 변환, 추상클래스 등에 대해서 알아보도록 하겠습니다.