본문 바로가기
Java관련/Java

Java 상속, 오버라이딩, 다이나믹 메소드 디스패치

by devstep 2022. 8. 29.

목차

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스

클래스 재사용 방법

  1. 상속
  2. 포함 Composit : 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언

자바의 상속(Inheritance)

상속을 받는 것은 조상 클래스를 확장(extend)한다는 의미로 해석할 수 있습니다.

  • 기존 클래스를 재사용하여 새로운 클래스 작성

코드 중복은 일관성 유지를 어렵게 해 유지보수가 어려워지게 하는 요소입니다. 같은 내용의 코드를 부모 클래스 한 곳에서 관리하므로 공통된 내용이 변경될 때 부모 클래스 하나만 변경할 수 있습니다.

  • 코드 재사용
  • 코드 중복 제거

조상 클래스가 변경되면 자손 클래스는 영향 받게 되지만, 자손 클래스가 변경되는 것은 조상 클래스에 영향을 주지 않습니다. 그러므로 파생 클래스 하나만 변경사항이 있을 경우 코드 변경이 유연합니다.

  • 공통으로 코드 관리로 코드의 변경, 추가 용이

용어

  • 조상클래스 : 부모/상위/기반 클래스, parent/super/base class
  • 자손클래스 : 자식/하위/파생된 클래스, child/sub/derived class

상속 계층도, 다이어그램

  • 상속 계층도


    (image: javatpoint.com/inheritance-in-java)

자바 상속의 특징

  • 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속됩니다.
  • 접근제어자가 private, default인 멤버는 상속되지 않는다기보다 자손클래스로부터 접근이 제한되는 것입니다.
  • 자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버도 함께 생성되기 때문에 따로 조상 클래스의 인스턴스를 생성하지 않고도 조상 클래스의 멤버들을 사용할 수 있습니다.
    • JVM 관점에서는 어떻게 이해할 수 있을까?
      모든 인스턴스 메서드에는 인스턴스의 주소가 super, this 지역변수로 저장된다고 하는데, 그렇다면 조상 클래스의 인스턴스도 생성되는 것 아닌가?
      super, this 지역변수와 연관지어서 조사하기.
      super()에 적어둔 내용과 함께 보기.

메소드 오버라이딩

상속받은 메서드의 내용을 변경하는 것

오버라이딩의 조건

  1. 접근제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없습니다.
  2. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없습니다.
    • 상위예외로 변경했을 때 많은 수의 예외를 던질 수 있으므로 선언 불가능
  3. 인스턴스 메서드를 static 메서드나 그 반대로 변경할 수 없습니다.
  • 참고: static 멤버는 정의된 클래스에 묶여있다고 생각하기! 조상 클래스에 있는 static 메서드를 자손 클래스에서 똑같은 이름으로 정의 했을 때 별개의 static 메서드를 정의한 것입니다.

super 키워드

모든 인스턴스 메서드에는 인스턴스의 주소가 지역변수로 저장됩니다. 그 지역변수가 this와 super의 값이 됩니다.

  • super라는 지역변수가 생기는지는 확실하지 않고 확인해봐야할 사항.

super 키워드로 멤버변수 접근

  • 자손 클래스에서 상속받은 멤버를 참조하는데 사용하는 참조변수
  • 상속받은 멤버도 자신 클래스 자신의 멤버이므로 super대신 this를 사용할 수 있다.
  • 그러나 멤버변수와 지역변수의 이름이 같을 때 this로 구별하듯이, 상속받은 멤버와 자신의 멤버가 이름이 같을 때 super를 붙여 구분합니다.
  • 즉, 조상과 자손 클래스에 멤버가 중복 정의되어 구별하는 경우에만 super를 사용하는 것이 좋습니다.

super 키워드로 메서드 접근

메서드 역시 super를 써서 호출할 수 있습니다. 조상 클래스의 메서드를 자손 클래스에서 overriding할 때 super를 사용할 수 있습니다.
조상 클래스의 내용에 추가적으로 작업을 덧붙이는 경우, super를 사용해 조상클래스의 메서드의 내용을 포함시키면 조상 클래스의 메서드 내용이 변경될 때 자동 반영됩니다.

