상속

한라봉봉·2023년 12월 16일

JAVA

목록 보기
12/16

1. 상속 - 시작 상속이 왜 필요한가?

공통되는 부분이 있는경우 추상적인 부모를 두고 상속하는 것이 유용하다.

2. 상속관계

객체지향 프로그래밍의 핵심요소중 하나로, 기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용하게 해준다. 이름 그대로 기존 클래스의 속성과 기능을 그대로 물려받는 것이다. 상속을 사용하려면 extends 키워드를 사용하며, 대상은 하나만 선택할 수있다.

  1. 용어정리
    1) 부모 클래스(슈퍼 클래스): 상속을 통해 자신의 필드와 메서드를 다른 클래스에 제공하느 클래스
    2) 자식 클래스(서브 클래스): 부모 클래스로부터 필드와 메서드를 상속받는 클래스

  2. 상속구조
    자식이 부모의 기능을 물려받아 사용할 수 있다. 반대로 부모 클래스는 자식 클래스에 대한 정보가 없으므로 접근할 수 없다.

  3. 단일 상속
    자바는 다중 상속을 지원하지 않는다. extend 대상은 하나만 선택할 수 있다. 부모가 또다른 부모를 하나 가지는 것은 괜찮다.
    다중 상속을 지원하면 다이아몬드 문제(어떤 부모를 사용할지 애매), 클래스 계층 구조가 복잡해지는 문제가 발생한다. 따라서 자바는 다중상속을 허용하지 않는다.

3. 상속과 메모리 구조: 상속관계를 객체로 생성할 때 메모리 구조를 확인해보자

Child clild = new clild();
//Child 는 Parent의 자식 클래스
//Child에는 chlidMethod(), Parent에는 parentMethod() 멤버 메서드가 있다.

  1. new clild()를 호출하면 외부에서는 하나의 인스턴스를 생성하는 것 같지만 내부에서는 부모와 자식이 모두 생성되고 공간도 구분된다.

  2. clild.chlidMethod() 호출시, 호출하는 변수의 타입(클래스 타입) 기준으로 Parent의인지, Child 인지 대상 타입을 찾는다.

  3. clild.parentMethod() 호출시, 자식타입에 해당 기능이 없으면 부모타입으로 올라가서 찾는다. (부모한테서도 찾지 못하면 컴파일 오류)

4. 상속과 기능 추가

부모에 기능을 한번만 추가하면 자식 클래스들에 일일히 기능을 추가하지 않아도 된다.
자식은 부모 기능 외 필요한 부분을 extend(확장) 하기만 하면 된다.

5. 상속과 메서드 오버라이딩

  1. 부모에게 상속받은 기능을 자식이 재정의 하는 것을 메서드 오버라이딩(Overriding)이라고 한다. 오버라이딩시 자식의 메서드를 호출한다.
  2. @Override 애노테이션
    컴파일러는 이 애노테이션을 보고 메서드가 정확히 오버라이드 되었는지 확인하며 오버라이드 조건을 만족하지 않으면 컴파일 에러를 발생시킨다. 실수방지!
    이 기능은 필수는 아니지만 코드의 명확성을 위해 붙여주는 것이 좋다.

6. 메서드 오버라이딩 조건

  1. 메서드 이름: 메서드 이름이 같아야 한다.
  2. 메서드 매개변수(파라미터): 파라미터 타입, 순서, 개수가 같아야 한다.
  3. 반환 타입: 반환타입이 같아야 한다. 단 반환 타입이 하위 클래스 타입일 수 있다.
  4. 접근 제어자: 오버라이딩 메서드의 접근 제어자는 상위 클래스의 메서드보다 더 제한적이어서는 안된다. 예를들면 아래와 같다.
    상위: protected -> 하위: public, protected 가능, private, default 불가
  5. 예외: 오버라이딩 메서드는 상위 클래스의 메서드보다 더 많은 체크 예외를 throws로 선언할 수 없다. 하지만 더 적거나 같은 수의 예외, 또는 하위타입의 예외는 선언할 수 있다.
  6. static, final, private 키워드가 붙은 메서드 X는 오버라이딩 될 수 없다.
    static 메서드는 클래스 레벨에서 작동하므로 인스턴스 레벨에서 사용하는 오버라이딩이 의미없음. 그냥 클래스 이름을 통해 필요한 곳에 직접 접근
    final 메서드는 재정의를 금지
    private 메서드는 해당 클래스에서만 접근 가능. 하위클래스에서 보이지 않아 오버라이딩 할 수 없다.
  7. 생성자 오버라이딩 X: 생성자는 오버라이딩 할 수 없다.

7. 상속과 접근제어

protected 는 다른 패키지에서 접근 불가이나, 상속시에는 접근 가능하다.
본인 타입에 없으면 부모타입에서 기능을 찾는데, 객체 내부에서는 자식과 부모가 구분되어 있으므로 접근제어자가 영향을 준다. 부모입장에서는 외부에서 호출한 것과 같다.

