다형성이란
객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며,
자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.즉,조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 구현하였다.
class Tv{
boolean power;
int channel;
void power() { power = !power;}
void channelUp() { ++channel; }
void channelDown() { --channel; }
}
class CaptionTv extends Tv{
String text;
void caption() { /* ... */ }
}
CaptionTv c = new CaptionTv();
Tv t = new CaptionTv();
위는 인스턴스를 같은 타입의 참조변수로 참조하였고,
아래는 인스턴스를 조상타입의 참조변수로 참조하였다.
이렇게 했을 경우 조상타입의 참조변수 t로는 CaptionTv인스턴스의 모든 멤버를 사용할 수 없다.
Tv타입의 참조변수로는 Tv클래스의 멤버들(상속받은 멤버 포함)만 사용할 수 있다.
따라서, 생성된 CaptionTv인스턴스 중에서 Tv클래스에 정의되지 않은 멤버, text와 caption()은 t로 사용 불가능하다.
둘다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.
실제로는 Object클래스로부터 상속받은 부분도 포함해야하지만, 생략함
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조하면 컴파일에러가 발생한다.
CaptionTv c = new Tv();
그 이유는 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수있는 멤버 개수가 더 많기 때문이다.
그래서 아예 허용하고 있지 않다.
참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
조상타입의 참조변수로 자손타입의 인스턴스를 참조 가능
자손타입의 참조변수로 조상타입의 인스턴스 참조 불가능
참조변수의 형변환(Up-casting, Down-casting)
서로 상속관계에 있는 클래스 사이에서 형변환이 가능하다.
기본형 변수의 형변환에서 작은 자료형에서 큰 자료형의 형변환은 생략이 가능하듯,
참조형 변수의 형변환에서는 자손타입의 참조변수를 조상타입으로 형변환하는 경우에 생략할 수 있다.
자손타입 -> 조상타입(Up-casting) : 형변환 생략가능
자손타입 <- 조상타입(Down-casting) : 형변환 생략불가
다운캐스팅(down-casting)은 조상타입의 참조변수를 자손타입의 참조변수로 변환하는 것.
업캐스팅(up-casting)은 자손타입의 참조변수를 조상타입의 참조변수로 변환하는 것.
형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.
단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것 뿐이다.
class CastingTest1 {
public static void main(String args[]) {
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
fe.water();
car = fe; // car =(Car)fe;에서 형변환이 생략된 형태다.
// 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!!!");
}
}
- Car car = null;
- Car타입 참조변수 car 선언, null로 초기화
- FireEngine fe = new FireEngine();
- FireEngine인스턴스 생성 및 FireEngine타입 참조변수 fe에 참조
- car = fe;
- 참조변수 fe가 참조하고 있는 FireEngine인스턴스를 car도 참조(인스턴스의 주소값)
- 이 때 두 참조변수의 타입이 다르지만, 자손에서 조상이기때문에 형변환 생략
- car도 FireEngine인스턴스를 사용할 수 있지만, Car클래스의 멤버가 아닌 water()는 사용 불가능
- fe2 = (FireEngine)car;
- 두 참조변수 타입이 다르므로, 조상에서 자손으로 명시적 형변환 실행
- fe2도 FireEngine인스턴스의 주소가 저장
- fe2는 모든 멤버 사용 가능
class CastingTest2 {
public static void main(String args[]) {
Car car = new Car();
Car car2 = null;
FireEngine fe = null;
car.drive();
fe = (FireEngine)car; // 8번째 줄. 컴파일은 OK. 실행 시 에러가 발생.
// 조상타입의 인스턴스를 자손타입의 참조변수로
//참조할려고 하기 때문
fe.drive();
car2 = fe;
car2.drive();
}
}
참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용하지 않는다.
그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.(instanceof 로 확인)
instanceof연산자
instanceof연산자는 인스턴스의 실제 타입을 알아보기 위해 사용한다.
왼쪽에는 참조변수 오른쪽에는 타입(클래스명)이 피연산자로 위치한다.
반환값은 boolean이다.
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
생성된 인스턴스는 FireEngine타입인데 그위의 조상클래스들에서도 true 결과값을 얻었다.
자손 클래스 FireEngine인스턴스는 조상클래스의 인스턴를 포함하고 있는 셈이기 때문이다.
어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
참조변수와 인스턴스의 연결
조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손클래스에 중복 정의했을때,
조상타입의 참조변수로 자손 인스턴스를 참조하는 경우는 조상 클래스에 선언된 변수를,
자손타입의 참조변수로 자손 인스턴스를 참조하는 경우 자손 클래스에 선언된 변수를 사용한다.
메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출된다.
static메서드는 static변수처럼 참조변수의 타입에 영향을 받는다. 그래서 static메서드는 '클래스이름.메서드()'로 호출해야한다.
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 method() {
System.out.println("x=" + x); // this.x와 같다.
System.out.println("super.x=" + super.x);
System.out.println("this.x=" + this.x);
}
}
결과값
p.x = 100
x=200
super.x=100
this.x=200
c.x = 200
x=200
super.x=100
this.x=200
매개변수의 다형성
참조변수의 다형적인 특징은 메서드의 매개변수에도 적용된다.
buy() 메서드를 통해 구입한다는 것을 구현할때,
매개변수가 달라질때마다 오버로딩하는 것은 힘들다.
그러므로 공통 조상으로 매개변수 처리하여 하나의 메서드로 간단하게 처리할 수 있다.
예제 보기
class Product {
int price; // 제품의 가격
int bonusPoint; // 제품구매 시 제공하는 보너스점수
Product(int price) {
this.price = price;
bonusPoint =(int)(price/10.0); // 보너스점수는 제품가격의 10%
}
}
class Tv extends Product {
Tv() {
// 조상클래스의 생성자 Product(int price)를 호출한다.
super(100); // Tv의 가격을 100만원으로 한다.
}
public String toString() { // Object클래스의 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 + "점입니다.");
}
}
여러 종류의 객체를 배열로 다루기 - Vector클래스
위에서처럼 Product클래스가 Tv, Computer, Audio클래스의 조상일 때 배열로 선언하여 사용하는 방법.
Product[] p = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();
배열의 크기가 유동적일 때, Vector클래스 사용하기
Vector클래스는 내부적으로 Object타입의 배열을 가지고 있고, 이 배열에 객체를 추가하거나 제거할 수 있게 작성되어 있다. 그리고 배열의 크기를 알아서 관리해주기 때문에 저장할 인스턴스의 개수에 신경쓰지 않아도 된다.
Vector클래스는 동적으로 크기가 관리되는 객체배열이다.
메서드 / 생성자 | 설명 |
Vector() | 10개의 객체를 저장할 수 있는 Vector인스턴스 생성. 10개 이상의 인스턴스가 저장되면, 자동적으로 크기가 증가. |
boolean add(Object o) | Vector에 객체를 추가. 추가에 성공하거나 실패하면 결과값으로 true,false 반환. |
boolean remove(Object o) | Vector에 저장되어 있는 객체를 제거. 성공 true, 실패 false 반환. |
boolean isEmpty() | Vector가 비어있는지 검사. 비어있으면 true, 아니면 false 반환. |
Object get(int index) | 지정된 위치(index)의 객체를 반환. 반환타입이 Object타입이므로 적절한 타입으로 형변환해서 쓸 것. |
int size() | Vector에 저장된 객체의 개수 반환. |
참조
'Java의 정석' 책
'Language > Java' 카테고리의 다른 글
[Java] 인터페이스(interface) (0) | 2022.03.04 |
---|---|
[Java] 추상클래스(abstract class) -추상화,구체화 (0) | 2022.03.04 |
[Java] 제어자(modifier) - 접근 제어자와 그 외 제어자(캡슐화) (0) | 2022.03.04 |
[Java] Package와 import (0) | 2022.03.04 |
[Java] 객체지향 프로그래밍 -상속과 포함관계,Object클래스 (0) | 2022.03.04 |
댓글