super() : 조상 클래스의 생성자

  • this(), super()는 생성자
  • this() : 같은 클래스의 다른 생성자 호출
  • super() : 조상 클래스의 생성자를 호출

자손 클래스의 인스턴스를 생성하면 자손/조상 멤버가 모두 합쳐진 하나의 인스턴스가 생성됩니다.
이때 조상 클래스 멤버의 초기화 작업이 수행되어야 해서 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 합니다.
모든 클래스의 생성자는 첫 줄에 반드시 this()로 자신의 다른 생성자를 호출하거나 super()로 조상의 생성자를 호출해야 합니다. 그렇지 않을 경우 컴파일러가 자동으로 생성자 첫 줄에 super()를 추가합니다.
그 이유는 자손 클래스의 멤버가 조상 클래스의 멤버를 사용하는 경우 조상의 멤버들이 먼저 초기화되어야 하기 때문입니다.

  • 조상 클래스의 생성자가 호출되야 한다는 것은 조상 클래스의 인스턴스가 생성된다는 말인데 자손/조상 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다는 말과는 맞지 않는 말인 것 같다. 상속한 클래스의 인스턴스가 어떤 형태로 생성되는지 찾아봅시다.

다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

동적 메소드 디스패치는 재정의(overring)된 메소드 호출이 컴파일 타임이 아닌 런타임에 확인되는 원리입니다.
메서드 overring(재정의)은 자바가 런타임 다형성을 지원하는 방법 중 하나입니다.

재정의(overring)된 메소드 호출은 업캐스팅을 통해 이루어집니다.
업캐스팅(upcasting)은 슈퍼클래스 참조 변수를 통해 서브클래스 객체를 참조할 수 있는 것을 말합니다.
오버라이드된 메소드가 수퍼클래스 참조를 통해 호출되면 호출이 발생한 시점에 참조되는 객체 타입에 따라 수퍼클래스/서브클래스 중 어떤 메소드의 버전을 실행할지 결정합니다.

Dynamic Method Dispatch의 장점

  1. 동적 메소드 디스패치를 통해 Java는 런타임 다형성의 핵심인 메소드 재정의를 지원할 수 있습니다.
  2. 메소드 재정의를 통해 클래스는 모든 파생 클래스에 공통 메서드를 지정할 수 있습니다. 파생 클래스는 공통 메소드의 일부 또는 전체의 구현을 정의할 수 있습니다.

정적 및 동적 바인딩(Static vs Dynamic binding) 차이

  • 정적 바인딩은 컴파일 시간 동안 수행되고 동적 바인딩은 런타임 중에 수행됩니다.
  • private/final/static 메소드와 변수는 static(정적) 바인딩을 사용하고 컴파일러에 의해 연결됩니다.
  • 반면 재정의된 메서드(overring)는 런타임 객체 타입에 따라 런타임에 연결됩니다.
// A Java program to illustrate Dynamic Method
// Dispatch using hierarchical inheritance
class A
{
    void m1()
    {
        System.out.println("Inside A's m1 method");
    }
}

class B extends A
{
    // overriding m1()
    void m1()
    {
        System.out.println("Inside B's m1 method");
    }
}

// Driver class
class Dispatch
{
    public static void main(String args[])
    {
        // object of type A
        A a = new A();

        // object of type B
        B b = new B();  

        // obtain a reference of type A
        A ref;

        // ref refers to an A object
        ref = a;

        // calling A's version of m1()
        ref.m1();

        // now ref refers to a B object
        ref = b;

        // calling B's version of m1()
        ref.m1();         
    }
}
  • output
  • Inside A's m1 method Inside B's m1 method

런타임 다형성은 멤버변수에서는 이뤄지지 않는다

  • 런타임 다형성이 멤버변수에서는 이뤄지지 않는 예시

클래스A 타입인 a는 클래스B의 객체를 생성합니다. 그러나 변수는 재정의되지 않으므로 a.x는 항상 상위 클래스의 데이터 멤버를 참조합니다.

// class A
class A
{
    int x = 10;
}

// class B
class B extends A
{
    int x = 20;
}

