티스토리 뷰

주의 사항!

  • 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.


다형성은 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말합니다. 코드 측면에서 보면 다형성은 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있도록 해줍니다. 다형성을 위해 자바는 부모 클래스로 타입 변환을 허용합니다. 

 

예를 들어 자동차를 설계할 때 타이어 클래스 타입을 적용했다면 이 클래스를 상속한 실제 타이어들은 어떤 것이든 상관없이 장착(대입)이 가능합니다. 코드로 표현하면 다음과 같습니다.

public class Car
{
	Tire t1 = new HankookTire();
	Tire t2 = new KumhoTire();
}

 

클래스 타입 변환은 상속 관계에 있는 클래스 사이에서 발생합니다. 자식 타입은 부모 타입으로 ㅏ동 타입 변환이 가능합니다. 위 코드에서 HankookTire와 KumhoTire는 Tire를 상속했기 때문에 Tire 변수에 대입할 수 있습니다.

 

자동 타입 변환

다음과 같은 경우에 자동 타입 변환이 일어납니다.

부모클래스 변수 = 자식클래스타입;

자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있습니다. 예를 들어 고양이는 동물의 특징과 기능을 상속받았습니다. 그래서 "고양이는 동물이다"가 성립합니다.

Cat cat = new Cat();
Animal animal = cat;

 

바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 가능합니다. 예를 들어 고양이는 동물의 특징과 기능을 상속받았지만, 동물이나 식물이나 생물의 특징과 기능을 상속받았기 때문에 "고양이는 생물이다"가 성립합니다.

Cat cat = new Cat();
Animal animal = cat;
Organism organism = cat;    //고양이는 유기체

부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메서드에만 접근이 가능합니다. 고양이는 동물이지만, 동물은 그르릉 소리를 내며 그루밍을 한다고 할 수는 없는 것과 같습니다.

 

하지만 예외는 있습니다. 메서드가 자식 클래스에서 오버 라이딩되었다면 자식 클래스의 메서드가 대신 호출됩니다. 이것은 다형성과 관련이 있기 때문에 매우 중요한 성질이므로 잘 알아두어야 합니다.

//Parent.java
package chapter00.exam00;

public class Parent 
{
	public void method1()
	{
		System.out.println("Parent-method1()");
	}
	
	public void method2()
	{
		System.out.println("Parent-method2()");
	}
}
//Child.java
package chapter00.exam00;

public class Child extends Parent
{
	@Override
	public void method2()
	{
		System.out.println("Child-method2()");
	}
	
	public void method3()
	{
		System.out.println("Child-method3()");
	}
}
//exam00.java
package chapter00.exam00;

public class exam00 
{
	public static void main(String[] args)
	{
		Child child = new Child();
		
		Parent parent = child;
		parent.method1();
		parent.method2();
		//parent.method3();    //호출 불가능
	}
}

/*
실행결과

Parent-method1()
Child-method2()

*/

 

필드의 다형성

다형성이란 동일한 타입을 사용하지만 다양한 결과가 나오는 성질을 말합니다. 두로 필드의 값을 다양화함으로써 실행 결과가 다르게 나오도록 구현하는데, 필드의 타입은 변함이 없지만, 실행 도중에 어떤 객체를 필드로 저장하느냐에 따라 실행 결과가 달라질 수 있습니다. 이것이 필드의 다형성입니다.

 

자동차를 구성하는 부품은 언제든지 교체할 수 있습니다. 부품은 고장 날 수도 있고 보다 더 성능이 좋은 부품으로 교체되기도 합니다. 객체 지향 프로그램에서도 마찬가지입니다. 프로그램은 수많은 객체들이 서로 연결되고 각자의 역할을 하게 되는데, 이 객체들은 다른 객체로 교체될 수 있어야 합니다. 예를 들어 자동차 클래스를 처음 설계할 때 사용한 타이어 객체는 언제든지 성능이 좋은 다른 타이어 객체로 교체할 수 있어야 합니다. 새로 교체되는 타이어 객체는 기존 타이어와 사용 방법은 동일하지만 실행 결과는 더 우수하게 나와야 할 것입니다.

 

