본문 바로가기

Java/Study

Ch7 상속 - 다형성 및 추상클래스

다형성
(polymorphism)

같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질

코드측면에서 보면, 다형성은 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있도록 해준다.

 

다형성에 대한 이해

더보기

자식객체는 부모 객체의 속성과 기능을 그대로 상속받아 이용하고, 재정의할 수 있다.
즉 자식객체는 부모 객체를 모두 가지고 있되, 좀 더 다양한 특성을 가지고 변형된다.
이로 인해 부모타입은 자식 객체를 저장할 수 있는데, 코드 측면으로 볼 때 클래스(자식→부모) 간의 변화(형변환)이 이루어지는 것이다.

이를 두고 하나의 타입에 여러 객체를 대입한다고 하고, 부모 타입으로의 자동형변환이 이루어진다고 한다.

그렇다면, 단순히 상속받은 멤버를 변형하여 자식 클래스로 객체를 생성하고 쓰면 되지, 왜 부모 타입에 자식 객체를 저장하는 것일까?

클래스 간의 관계를 설정할 때 부모 클래스와 실행클래스, 그 외 사용 관계의 클래스들만을 고려하여 코드를 작성하면 코드를 단순화시킬 수 있고, 수정 또한 용이하기 때문이다. 

예를 들어, 자동차를 작동시키는 프로그램을 만든다. Car 클래스는 Tire를 부품으로 가지고 있고, Tire의 문법을 선언한 부모 클래스(Tire)와 이를 적용시켜 조금씩 변형한 자식 클래스(금호타이어, 한국타이어)가 존재한다.  프로그램의 초반에는 기본 Tire로 자동차가 움직이다가, 문제가 생길 때 다른 자식 클래스로 Tire를 교체한다고 하자. 부모 클래스 Tire와 자식 클래스(금호Tire, 한국Tire) 모두 자동차에 들어가는 순간 같은 기능을 한다고 예상할 수 있다. 

처음에는 부모 클래스 Tire과 Car 클래스를 중심으로 코드가 작성될 것이다. 그러다가 기존 부모클래스의 Tire가 아닌 다른 자식클래스Tire을 사용하고 싶다면, 기존에 부모 객체를 저장하고 있던 참조변수에 새로운 자식 클래스를 생성하여 저장하면 다른 기능들을 일일이 수정할 필요가 없다. 말그대로 참조변수는 그대로 쓰되 참조하는 객체(내용물)를 바꿔줌으로써, 상속 관계의 여러 객체들을 부품처럼 사용 가능하다. 즉, 부모타입의 변수는 그대로 유지되고(필드의 타입은 변함이 없고) 그 안에 어떤 객체가 저장되어있느냐에 따라 프로그램의 실행 방향을 변화시킬 수 있다.

 

부모클래스명 참조변수명 = new 자식객체();

Animal animal = new Cat();

 

Animal 타입의 참조변수 animal에는 Cat의 주소값이 저장되기 때문에

다형성객체에서는 부모타입을 가져도 실제로 자식객체를 참조한다.

 

자료타입은 그 자료만 접근가능하다는 뜻이다.

부모타입을 가지기 때문에 변수 animal을 통해 Animal 객체의 자료만을 사용할 수 있다.

예외적으로 자식 클래스에 오버라이딩 메소드가 있다면 호출된다.  

 

메모리 측면을 살펴보면,  Cat객체를 생성할 때 부모 클래스인 Animal 객체가 자동으로 생성된다. 따라서 같은 메모리 공간에 Animal 객체도 함께 포함하고 있어 Animal타입에 저장가능하다(자동형변환). 이러한 메모리 구조는 명시적 형변환을 거쳐 자식 객체에 접근하는 것도 가능하게 한다.

 

 

 


다형성과 형변환

형변환은 다형성을 구현하는 방법으로 자동형변환(묵시적형변환)과 명시적형변환으로 나뉜다.

 

1.묵시적형변환 (Implicit Casting)

Animal animal = new Dog();

다른표준객체와 연결할 때, 즉 부품을 조립할때는 자동형변환을 통해 부모타입 참조변수를 사용

자식 클래스의 상위 타입으로의 변환은 프로그램 실행시 자동적으로 일어나기 때문에 자동형변환이라고 한다.

 

Animal a = new Cat();

큰타입 = 작은타입

: up캐스팅연산(묵시적 형변환)

cf. 메모리와는 무관하게 부모가 큰 타입이라고 전제된다.

 

2.명시적형변환 (Explicit Casting)

Cat cat = (Cat) animal;

자식객체에 있는 필드나 메소드를 사용하기 위해 다시 자식 클래스로 형변환하는 것

