[Java] 객체지향 프로그래밍 - 다형성

최지수·2022년 4월 16일
0

Java

목록 보기
12/27
post-thumbnail

다형성(Polymorphism)

상속과 함께 객체지향의 중요한 특징 중 하나에요. 상속Inheritance에 대해 충분히 숙지가 있어야 이해되요.

객체지향 개념에서 다형성이란 여러가지 형태를 가질 수 있는 능력을 의미하며, 자바에서는 한 타입의 참조 변수로 여러 타입의 객체를 참조할 수 있도록 하여 다형성을 프로그래밍적으로 구현했어요.

class Person {
	int height;
    int weight;
    int age;
    public void introduce() {}
}

class NoobNoob extends Person{
	@Overrice void introduce() {
		"힘세고 강한 안녕! 내 이름을 묻는다면 나는 늅늅!";
    }
}

public static void main(String[] args){
	Person human = new Person();	// 이건 당연히 되고
    Person noob = new NoobNoob();	// 이렇게 부모 클래스 타입의 변수에,
    								// 자식 클래스 타입의 인스턴스 참조가 가능해요.
}

위 예제처럼 PersonNoobNoob이 상속 관계에 있으면 부모 클래스 타입의 변수에 자식 클래스 인스턴스를 참조할 수 있게 해요.

하지만 반대는 불가능 해요. NoobNoob 자체에는 height, age 등의 변수가 없는데 만약에 이를 사용한다면 말이 안되겠죠? 그래서 자식 타입의 참조 변수로 부모 타입의 인스턴스를 참조하는 것은 존재 하지한는 멤버를 사용할 가능성이 있어 허용하지 않아요.

NoobNoob noob = new Person();	// Error!

참조 변수의 형변환

하지만 방법이 없는 건 아니에요. 기본형 변수와 같이 참조 변수도 형변환이 가능하고, 이를 통해 부모 인스턴스를 자식 타입으로 형변환하여 자식 타입 변수가 참조하게 할 수 있어요. 이렇게 부모 타입의 참조 변수를 자식 타입으로 형변환하는 경우를 다운캐스팅Down-casting.

기본형 변수의 형변환에서 작은 자료형에서 큰 자료형의 형변환은 생략이 가능하듯이, 참조형 변수의 형변환에서는 자식 타입의 참조 변수를 부모 타입으로 형변환하는 경우에는 형변환 생략이 가능해요. 이를 업캐스팅Up-casting이라고 해요. 그리고 반대로

Person person = new NoobNoob();		// Up-casting, 형변환 생략 가능
NoobNoob noob = (NoobNoob)person;	// Down-casting, 형변환 생략 불가

물론 이러한 형변환은 두 클래스가 상속 관계라는 전제 하에서만 동작해요. 그렇지 않으면 에러 처리되요. 그리고 다운 캐스팅을 수행한 참조 변수에서 잘못된 형변환을 하면 컴파일이 아닌 런타임에서 에러가 발생해 프로그램이 죽어요. 그렇기에 instanceof 연산자를 사용해서 참조 변수가 참조하고 있는 실제 인스턴스의 타입을 먼저 확인하고 형변환을 수행하는 것이 좋아요.

class A {}
class B extends A {}
class C extends A {}

A ab = new B();
B b = (B)ab;		// 가능!
C c = (C)b;			// 불가능! 둘은 상속 관계가 아니에요

A ac = new C();
b = (B)ac;			// 런타입 에러! instanceof 키워드로 먼저 체크!

if(ac instanceof B)	// 요렇게!
	b = (B)ac;		

근데 이러면 형변환을 하면 타입 자체가 바뀌니까 인스턴스 구조도 바뀌는 것이 아닐까 생각할 수도 있어요. 형변환은 참조 변수의 타입을 변환하는 것일 뿐이지 인스턴스 자체를 변환하는 것이 아니니까 아무런 영향을 주지 않아요. 가리키는 개념이 달라지는 것 뿐이니까 안심하셔도 되요.

instanceof 연산자

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알기 위해 intanceof 연산자를 사용해요.

if(ac instanceof B)	// 이렇게!

하지만 instanceof는 해당 인스턴스의 모든 부모 타입에도 true를 반환해요.

FireEngine fe = new FireEngine();
if (fe instanceof FireEngine) {
	System.out.println("It's fire engine.");	// 출력!
}
if (fe instanceof Car) {
	System.out.println("It's car.");			// 출력!
}
if (fe instanceof Object) {
	System.out.println("It's object.");			// 출력!
}

그러면 이렇게 정리할 수 있어요.

어떤 타입에 대한 instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능해요.

참조 변수와 인스턴스의 연결

부모에 선언된 인스턴스 변수와 동일한 이름의 인스턴스 변수를 자식에 중복으로 정의하게 되면, 조상 타입의 참조 변수로 자식 인스턴스를 참조하는 경우와 자식 타입의 참조 변수로 자식 인스턴스를 참조하는 경우는 서로 다른 결과를 얻어요.

메서드는 참조 변수 타입에 상관 없이 실제 인스턴스의 메서드를 호출하지만, 멤버 변수의 경우 참조 변수의 타입에 따라 달라져요.

물론 인스턴스 변수가 중복 정의되어 있지 않다면 상관 없어요. 그러니 사용하실 때 주의가 필요해요.

class Parent { int x = 100; }
class Child extends Parent { int x = 200; }

Parent p = new Child();	// p.x = 100
Child c = new Child();	// c.x = 200

내부적으로 변수는 superthis(혹은 미선언)을 통해 구분할 수 있어요.

class Child extends Parent{
	void func(){
		super.x // 100
		x		// 200
	}
}

여러 종류 객체를 배열로 만들기

부모 타입[] 형태의 객체 배열은 각 인덱스마다 자식 인스턴스를 참조할 수 있어요.

Parent[] p = new Parent[3];
p[0] = new Child();
p[1] = new GrandChild();
p[2] = new BaeDarlnChild();
profile
#행복 #도전 #지속성

0개의 댓글