Overriding을 사용하는 이유

Haiseong Jeong·2022년 10월 18일
5
post-thumbnail

다른 무엇보다 더 중요한, 최우선시 되는
출처 : 네이버 사전

polymorphism

다형성(polymorphism)이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미한다. 자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있다. 다형성은 상속, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징이다.

overriding

오버라이딩(overriding)이란 상속 관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니쳐를 갖는 메소드로 다시 정의하는 것이다. 자바에서 자식 클래스는 부모 클래스의 private 멤버를 제외한 모든 메소드를 상속받는다. 이렇게 상속받은 메소드는 그대로 사용해도 된다. 하지만 때론 필요한 동작을 위해 재정의하여 사용할 수도 있다. 오버라이딩이란 상속받은 부모 클래스의 메소드를 재정의하여 사용하는 것을 의미한다.

오버라이딩의 조건

자바에서 메소드를 오버라이딩하기 위한 조건은 다음과 같다.

1. 메소드의 선언부는 기존 메소드와 완전히 같아야 합니다.

2. 부모 클래스의 메소드보다 접근 제어자를 더 좁은 범위로 변경할 수 없다.

3. 부모 클래스의 메소드보다 더 큰 범위의 예외를 선언할 수 없다.

사용법

코드

class Parent {
    void display() { System.out.println("부모 클래스의 display() 메소드"); }
}
class Child extends Parent {
    void display() { System.out.println("자식 클래스의 display() 메소드"); }
}
 
public class InheritanceExample {
    public static void main(String[] args) {
        Parent pa = new Parent();
        pa.display();
        Child ch = new Child();
        ch.display();
        Parent pc = new Child();
        pc.display(); // Child cp = new Parent();
    }
}

실행결과

부모 클래스의 display() 메소드
자식 클래스의 display() 메소드
자식 클래스의 display() 메소드

위에 overriding의 뜻을 적어두었다. '다른 무엇보다 더 중요한, 최우선시 되는'이라는 의미대로 자식은 부모의 display() 메소드가 아닌 자신의 display() 메소드를 실행한다.

Parent pc = new Child();
이 부분에서는 부모 클래스의 레퍼런스 변수에 자식 클래스 인스턴스를 담는다. 여기서 의문이 생겨야 한다.

1.어떻게 부모 변수에 자식이 들어가는가?
2.pc는 부모의 레퍼런스 변수인데 어떻게 자식의 메소드를 실행시키는가?

업캐스팅

1번 질문에 대한 답이다. 우선 캐스팅은 '타입을 변환하는 것'을 말한다. 다른말로 형변환 이다.

업캐스팅이란 자식 클래스의 객체가 부모 클래스 타입으로 형변환 되는 것을 말한다. 자식 클래스는 부모 클래스의 확장판이다. 따라서 부모의 모든 속성을 가지고 있는데 따라서 부모형으로 형변환해도 모든 기능을 수행할 수 있기 때문에 업캐스팅은 가능하다.

혹시 다운캐스팅이 궁금하다면? 업이 있으면 다운도 있나? 맞다. 다운캐스팅은 반대로 부모 클래스의 객체가 자식 클래스 타입으로 형변환 되는것이다. 이건 일반적으로 무리가 있다. 왜냐하면 부모는 자식이 가진 기능을 모두 가지고 있자 않다. 하지만 가능한 경우가 있는데 바로 부모형으로 참조되고 있던 객체가 사실 자식 클래스 였을때다.
class Parent {
    void display() { System.out.println("부모 클래스의 display() 메소드"); }
}
class Child extends Parent {
    void display() { System.out.println("자식 클래스의 display() 메소드"); }
}
 
public class InheritanceExample {
    public static void main(String[] args) {
        Parent pc = new Child();
        Child c = (Child) pc;
    }
}

원래 pc는 Child 클래스의 객체인데 부모 클래스 레퍼런스로 참조되고 있었다면 다운캐스팅으로 다시 자식객체로 돌아갈 수 있다.

Dynamic binding

자 이제 1번 질문은 해결했고 2번질문으로 넘어간다.

2.pc는 부모의 레퍼런스 변수인데 어떻게 자식의 메소드를 실행시키는가?

컴퓨터는 어떻게 자식의 메소드와 부모 메소드중 실행시킬 메소드를 고르는 것일까? 그리고 언제 고르는것일까?

static 함수들은 컴파일 되는 시점에 어떤 메소드를 실행시킬지 정해진다. 이것을 정적 바인딩(Static binding)이라 한다. 그와 반대대는 개념은 동적 바인딩(Dynamic binding)이다. 동적 바인딩은 런타임, 즉 실행되는 중에 어떤 메소드를 실행시킬지 정한다. Overriding된 메소드는 동적 바인딩이 된다.

부모에도 display() 메소드가 있지만 자식의 display() 메소드가 오버라이딩 되어 있기때문에 우선권을 갖는다. 따라서 런타임 환경에서 자식의 display() 메소드를 링킹하는것이다.

그래서 왜 배움?

항상 나도 궁금했던 내용이었다. 왜냐하면 거의 모든 책이나 블로그에서 이 정도로 끝낸다. '부모의 메소드를 재정의 하는것은 알겠는데 굳이? 그냥 새로 메소드 하나 더 만들면 안되나?' 라는 생각을 했던것 같다. 하지만 이번에 학교수업을 들으면서 얻은 정보를 공유해 보려한다.

우선 가정을 하나 해야한다. 우리는 게임을 만들고 있는 상황이다. 슈팅게임이다. 우리는 우주선을 조정해 총을 쏘고 적 비행기를 맞추면서 앞으로 나아간다.

우선 게임에 필요한 객체를 만들어야 한다. 우리의 우주선, 적 비행기, 총알, 게임에 나오는 구름 .. 등등이 있을 것이다. 이 객체들은 각각 특성이 다르지만 공통점이 하나 있다. 바로 우리가 화면에 출력해야 한다는 점이다.

이제 우리는 생각한다.

음 우선 내 우주선은 하나니까 변수 하나 만들고, 비행기는 배열로만들고, 총알 배열, 구름도 배열로 넣고,, 그다음에 각각 반복문 돌리면서 display() 메소드 실행 하면 되겠네...

이렇게 할 수 있다. 그런데 더 좋은 방법이 있다. 우선 위의 방법은 너무 코드가 길어지고 관리하기 힘들어 진다. 왜냐하면 이미 변수, 배열, 반복문... 필요한 객체가 더 들어나면 더 길어진다. 이렇게 하면 어떨까.?

우선 공통조상 GameObject를 만들고 그 조상을 각 클래스가 상속받도록 하자. 그리고 조상 클래스에 display() 메소드를 정의하고 각 클래스에서 오버라이딩 한다음에 메인에는 GameObject 배열 하나만 만들어서 인스턴스들은 업캐스팅해서 배열에 넣고 반복문에서 display() 메소드를 실행하자.

우선 이렇게 되면 배열 하나와 반복문 하나로 게임 화면 출력기능을 해결 할 수 있다. 각각 객체의 출력 방식은 다르지만 오버라이딩을 사용한다면 각각 동적 바인딩되어 반복문 하나로도 각각의 출력을 할 수 있다.

profile
나는 개발자다. 5000만큼 코딩한다.

0개의 댓글