이것을 프로그램으로 구현하기 위해서는 상속과 오버 라이딩, 그리고 타입 변환을 이용해야 합니다. 부모 클래스를 상속하는 자식 클래스는 부모가 가지고 있는 필드와 메서드를 가지고 있으니 사용 방법이 동일할 것이고, 자식 클래스는 부모의 메서드를 오버 라이딩해서 메서드의 실행 내용을 변경함으로써 더 우수한 실행 결과가 나오게 할 수 있습니다.

 

필드의 다형성을 코드로 이해해 보겠습니다.

public class Car
{
	//필드
	Tire frontLeftTire = new Tire();
	Tire frontRightTire = new Tire();
	Tire rearLeftTire = new Tire();
	Tire rearRightire = new Tire();

	//메서드
	void run() { ... }
}

Car 클래스는 4개의 Tire 필드를 가지고 있습니다. Car 클래스로부터 Car 객체를 생성하면 4개의 Tire 필드에 각각 하나씩 Tire 객체가 들어가게 됩니다. 그런데 frontRightTire와 rearLeftTire를 HankookTire와 KumhoTire로 교체할 필요성이 생겼습니다. 이러한 경우 다음과 같은 코드를 사용해서 교체할 수 있습니다.

Car myCar = new Car();

myCar.forntRightTire = new HankookTire();
myCar.rearLeftTire = new KumhoTire();

myCar.run();

Tire 클래스 타입인 frontRightTire와 rearLeftTire는 원래 Tire 객체가 저장되어야 하지만, Tire의 자식 객체가 저장되어도 문제가 없습니다. 왜냐하면 자식 타입은 부모 타입으로 자동 타입 변환이 되기 때문입니다. 

 

Car 객체에 run() 메서드가 있고, run() 메서드는 각 Tire 객체의 roll() 메서드를 다음과 같이 호출한다고 가정해보겠습니다.

void run()
{
	frontLeftTire.roll();
	frontRightTire.roll();
	rearLeftTire.roll();
	rearRightTire.roll();
}

frontRightTire와 rearLeftTire를 교체하기 전에는 Tire 객체의 roll() 메서드가 호출되지만, HankookTire와 KumhoTire로 교체가 되면 이 두 객체가 roll() 메서드를 오버 라이딩하고 있으므로 교체 이후에는 이 두 객체의 roll() 메서드가 호출됩니다.

 

매개변수의 다형성

자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메서드를 호출할 때 많이 발생합니다. 메서드를 호출할 때에는 매개 변수의 타입과 동일한 매개 값을 지정하는 것이 정석이지만, 매개 값을 다양화하기 위해 매개 변수에 자식 타입 객체를 지정할 수도 있습니다.

 

예를 들어 다음과 같이 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 객체를 매개 값으로 준다면 어떨까요?

Driver driver = new Driver();
Bus bus = new Bus();
driver.drive(bus);

이 때는 Bus 가 Vehicle의 자식 클래스이므로 자동 타입 변환이 일어나기 때문에 매개 값으로 Bus 객체를 주는 것이 가능합니다. 그리고 만약 Bus에 run() 메서드가 오버 라이딩되어 있다면 Bus의 run() 메서드가 호출될 것입니다.

 

다음은 이를 코드로 표현한 것입니다. 우선 Driver 클래스와 Vehicle 클래스가 정의되어 있습니다.

//Driver.java
package chapter00.exam00;

public class Driver 
{
	public void drive(Vehicle vehicle)
	{
		vehicle.run();
	}
}
//Vehicle.java
package chapter00.exam00;

public class Vehicle 
{
	public void run()
	{
		System.out.println("차량이 달립니다.");
	}
}

다음은 Bus 클래스와 Taxi 클래스인데, Vehicle 클래스를 상속하고 있고, run() 메서드를 오버 라이딩하고 있습니다.

//Bus.java
package chapter00.exam00;

public class Bus extends Vehicle
{
	@Override
	public void run()
	{
		System.out.println("버스가 달립니다.");
	}
}
//Taxi.java
package chapter00.exam00;

