다형성

정의

  • 하나의 객체가 여러개의 형(타입)을 가질 수 있는 특징
  • 상속을 전제로 함
  • 상속 관계에 있을때 부모의 타입으로 자식 타입 객체를 참조(레퍼런스)할 수 있음
  • 메서드 다중 정의(오버로딩), 메서드 재정의(오버라이딩)은 자바의 다형성 특징으로 가능함.

활용

  1. 다른 타입의 객체를 배열로 묶어 사용할 수 있음

    • 다른 타입의 데이터를 하나의 배열로 관리
    • Object는 모든 클래스의 부모이므로 Object 배열은 어떤 타입의 객체도 다 저장할 수 있음
    • 기본형 데이터 타입의 경우 객체가 아니므로 원칙적으로는 불가능하지만, Auto-Boxing을 이용해 Wrapper 클래스로 바꾸면 가능함
    Java
    void polyArray {
        Person[] persons = new Person[2];
        persons[0] = new Person();
        persons[1] = new SpiderMan();
    }
  2. 매개 변수의 다형성

    • 메서드의 매개 변수로 부모 타입을 처리한다면 객체 타입에 따라 여러개의 메서드를 만들 필요가 없음
    • public void println(Object obj) { }
    • 그러나 무조건(불필요하게) 최상위 타입으로 매개 변수 타입을 정하게 된다면 최상위 타입의 객체를 접근할 필요가 없는데도 메서드 내에서 인자 타입에 따른 조건을 일일이 달아주어야 하는 번거로운 상황이 발생할 수도 있음
    • 따라서 비즈니스 로직 상 최상위 객체를 사용하는 것이 권장됨
    Java
    Object 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);
  3. 리턴 타입의 다형성

    • 메서드의 리턴 타입으로 부모 타입을 지정하면 객체 타입에 따라 여러개의 메서드를 만들 필요가 없음

다형성의 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;
}