자식 타입이 부모 타입으로 자동 변환한 후, 다시 자식 타입으로 변환할 때 사용된다.

animal 변수는 Animal이라는 큰 타입을 가지고 있기 때문에 작은 타입으로 만들어주기 위해 강제형변환이 요구된다.

 

Cat    c  =  (Cat) a;

작은타입  =   큰타입

: down캐스팅연산(명시적 형변환)

 

더보기

byte b; int i;

b=(byte) i; // down캐스팅연산, 명시적형변환

i = b; //up캐스팅연산, 묵시적형변환

byte b; int i;

b=(byte) i; // down캐스팅연산, 명시적형변환

i = b; //up캐스팅연산, 묵시적형변환

 


is a 관계가 성립하면 상속관계로 만들 수 있다.

Cat cat = new Cat();

Animal animal = cat; 

=>Cat 객체와 Animal 객체가 같은 변수를 참조한다(cat = animal)

=> 줄여서 Animal animal = new Cat();으로 쓸 수 있음

 

 

 

package p4;

public class Bmain {
	public static void main(String[] args) {
		//자료형 참조변수명 : 참조변수는 자료형의 타입이다. 
		//변수안에는 Cat객체의 힙메모리 주소가 들어있다. 변수안의 주소로 Cat 객체와 부모객체인 Animal 모두 찾아갈 수 있다는 뜻
		Cat c = new Cat(); //일반적인 객체생성방법
		
		//a의 주소로 Animal 객체로 접근한다.
		//자식을 new하는데 부모변수(타입)으로 받는다. >부모한테만 접근 가능
		Animal a = new Cat(); //다형성 : 자식 클래스의 객체를 생성하면서 변수는 부모로 받는 것.
		//a변수는 Animal 클래스의 자료만 접근할 수 있도록 만든 것
		//물론 힙메모리안에 Cat 클래스의 자료도 있지만 Cat의 접근을 막아버린 것 (메모리 안에는 Animal과 Cat 둘다 있는데 Animal에만 접근 가능)
        Cat ca	= new Animal(); // 다형성이 아님 : 자식변수로 부모의 객체를 생성 Animal 객체엔 cat이 없기 때문에 cat타입으로 받을 수 없다
	}

}

 


매개변수의 다형성

자동타입변환은 필드의 값을 대입할 때에도 발생하지만, 메소드를 호출할 때 주로 발생한다.

매개값을 다양화하기 위해 매개변수에 자식 타입 객체를 지정한다.

매개값으로 어떤 자식 객체를 사용하느냐에 따라 메소드의 실행 결과는 다양해질 수 있다.

이때, 자식 객체는 부모의 메소드를 재정의해야 한다.

또한 부모의 메소드를 소환하는 부모 객체를 매개변수로 가지는 또다른 메소드가 필요하다.

 

	//bus.run()을 실행한 결과와 같지만, 
	//run이라는 메소드를 별개로 만들어두면 매개값만 바꿔줌으로써 다양한 결과를 내면서도 코드의 가독성을 높인다
	//부모 타입 객체를 매개변수로 가지는 메소드 run은, 자식 객체도 매개값으로 받을수 있다.
    Vehicle vehicle = new Vehicle; Bus bus = new Bus();
    run(bus);
	
private static void run(Vehicle vehicle) {
	vehicle.run();
}
12 매개변수의 다형성
package BookExampleHomeWork.polymorphism;

public class Vehicle {
	public void run() {
		System.out.println("차량이 달립니다.");
	}
}
---------------------------------------------
public class Driver {
	public void drive(Vehicle vehicle) { 
//매개값을 다양화하기 위해 매개 변수에 자식 타입 객체를 지정한다.
		vehicle.run();
	}
}
-----------------------------------------------
public class Bus extends Vehicle {
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
}
----------------------------------------------------
public class Taxi extends Vehicle {
	@Override
	public void run() {
		System.out.println("택시가 달립니다.");
	}
}
-------------------------------------------------
public class DriverExample {

	public static void main(String[] args) {
		Driver driver = new Driver();
		Taxi taxi = new Taxi();
		
//		driver.drive(new Vehicle());
		driver.drive(new Bus()); //자식객체를 바로 생성해서 부모타입으로 변환할 수 있다.
		driver.drive(taxi);	//오버라이딩을 했기 때문에 자식메소드가 소환된다. 
					//즉 taxi에 run메소드가 없다면 vehicle클래스의 run메소드가 실행될 것
	}
}

강제적형변환

package p4;