public class Taxi extends Vehicle
{
	@Override
	public void run()
	{
		System.out.println("택시가 달립니다.");
	}
}

이제 이들을 이용해서 실행하는 예제입니다.

//exam00.java
package chapter00.exam00;

public class exam00 
{
	public static void main(String[] args)
	{
		Driver driver = new Driver();
		
		Bus bus = new Bus();
		Taxi taxi = new Taxi();
		
		driver.drive(bus);
		driver.drive(taxi);
	}
}

/*
실행결과

버스가 달립니다.
택시가 달립니다.

*/

 

강제 타입 변환(Casting)

강제 타입 변환은 부모 타입을 자식 타입으로 변환하는 것을 말합니다. 그렇다고 해서 모든 부모 타입을 자식 클래스 타입으로 상제 변환할 수 있는 것은 아닙니다. 자식 타입이 부모 타입으로 자동 변환된 후, 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있습니다.

(즉, 원래 자식 클래스였던 것이 부모 클래스로 자동 타입 변환되었을 때 이를 다시 자식 클래스 타입으로 변환하는 것만 가능하다는 것입니다.)

 

자식 타입이 부모 타입으로 자동 변환하면, 부모 타입에 선언된 필드와 메서드만 사용 가능하다는 제약 사항이 따릅니다. 만약 자식 타입에 선언된 필드와 메서드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환한 다음 자식 타입의 필드와 메서드를 사용하면 됩니다.

//Parent.java
package chapter00.exam00;

public class Parent 
{
	public String field1;
	
	public void method1()
	{
		System.out.println("Parent-method1()");
	}
	
	public void method2()
	{
		System.out.println("Parent-method2()");
	}
}
//Child.java
package chapter00.exam00;

public class Child extends Parent
{
	public String field2;
	
	@Override
	public void method2()
	{
		System.out.println("Child-method2()");
	}
	
	public void method3()
	{
		System.out.println("Child-method3()");
	}
}
//exam00.java
package chapter00.exam00;

public class exam00 
{
	public static void main(String[] args)
	{
		Parent parent = new Child();
		
		parent.field1 = "data1";
		parent.method1();
		parent.method2();
		
		/*
		 parent.field2 = "data2";     //불가능
		 parent.mothod3();            //불가능
		 */
		
		Child child = (Child)parent;
		
		child.field2 = "yyy";
		child.method3();
	}
}

 

객체 타입 확인

자식 클래스 타입이 부모 클래스 타입으로 자동 타입 변환될 수 있다는 것을 알았습니다. 그렇다면 Parent 타입의 변수 parent가 실제 Parent 객체를 가리키고 있는지, Child 객체를 가리키고 있는지 어떻게 알 수 있을까요?

 

어떤 객체가 어떤 클래스의 인스턴스인지 확인하려면 instanceof 연산자를 사용할 수 있습니다. instanceof 연산자의 좌항은 객체가 오고, 우항은 타입이 오는데, 좌항의 객체가 우항의 인스턴스이면 ture를 산출하고 그렇지 않으면 false를 산출합니다.

boolean result = 객체 instanceof 클래스

 

instanceof 연산자는 매개 값의 타입을 조사할 때 주로 사용됩니다. 메서드 내에서 강제 타입 변환이 필요한 경우 반드시 매개 값이 어떤 객체인지 instanceof 연산자로 확인하고 안전하게 강제 타입 변환을 해야 합니다.

public void method(Parent parent)
{
	if(parent intanceof Child)
	{
		Child child = (Child) parent;
	}
}

 만약 타입을 확인하지 않고 강제 타입 변환을 시도한다면 ClassCastException 예외가 발생할 수 있습니다.

'공부 일지 > JAVA 공부 일지' 카테고리의 다른 글

자바, 추상 클래스  (0) 2021.04.08
자바, 하나의 배열로 객체 관리  (0) 2021.04.08
자바, final 클래스와 final 메서드  (0) 2021.04.08
자바, 메서드 재정의  (0) 2021.04.08
자바, 상속  (0) 2021.04.08
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/01   »
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
글 보관함