다형성
정의
- 하나의 객체가 여러개의 형(타입)을 가질 수 있는 특징
- 상속을 전제로 함
- 상속 관계에 있을때 부모의 타입으로 자식 타입 객체를 참조(레퍼런스)할 수 있음
- 메서드 다중 정의(오버로딩), 메서드 재정의(오버라이딩)은 자바의 다형성 특징으로 가능함.
활용
-
다른 타입의 객체를 배열로 묶어 사용할 수 있음
- 다른 타입의 데이터를 하나의 배열로 관리
- Object는 모든 클래스의 부모이므로 Object 배열은 어떤 타입의 객체도 다 저장할 수 있음
- 기본형 데이터 타입의 경우 객체가 아니므로 원칙적으로는 불가능하지만, Auto-Boxing을 이용해 Wrapper 클래스로 바꾸면 가능함
Javavoid polyArray { Person[] persons = new Person[2]; persons[0] = new Person(); persons[1] = new SpiderMan(); }
-
매개 변수의 다형성
- 메서드의 매개 변수로 부모 타입을 처리한다면 객체 타입에 따라 여러개의 메서드를 만들 필요가 없음
public void println(Object obj) { }
- 그러나 무조건(불필요하게) 최상위 타입으로 매개 변수 타입을 정하게 된다면 최상위 타입의 객체를 접근할 필요가 없는데도 메서드 내에서 인자 타입에 따른 조건을 일일이 달아주어야 하는 번거로운 상황이 발생할 수도 있음
- 따라서 비즈니스 로직 상 최상위 객체를 사용하는 것이 권장됨
JavaObject obj = new Object(); Person person = new Person(); SpiderMan sman = new SpiderMan(); // 매개변수에 상위 클래스인 Object 타입 올 경우 void jumpTest1(Object obj) { // jump를 할 일이 없는 Object를 굳이 매개변수로 받을 필요가 있을끼? if (obj instanceof Person) { Person p = (Person) obj; p.jump(); } } j.jumpTest1(obj); // 의미 없음 j.jumpTest1(person); j.jumpTest1(sman); // 매개변수에 하위 클래스인 Person 타입 올 경우 void jumpTest2(Person person) { person.jump(); } j.jumpTest1(obj); // 실행 불가능하나 실행해도 의미 없으므로 실행할 필요 없음 j.jumpTest1(person); j.jumpTest1(sman);
-
리턴 타입의 다형성
- 메서드의 리턴 타입으로 부모 타입을 지정하면 객체 타입에 따라 여러개의 메서드를 만들 필요가 없음
다형성의 trade-off
- 일반적으로 프로그래밍에서 등호의 좌변은 값이나 객체를 담는 참조변수가, 우변은 값이나 객체 자체가 위치함.
- 다형성 특징을 적용하면 좌변에는 우변의 객체와 동일한 타입뿐만 아니라 더 큰 타입도 올 수 있음.
- 더 큰 타입(부모)으로 참조변수를 만들 경우 해당 부모를 상속받는 다양한 자식 객체를 담아 활용할 수 있음(일반화). 하지만 오직 부모 차원의 공통적인 속성과 기능만 사용할 수 있음
- 반대로 자신과 동일한 타입(부모보다 작은 자식 타입) 참조변수를 만들 경우 해당 객체보다 큰 타입은 담을 수 없음. 하지만 객체에게 특화된 속성과 기능을 사용할 수 있음(구체화)
- 따라서 사용 목적에 따른 설계가 필요함!
- 정적 바인딩 : 컴파일타임에 선언된 객체 타입에 따라 호출될 함수를 인식하는 것
- 동적 바인딩 : 런타임에 실제 참조하는 객체를 따라가 해당 객체 타입에 따라 호출될 함수를 인식하는 것
컴파일러는 선언된 참조변수의 타입만 체크하기때문에 아래와 같은 상황이 발생함.
Engineer engineer = new Engineer();
- Engineer, Employee, Object의 멤버 모두 접근 가능
Employee employee = new Engineer();
- Employee, Object의 멤버만 접근 가능
Object object = new Engineer();
- Object의 멤버만 접근 가능
단, 런타임 시 참조를 따라가 실제 생성한 객체 타입을 확인하므로, 부모의 메서드를 재정의한 메서드가 자식에 존재한다면 상위 타입 참조변수에서 자식의 메서드를 사용할 수 있음
-> Virtual Method Invocation
다형성과 참조형 객체의 형변환
묵시적 형변환과 명시적 형변환
Java
// 묵시적 형변환 : 부모인 Person 타입의 참조변수에 자식인 SpiderMan 객체 할당
Person person = new SpiderMan(); // person은 SpiderMan 멤버 접근 불가
// 명시적 형변환 : 자식인 SpiderMan 타입 참조변수에 부모 타입 참조변수를 형변환하여 할당. 단 person 참조변수가 생성하여 가리키는 실제 객체는 SpiderMan 타입이므로 형변환이 가능
SpiderMan sm = (SpiderMan) person; // sm은 SpiderMan 멤버 접근 가능
- 메모리 상에 객체가 생성되어 존재하는 것과 객체를 사용할 수 있는 것은 차이가 존재함
- 클래스는 자신을 포함한 자신이 상속받은 조상 클래스들의 객체만 접근 및 사용할 수 있음
- 즉, 자신의 자식 클래스 객체는 자신이 직접 접근하여 사용할 수 없음
주의할 점
- 메모리에 부모 클래스 객체가 들어있는 경우 자식 클래스 타입을 참조한다 할지라도 자식의 멤버에 접근 및 사용할 수 없음(작은 타입에서 큰 타입은 접근 가능하지만 큰 타입에서 작은 타입은 접근할 수 없이 별개로 존재)
- 실제 메모리에 있는 객체가 특정 타입인지 확인하기 위해
instanceof
를 사용할 수 있음
Person person = new Person(); // 메모리에는 Person 클래스 객체 존재
SpiderMan sm = (SpiderMan) person; // 부적절한 형변환
if (person instanceof SpiderMan){ // 객체를 확인한 뒤 형변환하기
SpiderMan sm = (SpiderMan) person;
}