본문 바로가기
자바

상속과 다형성 기본

by 이상한나라의개발자 2023. 12. 11.

상속(Inheritance) : 한 클래스가 다른 클래스의 속성을 가져와 확정하거나 수정하는 메커니즘 입니다.

  • 자바는 다중 상속을 지원하지 않습니다. 그래서 extends 대상은 하나만 선택할 수 있습니다. 물론 부모가 상위 부모를 가지는 것은 괜찮습니다.
  • 만약 비행기와 자동차를 상속 받아서 하늘을 나는 자동차를 만든가고 가정해보면. 아래 그림과 같이 다중 상속을 사용하게 됩니다. AirplaneCar 입장에서 move()를 호출할 때 어떤 부모의 move() 사용해야 할지 애매한 문제가 발생합니다. 이것을 다이아몬드 문제라고 합니다. 그리고 다중 상속을 사용하면 클래스 계층 구조가 매우 복잡해질 수 있습니다. 이런 문제점 때문에 자바는 클래스의 다중 상속을 허용하지 않습니다. 대신에 인터페이스의 다중 구현을 허용하여 이러한 문제를 피합니다.

 

 

 

다형성(Polymorphism) : 다형성은 "여러 형태" 라는 뜻의 그리스어에서 유래 되었습니다. 자바에서의 다형성은 객체가 여라 형태를 가진 수 있음을 의미합니다.

  • 다형성 참조 : 하나의 변수 타입으로 다양한 자식 인스턴스를 참조할 수 있는 기능
  • 메서드 오버라이딩 : 기존 부모의 기능을 자식 타입에서 새로운 기능을 재정의 이때 오버라이딩한 메소드가 절대적으로 우선권을 가집니다.

 

컴파일 시간 다형성 : 주로 오버로딩을 통해 구현 됩니다. 같은 이름의 메서드를 여러 개 정의하지만 매개변수 타입이나 개수가 다를 때 이를 구분하여 사용하는 것입니다.

 

실행 시간 다형성 : 오버라이딩을 통해 구현됩니다. 상위 클래스의 메서드를 하위 클래스에서 재정의 하는 것입니다. 이 때, 상위 클래스의 참조 변수를 사용하여 하위 클래스를 참조할 수 있습니다.

 

public class ExtendsExampleMain {
    public static void main(String[] args) {
        Parent parent = new Parent();
        Parent extendedParent = new Child(); // 다형성
        Child child = new Child();

        parent.someMethod();
        extendedParent.someMethod();
        // Child 인스턴스 이지만 Parent 타입으로 선언되었기 때문에 사용 불가
        // extendedParent.anotherMethod();
        child.someMethod(); // Parent 에게 상속 받은 메서드
    }
}


public class Parent {
    public int parentPublicInt;
    protected int parentProtectedInt;
    private int parentPrivateInt;

    public void someMethod() {
        System.out.println("Parent someMethod");
    }
}


public class Child extends Parent {
    public void anotherMethod() {
        System.out.println("Child someMethod");
        this.parentProtectedInt = 0;
        this.parentPublicInt = 0;
        // this.parentPrivateInt = 0; // 상속 되지 않음 (private)
    }
}

 

 

오버라이딩(재정의) - Overriding

부모 클래스에서 상속 받은 메서드를 자식 클래스에서 다시 정의 하는 것

  • 멤버 변수는 재정의 불가
  • 메서드는 재정의 가능 , 이때 오버라이딩 된 메서드가 우선권을 가집니다.

 

public class ExtendsExampleMain {
    public static void main(String[] args) {
        Parent parent = new Parent();
        Parent extendedParent = new Child(); // 다형성
        Child child = new Child();

        parent.someMethod();
        extendedParent.someMethod();
        // Child 인스턴스 이지만 Parent 타입으로 선언되었기 때문에 사용 불가
        // extendedParent.anotherMethod();
    }
}

 

    @Override
    public void someMethod() {
        System.out.println("Child overriding someMethod");
    }

 

 

오버로딩(중복정의) - Overloading

 

동일한 메서드 이름으로 파라미터, 리턴 타입이 다른 메서드를 여러 개 정의 하는 것

public class OverloadingExampleMain {
    public static void main(String[] args) {
        AddCalculator addCalculator = new AddCalculator();

        int intResult = addCalculator.add(10, 20);
        long longResult = addCalculator.add(10L, 20L);
        double doubleResult = addCalculator.add(10.0, 20.0);

        System.out.println(intResult);
        System.out.println(longResult);
        System.out.println(doubleResult);
    }
}