public class Bmain {
	public static void main(String[] args) {
		
		//다형성으로 Cat객체를  생성합니다
		Animal a1 = new Cat();
		//animalField 값 100을 넣기
		a1.animalField = 100;
		//catField에 값 200 넣기
//		a1.catField = 200; 컴파일에러 why? a참조변수의 자료타입은 Animal이니까
		//animalMethod 호출
		a1.animalMethod();
		
		//catMethod 호출
//		a1.catMethod(); 컴파일에러 why? cat method는 cat 클래스에 속함. a1는 animal타입이라 animal 객체만 찾아줄 수 있음.
		
		//방법 : 변수선언 Cat --> a주소를 cat변수에 넣어주기
		Cat c; //자료형 참조변수명
		c = (Cat) a1; //Cat 클래스 참조변수 = Animal 클래스의 참조변수
		c.catField = 200;
		c.catMethod();
		
		/*Cat c = (Cat) a1;
		c.catField = 200;
		 */		
		
		//다형성으로 Dog객체를 생성합니다.
		Animal a2 = new Dog();
		
		//animalField에 값 1000넣기
		a2.animalField = 1000;
		
		//dogField에 값 2000넣기
//		a2.dogField = 2000;	//why not? a2 참조변수의 자료타입이 Animal
		
		//animalMethod() 호출하기
		a2.animalMethod();
		
		//dogMethod() 호출
//		a2.dogMethod();		//why not? a2 참조변수의 자료타입이 Animal
		
		Dog d = (Dog) a2; //a2의 주소값을 더 작은 타입d에 넣어주기 위해 명시적 형변환이 필요하다.
		d.dogField = 2000;
		d.dogMethod();
	}
}

 

형변환과 RunTimeError

클래스와 각 클래스 간의 관계가 이미 선언된 상황에서 실제 메모리 구조와 관계없이 형변환은 가능하다.

다만, 메모리 구조에 따라 실행시에 RunTime Error가 발생한다. 따라서 실행 시에 문제가 된다면 애초에 프로그램(형변환)하지 않아야 한다.

A - B - D

A - C - E

package p5;

public class PromotionEx {
	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; //부모의 참조변수(큰타입) = 자식의 참조변수(작은타입) : /Upcasting(묵시적형변환)
		A a2 = c;
		A a3 = d;
		A a4 = e;
		
		B b1 = d;
		C c1 = e;
		
//		B b3 = e; down캐스팅할 수 없다. Why? e참조변수의 주소안에는 A,C,E객체만 있고 B객체는 없다.
		e = (E) c;
//		C c2 = d; Why? d참조변수의 주소안에는 C객체가 없다 A-B-D만 있다.
	
		//A-B-D | A-C-E
		e = (E) c; //타입의 구조상 형변환은 가능한데 실제 메모리엔 E가 없어 접근은 불가.
        		//c참조변수는 A클래스와 C클래스만 힙메모리에 올렸으므로 E클래스의 필드는 힙메모리에 올려져있지 않음.
		e.e =20; //ClassCastException
		
		e = (E) a1;
		d = (D) b;
		d = (D) a1;
	}

}


class A{
	int a =0;
}

class B extends A{
	int b=1;
}

class C extends A{
	int c=2;
}

class D extends B{
	int d=3;
}

class E extends C{
	int e=4;
}

다형성 A에 다같이 조립을 해줬는데, a1 참조변수로는 A클래스만 접근이 가능하다는 문제가 생겼을때
해결방법 1 다운캐스팅 2 메소드 오버라이딩

앞서 얘기했듯, 다형성 객체를 만들면 부모 필드와 메소드만 사용할 수 있다.

자식필드와 메소드를 사용하기 위해서는

 

1. 다운캐스팅 자식 타입 필드나 메소드 이용

2. 메소드 오버라이딩 → 사용 : 부모메소드와 유사한 기능을 하는 메소드일 경우

 

 

ex1. 계산 기능

부모는 사칙연산 리턴은 int → int m1(){//사칙연산}

자식은 논리연산 리턴은 int int m1() {//사칙연산}

 : 오버라이딩으로 구현, 다형성 사용     

 

ex2. 계산 기능

부모는 입력받는다 리턴타입은 없고 매개변수 정수3개

  void inputMethod(int a, int b, int c){ }

자식은 입력받는다 리턴타입은 boolean이고 매개변수는 정수2 ,실수2

  boolean inputMethod(int a, int b, double c, doubld ){ }

: 오버라이딩 불가, 다운캐스팅을 통해 자손클래스 호출

 

 

다운캐스팅으로 자손클래스 메소드를 호출하는 법

1) 일반 --- 표준
일반객체화 Cat cat = new Cat();
업캐스팅 Animal animal = cat;

업캐스팅 - 부모접근 animal.eat();
일반객체화 - 자식접근 cat.eat();

2) 표준 --- 일반 (더 많이 사용된다)
다형성 객체 만들기 Animal animal = new Cat();

