본문 바로가기
programming/JAVA

[JAVA] 다형성(Polymorphism) 개념 정리

by buddev 2020. 4. 26.

자바의 Polymorphism (다형성) 에 관한 정리입니다.

onenote 필기자료를 github 업로드를 위해 .md 파일로 다시 정리하고 있습니다.

혹시 잘못된 부분이 있다면 댓글 달아주시면 바로 수정하겠습니다! :)


Polymorphism (다형성)

object polymorphism

같은 타입의 변수가 다양한 형태의 객체를 참조하는 것. super type의 변수가 다양한 sub type 을 참조하는 형태. 명시적으로 형변환을 하지 않아도 자동으로 형변환 된다.

sub 객체 생성 시 super도 같이 생성되어지기 때문에 메모리에 존재하는 super type 으로 변수를 선언할 수 있다.

 

method polymorphism

같은 클래스 타입의 같은 method를 호출 시 기능이 다양하게 처리 되는 것.

Overloading method call과 Overriding method call로 구현 가능.

 

super class의 참조가 자신을 상속한 객체를 모두 가리킬 수 있다는 up casting을 이용하여,

super class를 override 한 메소드 여러 개를 표시할 수 있는 것을 의미한다.

 

car.drive(); 라 하면 car 변수가 가리키는 객체는 차, 버스, 택시, 승용차일 수 있다.

여기서 drive()는 car가 어떤 클래스의 객체냐에 따라 네개의 drive() 중에 하나가 동작하게 된다.

코드 상에서는 동일한 drive() 호출처럼 보이지만 실제로 실행될 때는 객체가 어떤 것이냐에 따라 다른 drive()가 호출된다.


Reference type casting(자동(묵시적) 형변환)

sub 객체 내에는 항상 super 객체가 같이 있기 때문에 super 객체 타입으로 sub 객체를 레퍼런스 할 수 있다

-> 메소드 오버로딩을 줄일 수 있다.

 

즉, 특정 메서드의 파라미터 타입으로 super 타입을 설정해 놓으면, 다양한 자식 객체를 받아서 형변환 없이 편하게 사용할 수 있다(대신 부모에 있는 내용 범위 안에서만 접근 가능, 자식에서 추가한 범위까지 접근하려면 강제 형변환이 필요하다).

class Car {
    String name;
    int number;
    public Car(String name, int number){
        this.name = name;
        this.number = number;
    }

    public static String drive() {
        return "차 운전";
    }
}

class Bus extends Car {
    int fee;
    public Bus(String name, int number, int fee){
        super(name, number);
        //this.name = name;
        //this.number = number;
        this.fee = fee;
    }

    @Override
    public static String drive() {
        return "버스 운전";
    }
}

public static void main(String[] args) {
    Bus b = new Car();
}

 

super가 가지고 있는 member만 사용 가능하다

위 코드에서 b.fee로 접근하면 오류 발생!

why?

-> b는 Car의 속성에만 접근 가능하다. 메모리에 Car의 name, number와 Bus의 fee 모두가 있는 것은 맞지만,

b는 Car의 변수에만 접근 할 수 있고 Bus의 변수인 fee에는 접근할 수 없다. 이를 '가려져 있다'고 표현하기도 한다.

 

그럼에도 불구하고 쓰는 이유는 호출에 대한 편리성과 유지보수, 재사용성에 대한 강점 때문.

레퍼런스 타입의 변수들은 상속관계일 경우 형변환이 허용된다.

예를 들면 Car c = mgr.getCar("차"); 를 수행하면 c 자리에 Bus가 와도 되고, Car가 와도 된다. 이런 강점이 있음.


강제 형변환

sub가 가지고 있는 memeber를 쓰고 싶다면

만약, sub 객체인 Bus가 가지고 있는 fee를 꺼내고 싶다면 타입을 원래 타입인 Bus로 변경(강제형변환, 여기서는 down casting) 해야 한다.