// Driver class
public class Test
{
    public static void main(String args[])
    {
        A a = new B(); // object of type B

        // Data member of class A will be accessed
        System.out.println(a.x);
    }
}
  • output
  • 10

추상 클래스

추상 메소드를 포함하고 있는 부분이 일반 클래스와 차이점이고 나머지는 일반 클래스와 동일하게 생성자, 멤버변수, 메소드를 가질 수 있습니다.
추상 메소드는 구현부만 있고 선언부가 없는 메서드입니다.

자손 클래스는 오버라이딩을 통해 추상 클래스의 추상 메서드를 모두 구현해야 합니다. 만일 상속받은 추상 메소드 중 하나라도 구현하지 않으면 자손 클래스도 추상 클래스로 지정해 주어야 합니다.

아래 코드는 커스텀 익셉션을 추상클래스로 구현한 예시입니다.

  • 커스텀 익셉션을 3단계로 나누어 클래스를 설계한 모습입니다. 가장 최상위 추상 클래스인 BusinessException는 커스텀 익셉션이 가져야할 메서드 2개를 선언했고, 중간 단계 추상 클래스에서는 메소드 중 하나인 getHttpStatus()를 구현하여 httpStatus코드를 동일하게 묶을 수 있게 하였습니다. 그리고 가장 하위 클래스인 일반 클래스는 특정 도메인을 지정하여 그에 맞는 에러 메세지를 getBodyMessage() 메소드로 표현한 구조입니다.
public abstract class BusinessException extends RuntimeException {

    protected abstract HttpStatus getHttpStatus();

    protected abstract String getBodyMessage();
}

public abstract class NotFoundException extends BusinessException {

    @Override
    protected HttpStatus getHttpStatus() {
        return HttpStatus.NOT_FOUND;
    }
}

public class IssueNotFoundException extends NotFoundException{

    @Override
    protected String getBodyMessage() {
        return "There is no corresponding issueId.";
    }
}

추상클래스 규약

추상 클래스의 추상 메소드는 구현부를 작성하지 않고 {} 처럼 빈 몸통으로 작성해도 오버라이딩(overring)이 가능하나 abstract를 붙여서 반드시 구현하도록 강제합니다. 그럼으로써 해당 메소드가 구현되지 않음을 나타냅니다.


final 키워드

final 메소드 static(정적) 바인딩을 사용하고 컴파일러에 의해 연결되기 때문에 위에서 이야기한 런타임시 동적 메서드 디스패치를 할 수 없어 메소드 확장인 오버라이딩을 할 수 없습니다.

제어자 대상 의미
final 클래스 변경(확장)될 수 없는 클래스. 다른 클래스의 조상이 될 수 없다
final 메서드 변경(확장)될 수 없는 메서드. 오버라이딩을 통해 재정의될 수 없다
final 멤버/지역변수 값을 변경(재할당)할 수 없는 상수가 된다
  • 대표적인 final클래스는 String, Math 가 있습니다.

특징

final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 수행합니다. 하지만 인스턴스 변수의 경우 생성자에서 초기화할 수 있습니다.
그렇기 때문에 각 인스턴스마다 final이 붙은 멤버변수가 다른 값을 갖는 것이 가능합니다.


Object 클래스

모든 클래스의 최고 조상 클래스로 Object 클래스의 멤버는 모든 클래스에서 사용할 수 있습니다.

Object 클래스 메서드 설명
protected Object clone() 객체의 복사본 반환
getClass() 객체 자신의 클래스 정보를 담고 있는 Class 인스턴스 반환
equals() 같은 객체인지 확인
hashcode() 객체의 해시코드 반환
notify() 객체 자신을 사용하려고 기다리는 쓰레드 하나만 깨운다

참고자료

'Java관련 > Java' 카테고리의 다른 글

Java static, static final, final 차이  (0) 2022.09.15
자바 상속의 메모리 할당 과정  (0) 2022.09.05
JVM과 자바 실행 과정 알아보기  (2) 2022.08.07
Java 데이터 타입, 변수, 배열  (0) 2022.07.31
NoSuchElementException  (0) 2022.04.16

댓글