객체로 부모 접근 animal.eat();

자식접근하는법 1.오버라이딩 animal.eat(); → Cat 클래스 안의 오버라이딩된 eat 메소드 실행
                              2. 다운캐스팅 Cat cat = (Cat) animal;  //animal을 Cat타입으로 형변환해 Cat타입의 cat 변수에 담는다.



instanceof

어떤 객체가 어떤 클래스의 인스턴스인지 확인(어떤 객체를 참조하고 있는지)하기 위한 연산자

 

boolean result = 좌항(객체) instanceof 우항(타입)

좌항의 객체가 우항의 인스턴스이면 true, 아니면 false를 산출

좌항의 객체가 우항의 타입으로 객체가 생성되었다면 true

*Parent parent = new Child();

Child는 Parent의 타입으로 객체가 생성되었다고 생각해 혼동할 수 있지만,

  사실은 Child타입으로 객체가 생성된 후,

Parent타입으로 형변환된 상태이다. 

 

매개값의 타입을 조사할 때 주로 사용된다.

메소드 내에서 강제타입변환이 필요할 경우, instacneof로 매개값을 확인해야 한다.

그렇지 않을 경우 ClassCastException예외가 발생할 수 있다

package p13;

public class ChildExample {

	public static void main(String[] args) {
		//부모자식클래스 객체화
		Parent parent = new Parent();
		Child child = new Child();
		//다형성 : Upcasting 묵시적형변환 자식참조변수 --> 부모참조변수
										//		자식이 객체화될 때 부모타입으로 만드는 것
		Parent parent1 = new Child();
		//캐스팅연산 down casting 명시적 형변환 : 부모참조변수 --> 자식참조변수 
		 Child child1 = (Child) parent1;
//		 Child child2 = (Child) parent; //RunTimeError parent는 바로 객체를 생성했기 때문에 애초에 자식 객체를 포함하고 있지 않다.
		
		 //질문 오류가난다면 어떤 변수? parent; Why?힙메모리에 Child 클래스가 없으니까
		 child1.field2 = "홍길동";
//		 child2.field2 = "이순신"; (x)
		 
		 child1.method3();
		 
		 //좌항 객체 instanceof 우항타입 
		 boolean result = parent instanceof Parent; 
		 System.out.println(result);
		 
		 result = child1 instanceof Parent; 
		 System.out.println(result);
		 
		 result = child1 instanceof Child; 
		 //child1 객체는 Child타입으로 객체생성됐습니까? 
         //child1 객체는 Child객체의 인스턴스입니까?
         //child1참조변수로 Child 클래스를 가르킬 수 있습니까?
		 //child1 참조변수의 주소로 찾아가면 힙메모리안에 Child 클래스를 찾을 수 있습니까?
		 System.out.println(result);
		 
		 result = parent instanceof Parent;
		 System.out.println(result);
		 
		 result = parent instanceof Child;
		 System.out.println(result + "~~~");
	}
}

추상클래스

(abstract class)

 

실체클래스들의 공통적인 특성을 추출해서 선언한 클래스로, 객체를 직접 생성할 수 없다

실체클래스들의 공통된 필드와 메소드의 이름을 통일할 목적으로 사용되며

실체 클래스를 작성할 때 시간을 절약하게 한다

 

추상클래스의 멤버들 : 필드, 생성자, 일반메소드, 추상메소드

cf.생성자는 필수이다 why? 자식 객체가 생성될 때 super()를 호출해서 추상클래스를 객체생성

 

추상메소드

(abstract method)

 

void method();

미완성된 메소드 { } 동작의 내용 부분이 없다

 

- 추상클래스(abstract class) : 추상메소드를 넣을 수 있는 클래스. 반드시 추상메소드를 가지고 있지 않아도 된다 (선택)
   반면 추상메소드(abstract 메소드명)를 갖는 클래스는 반드시 추상클래스여야 한다 (필수)
- 추상클래스를 부모로 사용하는 자식 클래스는 반드시 추상메소드를 재정의해야 한다.
   즉 자식이 반드시 부모 클래스의 메소드를 재정의하게 하고자 할 때 사용한다(자식 클래스에서 override해서 강제로 사용하게 하기 위함)
- 추상클래스는 객체화할 수 없다.
   자식클래스가 객체화해서 추상클래스인 부모를 사용하도록 한다

'Java > Study' 카테고리의 다른 글