Bus bb = (Bus) b; 이런 식으로
(처음부터 쓴다면 Bus bb = (Bus) new Car(); 이렇게 쓸 수 있다)

 

이 때 b가 항상 sub객체를 가지고 있는게 아니라, super 객체(Car)만 가지고 있을 수도 있다. 그런데 그 상황에서 sub 타입으로 강제 형변환을 요청하면 ClassCastException 이 발생한다. 그러므로 강제 형변환을 하기 전 반드시 서브 객체를 가지고 있는지를 확인한 후에 형변환을 해야 한다.

if (b instanceof Bus) {
    Bus bb = (Bus) b;
}

 

instanceof 는 객체의 존재 여부를 물어보는 연산자이다.

변수명 instanceof 객체타입 형식으로 사용하고, 이 때 객체타입은 반드시 sub객체(Bus)로 설정해야 한다.

super 객체는 sub 객체가 생성될 때 항상 같이 생성되기 때문에, super객체로 확인하면 늘 true 값을 반환할 수 있다.


동적 바인딩

컴파일러의 구현 관점에서 어떤 함수를 호출하는가에 대한 것.

참조 변수에 대해 메소드를 호출할 때 실제로 호출되는 메소드가 어떤 것인지를 결정하는 방법이다.

동적바인딩이란 어떤 함수를 호출할 지가 컴파일할 때 까지도 결정되지 않았다는 뜻이다.

 

즉, 실행시켜 봐야지만 어떤 함수가 실행될지를 알 수 있다. car 객체가 그냥 차인지, 버스인지, 택시인지 등은 실행시켜 봐야지만 알 수 있다.

 

참고
https://plas.tistory.com/29


override

b.메소드 실행 시 컴파일러는 super에 해당 메소드가 있는지를 체크하지만,

실행 시에는 만약 sub클래스에 overriding되어 있는 method가 있다면 override 된 sub 클래스의 메소드를 수행한다.

 

예를 들어, Car c = new Bus(); 에서 c.toString 을 했을 때 Car의 toString이 아니라

Bus에서 새롭게 overriding 한 toString을 사용한다!

(단, Bus에서 overriding을 했을 때에만. 안했으면 부모의 toString을 사용한다.)

여러 단계에 거쳐서 상속이 이루어졌다면, 가장 상위~지금 내 클래스 중에서 가장 마지막에 override 된 메소드를 사용한다.

 

그렇기 때문에 부모 클래스에서는 method를 내용 없는 추상 메소드로 작성해도 괜찮다. 내용이 없어도 자식 클래스에서 override한 메소드가 있다면 그 메소드를 사용 할 것이기 때문이다.

 

단, 이렇게 override해서 같은 함수를 재정의하는 것은 method에만 적용되는 내용이다. 변수에는 적용되지 않는다.

만약 Car의 name을 Bus에서 재정의하려는 목적으로 다시 쓴다 해도 컴퓨터는 이를 전혀 다른 변수라고 인식한다.

때문에 절대 이렇게(부모와 자식 간에 같은 이름의 변수를 사용하게) 코드를 짜서는 안 된다.


다형성의 다양한 사용

  1. 배열에 적용

    다형성에 의해 모든 sub 타입은 super 로 형변환이 자동으로 되기 때문에 super 타입의 배열 하나만 선언하면 super 타입의 객체 뿐 아니라 sub 타입의 객체도 저장할 수 있다.

  2. 리턴타입에 적용

    다형성에 의해 모든 sub 타입은 super 로 형변환이 자동으로 되기 때문에 리턴타입을 super로 선언하면 sub도 리턴할 수 있다.

  3. 메서드 인자 적용

    다형성에 의해 모든 sub 타입은 super 로 형변환이 자동으로 되기 때문에 메서드 인자를을 super로 선언하면 모든 sub타입의 객체도 인자로 전달받을 수 있다. -> 메서드 overloading을 줄일 수 있다.

댓글