package extends1.access.parent;
public class Parent {
    public int publicValue;
    protected int protectedValue;
    int defaultValue;
    private int privateValue;
    public void publicMethod() {
        System.out.println("Parent.publicMethod");
    }
    protected void protectedMethod() {
        System.out.println("Parent.protectedMethod");
    }
    void defaultMethod() {
        System.out.println("Parent.defaultMethod");
    }
    private void privateMethod() {
        System.out.println("Parent.privateMethod");
    }
    public void printParent() {
        System.out.println("==Parent 메서드 안==");
        System.out.println("publicValue = " + publicValue);
        System.out.println("protectedValue = " + protectedValue);
        System.out.println("defaultValue = " + defaultValue); //부모 메서드 안에서 접근 가능
        System.out.println("privateValue = " + privateValue); //부모 메서드 안에서 접근 가능
        //부모 메서드 안에서 모두 접근 가능
        defaultMethod();
        privateMethod();
    }
}
package extends1.access.chlid;
public class Child extends Parent {
    public void call() {
        publicValue = 1;
        protectedValue = 1; //상속 관계 or 같은 패키지
        //defaultValue = 1; //다른 패키지 접근 불가, 컴파일 오류
        //privateValue = 1; //접근 불가, 컴파일 오류

        publicMethod();
        protectedMethod(); //상속 관계 or 같은 패키지
        //defaultMethod(); //다른 패키지 접근 불가, 컴파일 오류
        //privateMethod(); //접근 불가, 컴파일 오류

        printParent();
    }
}

8. super - 부모참조

부모와 자식의 필드명이 같거나 메서드가 오버라이딩 되어있으면, 자식에서 부모의 필드나 메서드를 호출할 수 없다. 이때 super 키워드를 사용하면 부모를 참조할 수있다. super는 이름 그대로 부모 클래스에 대한 참조를 나타낸다.

public class Child extends Parent {

    public String value = "child";

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

    public void call() {
        System.out.println("this value = " + this.value); //this 생략 가능
        System.out.println("super value = " + super.value);// 부모참조

        this.hello(); //this 생략 가능
        super.hello();// 부모참조
    }
}

9. super - 생성자

상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다.(규칙)
상속관게에서 부모의 생성자를 호출할 때는 super(..) 를 사용
1. ClassA 는 최상위 부모 클래스이다.

public class ClassA {

    public ClassA() {
        System.out.println("ClassA 생성자");
    }
}
  1. ClassB 는 ClassA를 상속받았다. 상속을 받으면 생성자의 첫줄에 super()를 사용해서 부모 클래스의 생성자를 호출해야 한다.
    1) 예외로 생성자 첫줄에 this()를 사용할 수는 있으나 언젠가는 반드시 자식 생성자 안에서 super()를 호출해야 한다.
  2. 부모 클래스의 생성자가 기본 생성자(파라미터가 없는 생성자)인 경우에는 super()를 생략할 수 있다.
    1) 기본생성자를 많이 사용하기 때문에 편의를 제공.
public class ClassB extends ClassA {

    public ClassB(int a) {
        super(); //부모클래스 생성자가 기본 생성자이므로 생략 가능
        System.out.println("ClassB 생성자 a=" + a);
    }

    public ClassB(int a, int b) {
        super(); //부모클래스 생성자가 기본 생성자이므로 생략 가능
        System.out.println("ClassB 생성자 a=" + a + " b=" + b);
    }
}
  1. ClassC는 ClassB를 상속 받았다. 생성자는 하나만 호출 할 수 있다. ClassB의 두 생성자 중 하나를 선택하면 된다.
    참고로 부모인 ClassB에는 기본 생성자가 없으므로 super(...)을 생략할 수 없다.
public class ClassC extends ClassB {

    public ClassC() {
        super(10, 20);
        System.out.println("ClassC 생성자");
    }
}
  1. 실행 결과
    실행해 보면 ClassA -> ClassB -> ClassC 순서로 실행된다.
    생성자의 실행순서가 결과적으로 최상위 부모부터 실행되어서 하나씩 아래로 내려오는 것이다. 초기화는 최상위 부모부터 이루어진다. 왜냐하면 자식 생성자의 첫줄에서 부모의 생성자를 호출해야 하기 때문이다.
public class Super2Main {

    public static void main(String[] args) {
        ClassC classC = new ClassC();
    }
}

  1. 예외로 생성자 첫줄에 this()를 사용할 수는 있으나 언젠가는 반드시 자식 생성자 안에서 super()를 호출해야 한다.
public class ClassB extends ClassA {

    public ClassB(int a) {
        this(a, 0); //this를 생성자 첫줄에 써주더라도 언젠가 반드시 자식 생성자 안에서 super()를 호출 필요
        System.out.println("ClassB 생성자 a=" + a);
    }

    public ClassB(int a, int b) {
        super(); // super()를 호출 
        System.out.println("ClassB 생성자 a=" + a + " b=" + b);
    }
}

main

public class Super2Main {

    public static void main(String[] args) {
       // ClassC classC = new ClassC();
        ClassB classB = new ClassB(100);
    }
}

출력결과
ClassA -> ClassB -> ClassB(첫줄 this( a, 0 )인 생성자) 순서로 실행

10. 코드 스타일

Item 클래스에서 기본 기능을 제공하기 때문에
Book과 같은 자식클래스는 기본 기능을 super로 가져다 쓰고
개별 속성이나 기능만 신경쓰면 된다.

public class Item {
	private String name;
    private int price;
    
    public Item(String name, int price){
    	this.name = name;
        this.price = price;
    }
    
    public int getPrice(){
    	return price;
    }
    
    public void print() {
    	system.out.println("이름:" + name +", 가격:" + price)
    }
}
public class Book extends Item {
    private String author;
    private String isbn;

    public Book(String name, int price, String author, String isbn) {
        super(name, price); //생성자 첫줄 super(...)로 부모 생성자 호출
        this.author = author;
        this.isbn = isbn;
    }

    @Override
    public void print() {
        super.print(); // private 선언 부모 멤버변수는 접근불가. 부모 메서드는 public이므로 super로 메서드 호출
        System.out.println("- 저자:"+ author + ", isbn:" + isbn);
    }
}
profile
백엔드 개발공부 로그를 기록합니다

0개의 댓글