자바의 상속(inheritance)

    자바의 상속


    실제 상속은 부모가 자식에게 재산을 물려주는 행위를 말합니다. 자바에서의 상속의 개념은 기존 클래스의 필드메서드를 새로운 클래스에서 재사용하게 해 줍니다. 이름 그대로 기존 클래스의 속성과 기능을 그대로 물려받을 수 있습니다.

     

    먼저 상속 관련 용어에 대해서 알아보도록 하겠습니다.

    • 부모 클래스(슈퍼 클래스) : 상속을 통해서 자신의 필드와 메서드를 다른 클래스에게 제공하는 클래스를 의미합니다.
    • 자식 클래스(서브 클래스) : 부모 클래스(슈퍼 클래스)로부터 필드와 메서드를 상속받는 클래스를 의미합니다.

     

    상속을 통해, 기존에 존재하는 잘 개발된 클래스를 재사용해서 새로운 클래스를 생성하고 몇 가지 기능들만 추가할 수 있고, 부모 클래스를 수정하게 되면 상속 관계에 있는 자식 클래스들도 수정되어 작업의 효율성을 극대화시킬 수 있습니다.

     

     

     

     

    클래스 상속


    자바에서의 상속은 현실에서의 상속의 개념과는 약간의 차이가 있습니다. 실제로는 부모가 자식을 선택해서 상속을 하는 반면, 자바에서는 자식 클래스가 부모 클래스를 선택할 수 있습니다. 선택한 부모 클래스의 클래스 명을 클래스 선언부 우측에  extends 키워드를 작성하여 상속을 받습니다.

     

    상속은 연속해서 진행할 수 있습니다. 상속 관계에 있는 부모 클래스가 다른 클래스를 부모 클래스로 선택해서 자식 클래스가 될 수도 있습니다. 이전의 상속 관계는 그대로 유지됩니다.

    public class SubClass extends SuperClass {
    }

     

    다른 프로그래밍 언어와 다르게 자바에서는 한 번에 다중 상속을 허용하지 않습니다. 따라서  extends 키워드 뒤에는 하나의 클래스 명만 작성 가능합니다.

    public class SubClass extends SuperClass1, SuperClass2 { //컴파일 오류 발생
    }

     

    만일, 다중 상속을 허용하게 된다면 다음과 같은 문제가 발생할 수 있습니다.

    public class SuperClass1 {
    	public int add(int num) {
        	int result = num + 3;
            return result;
        }
    }
    public class SuperClass2 {
    	public int add(int num) {
        	int result = num - 7;
            return result;
        }
    }

    위와 같이 동일한 메서드 명을 가지고 있는 클래스 두 개를 자식 클래스에서 상속받아서 해당 자식 클래스를 통해 add() 메서드를 호출했을 때, 어떤 메서드를 호출해야 하는지 결정할 수 없게 되는 문제가 발생합니다. 이러한 문제를 '다이아몬드 문제'라고 합니다. 이러한 상황을 방지하기 위해서 자바는 다중 상속을 허용하지 않습니다.(인터페이스는 허용)

     

     

     

     

    상속의 메모리 구조


    부모 클래스와 상속 관계를 이루고 있는 자식 클래스를 통해 객체를 생성했을 때, 부모 클래스로부터 단순하게 필드와 메서드만을 상속받아서 하나의 객체를 생성할 것 같지만, 실제로는 그렇지 않습니다.

     

    힙 메모리에는 자식 객체만 생성되지 않고, 사실은 부모 객체도 같이 생성됩니다. 순서를 따지자면 부모 클래스가 먼저 생성되고 후에 자식클래스 객체가 생성됩니다. 스택 영역에서 참조하는 참조값은 동일하지만, 해당 메모리 주소에는 부모 클래스와 자식 클래스의 객체가 공존하게 됩니다.

     

    아래의 예시를 통해서 이해해 봅시다.

    public class Pencil {
        public void write() {
            System.out.println("글씨 쓰기");
        }
    }
    public class Sharp extends Pencil{
        /*
        Sharp() {
        	super(); //부모 객체 생성자
        }
        생성자 내부에 부모 객체 생성자 자동 생성*/
        public void click() {
            System.out.println("클릭");
        }
    }
    public class Main {
        public static void main(String[] args) {
            Sharp sharp1 = new Sharp();
            sharp1.click();
            sharp1.write();
        }
    }

    상속 관계에 있는 클래스를 정의하면 컴파일 과정에서 자동으로 자식 클래스 생성자에 super() 생성자를 추가합니다. super() 생성자는 부모 클래스의 객체를 생성하는 역할을 합니다.

     

    만약, 부모 클래스에 기본 생성자가 아닌 직접 생성자를 정의했다면, 자식 클래스 생성자에도 super() 생성자를 직접 작성해 주어야 합니다.

     

    이후  메인 메서드에서 Sharp 클래스의 객체를 생성하면  super() 생성자를 통해 부모 클래스의 객체를 생성해서 동시에 두 개의 객체를 생성하게 됩니다.

     

    sharp1.click()를 호출하게 되면 sharp1에 저장된 참조 값을 통해서 click() 메서드를 호출하게 됩니다. 이때, 상속 관계에 놓여 있는 객체에 경우에는 부모와 자식 클래스의 객체 두 가지가 동시에 존재하기 때문에, 먼저 어떠한 객체를 참조할지 선택하게 됩니다. 선택하는 기준은 호출하는 참조 변수(reference variable)의 타입(클래스)을 기준으로 선택합니다.

     

    sharp1의 경우 변수의 타입이 sharp 클래스이므로 먼저 sharp클래스를 통해서 click() 메서드를 호출하게 됩니다.

     

    만약 여기서 click() 메서드를 호출하지 않고 wirte() 메서드를 호출한다면, 이전과 동일하게 sharp1 변수에 저장된 참조값을 통해서 객체에 접근하고, 먼저 Sharp클래스를 통해서 write() 메서드를 찾지만 이경우에는 write() 메서드가 존재하지 않으므로 부모 클래스에 접근해서 write() 메서드를 찾습니다. 부모 클래스에는 write() 메서드가 존재하므로 해당 메서드를 호출하게 됩니다.

     

    만약 부모 클래스에 존재하지 않는다면 컴파일 오류가 발생합니다.

     

    이는 메서드뿐만 아니라 인스턴스 변수에도 동일한 원리로 작동합니다.

     

    정리하자면 다음과 같습니다.

    1. 상속 관계에 있는 클래스의 객체를 생성하면 해당 객체 내부에는 부모와 자식 객체 모두 생성됩니다.
    2. 객체를 생성할 때 선언한 참조 변수의 클래스 유형을 통해서 먼저 접근할 객체를 선택합니다.
    3. 먼저 접근한 객체에서 해당 필드나 객체를 찾지 못한다면 상위 객체에 접근해서 찾게 됩니다. 찾지 못하게 되면 컴파일 오류가 발생합니다.

     

     

     

     

    메서드 오버라이딩(Method Overriding)


    상속 관계에서 부모 클래스에 존재하는 메서드를 자식 클래스에서 다르게 재정의 할 수 있습니다.

    이러한 기능을 메서드 오버라이딩(Method Overriding)이라고 부릅니다.

     

    오버라이딩을 하지 않으면 상속 받은 메서드 그대로 사용 가능합니다.

    public class Pencil {
        public void write() {
            System.out.println("글씨 쓰기");
        }
    }
    public class Sharp extends Pencil{
        @Override
        public void write() {
            System.out.println("샤프로 글씨 쓰기");
        }
    }

    위와 같이 상속받은 클래스의 메서드의 이름은 그대로 사용하고 싶지만, 구현부의 기능은 다르게 하고 싶을 때, 직접 자식 클래스에서 새로 작성하여 오버라이딩 할 수 있습니다. 이때, 재정의 한 메서드에 @Override 애너테이션을 붙여서 오버라이딩 한 메서드임을 표시합니다.

    애너테이션이란?
    자바 소스코드에 추가해서 사용 가능한 메타 데이터의 일종입니다.
    프로그램이 읽을 수 있는 특별한 주석이라고 생각하면 됩니다. 이 애너테이션을 통해서 해당 문서를 읽는 사람이 알아보기 쉽게 하거나 컴파일러에게 해당 메서드가 올바른 문법으로 작성되었는지 확인할 수 있도록 합니다.
    @Override 애너테이션은 부모 클래스의 메서드를 자식 클래스에서 제대로 오버라이딩이 되었는지 확인합니다.

     

    메서드 오버라이딩 작성 규칙

    • 메서드 이름이 같아야 합니다.
    • 메서드의 매개 변수의 유형이 모두 같아야 합니다.(매개변수 타입, 순서, 개수)
    • 반환 타입이 같아야 합니다.
    • 접근 제한자가 오버라이딩 하고자 하는 메서드의 접근 제한자보다 더 제한적 이선 안됩니다. 예를 들어, 상위 클래스의 접근 제한자가 protected이면 private 또는 default 접근 제한자를 사용할 수 없습니다.
    • 생성자는 오버라이딩 할 수 없습니다.
    • static, final, private 키워드가 붙은 메서드는 오버라이딩 할 수 없습니다.

     

     

     

     

    final의 상속


    final 키워드를 붙인 클래스, 메서드, 필드들은 자식 클래스로 상속할 수 없습니다. final은 최종적으로 결정했다는 의미로 더 이상 변경 할 수 없기 때문에 상속 관계가 성립되지 않습니다.

    public final class FinalClass {} // 상속 불가
    
    public final finalMethod() {} // 상속 불가
    
    public final int finalValue; // 상속 불가

    'Backend > Java' 카테고리의 다른 글

    자바의 객체 지향 프로그래밍  (0) 2024.05.30
    자바의 배열(array)  (2) 2024.03.01
    자바의 final  (0) 2024.02.28
    자바의 생성자(constructor)  (0) 2024.02.19
    자바의 패키지(package)  (2) 2024.02.16

    댓글