public class AddCalculator {
    public int add(int num1, int num2) {
        return num1 + num2;
    }

//    return 타입만 다르게 오버로딩은 불가능
//    public long add(int num1, int num2) {
//        return num1 + num2;
//    }

    public long add(long num1, long num2) {
        return num1 + num2;
    }

    public double add(double num1, double num2) {
        return num1 + num2;
    }

}

 

 

올바른 상속을 위해서는 메서드 재사용 은 지양하며 필드에 대한 재사용으로 사용해야 합니다.

올바른 상속 -> 메서드 재사용 X, 필드에 대한 재사용 할때 사용한다. -> 전략 패턴 구성 (Composite) 활용

-> 부모 타입이 할 수 있는 일은 자식 타입도 할 수 있여야 함 -> 리스코프 치환 원칙

 

상속과 메모리의 구조 

  • 인스턴스 생성 : 자식 클래스의 인스턴스가 생성될 때, 부모 클래스의 필드도 해당 인스턴스 내에 포함되어 힙 메모리에 할당 됩니다.
    • 이때 생성된 인스턴스의 참조변수는 부모와 자식이 같은 참조값을 가집니다. 
상속관계에서의 힙 영역 메모리 할당

 

  • 메서드 오버라이딩 : 자식 클래스에서 부모 클래스의 메서드를 오버라이딩하면, 해당 메서드의 호출은 동적 바인딩을 통해 실행 시간에 결정됩니다. 이는 JVM이 메모리에서 해당 객체의 실제 타입을 확인하고 적절한 메서드를 실행하는 과정을 포함합니다.
  • 메서드 호출 : 객체는 메서드가 호출되면, 해당 메서드의 실행 정보 (매개변수, 로컬변수 등)는 스택 메모리에 저장합니다.
class Parent {
    public Parent() {
        System.out.println("Parent Constructor");
    }

    public void show() {
        System.out.println("Parent show");
    }
}

class Child extends Parent {
    public Child() {
        super(); // 부모 클래스의 생성자 호출, 생략할 경우 부모의 기본 생성자 자동으로 호출
        System.out.println("Child Constructor");
    }

    public void show() {
        super.show(); // 부모 클래스의 show 메서드 호출
        System.out.println("Child show");
    }
}

public class Test {
    public static void main(String[] args) {
        Child c = new Child(); // 부모와 자식 클래스의 생성자가 순서대로 호출됨
        c.show(); // "Parent show"와 "Child show"가 순서대로 출력됨
    }
}
 

 

요약하면 아래와 같습니다 

 

  • 상속 관계의 객체를 생성하면 그 내부에는 부모와 자식이 모두 생성 됩니다.
  • 상속 관계의 객체를 호출할 때, 대상 타입을 정해야 합니다. 이때 호출자는 타입을 통해 대상 타입을 찾습니다  
    • Parent extendedParent = new Child(); // 다형성
    • 이때 자식에서 부모 메서드를 오버라이딩 했다면 자식 메서드에 코드를 실행하게 된다.
    • 위에서 설명한 상속관계에서 인스턴스 생성은 부모 자식 모두 힙 메모리에 같은 참조 값으로 생성이 되고, 변수 extendedParent변수는 대상 (Parent) 에서 먼저 메소드를 찾고 해당 메서드를 오버라이딩한 자식 메서드의 코드를 실행하게 됩니다.
public class Parent {

    public void method1() {
        System.out.println("Parent.method1()");
    }
}


public class Child extends Parent {

    @Override
    public void method1() {
        System.out.println("Child.method1()");
    }

    public void chileMethod() {
        System.out.println("Child.childMethod()");
    }

    public static void main(String[] args) {
        Parent parent = new Child();
        // 부모 타입으로 메서드 호출, 이때 코드는 자식 타입의 코드가 실행된다.
        // 대상 타입의(Parent) 메서드를 호출해서 찾지만 인스턴스는 자식이기 때문이다.
        parent.method1();
    }
}

 

  • 현재 타입에서 기능을 찾지 못하면 부모 타입으로 기능을 찾아서 실행 합니다. 이때는 자식 타입으로 인스턴스를 생성합니다.
    • Child child = new Child();

'자바' 카테고리의 다른 글

예외  (0) 2023.12.11
캡슐화  (0) 2023.12.11
추상클래스와 인터페이스  (0) 2023.12.11
객체지향적으로 개발해야 하는 이유  (0) 2023.12.11
동시성 이슈  (2) 2023.12.11