[아이티센 부트캠프] (상속)

이언덕·2026년 3월 24일

아이티센 부트캠프

목록 보기
14/115
post-thumbnail

상속

상속의 기본 개념

개념

상속은 기존 클래스를 바탕으로 새로운 클래스를 만드는 방법이다.
처음부터 전부 새로 만드는 것이 아니라, 이미 있는 클래스의 기능을 이어받고 필요한 부분만 추가해서 더 구체적인 클래스를 만든다.

자바에서는 extends를 사용한다.

class 자식클래스 extends 부모클래스 {
    // 추가 내용
}

이렇게 상속하면 자식 클래스는 부모 클래스의 멤버변수와 메서드를 사용할 수 있다.
다만 부모의 멤버를 필요한 것만 골라 받는 방식은 아니다. 부모에 있는 멤버를 바탕으로 자식 클래스가 확장되는 구조라고 이해하면 된다.

알아야할 것은 생성자와 초기화 블록은 예외로 상속되지 않는다.
대신 자식 객체를 만들 때 부모 쪽이 먼저 준비된다.



핵심 특징

  • 상속은 기존 클래스를 재사용해서 새 클래스를 만드는 방법이다.
  • 자식은 부모의 멤버변수, 메서드를 물려받는다.
  • 자식은 부모를 바탕으로 기능을 확장한 클래스라고 보면 된다.
  • 공통 기능은 부모에 두고, 자식은 자기에게 필요한 부분만 추가하는 식으로 쓴다.
  • 생성자, 초기화 블록은 상속되지 않는다. 대신 자식 객체를 만들 때 부모 쪽이 먼저 준비된다.

예제: InheritanceBasic

class Car {
    int wheel = 4;   // 부모가 가진 멤버변수

    void drive() {   // 부모가 가진 메서드
        System.out.println("자동차가 달린다.");
    }
}

class SportsCar extends Car { // SportsCar는 Car를 상속받는 자식 클래스
    int turbo = 1;            // 자식이 추가한 멤버변수
}

public class InheritanceBasic {
    public static void main(String[] args) {
        SportsCar car = new SportsCar(); // 자식 객체 생성
                                         // → SportsCar 자신의 멤버 + Car에게서 물려받은 멤버 사용 가능

        System.out.println(car.wheel);   // 부모 Car에게서 물려받은 wheel 사용
        car.drive();                     // 부모 Car에게서 물려받은 drive() 호출
        System.out.println(car.turbo);   // 자식 SportsCar가 직접 가진 turbo 사용
    }

    // 출력결과
    // 4
    // 자동차가 달린다.
    // 1
}

이 코드는 상속의 가장 기본 형태를 보여준다.

  • SportsCar 안에는 wheel과 drive()를 직접 만들지 않았다.
  • 그런데도 사용할 수 있다.
  • 이유는 SportsCar가 Car를 상속받았기 때문이다.

즉 자식 객체는 부모의 기능을 자기 것처럼 사용할 수 있다.

헷갈리기 쉬운 부분

부모의 기능을 쓸 수 있다고 해서 코드를 복사해 온 것으로 생각하면 안 된다.
상속은 복붙이 아니라 클래스 사이에 부모-자식 관계를 만든 것이다.

또 생성자도 상속되는 것 아닌가 하고 많이 헷갈리는데, 생성자는 상속되지 않는다.
부모 생성자가 먼저 호출될 뿐이다.




상속관계 이해하기

개념

상속은 클래스를 위아래로 연결해서 보는 구조다.
이 구조를 그림으로 보면 상속계층도가 된다.

예를 들어 아래처럼 되어 있으면,

class Parent { }
class Child extends Parent { }
class Child2 extends Parent { }
class GrandChild extends Child { }
  • Parent는 조상 클래스
  • Child, Child2는 자식 클래스
  • GrandChild는 Child의 자식이면서 Parent의 자손 클래스

가 된다.


여기서 중요한 점은 상속관계에는 조상과 자손 관계만 있다는 것이다.
Child와 Child2는 같은 부모를 가졌어도 서로 직접 상속관계는 아니다.

또 GrandChild는 Child를 통해 기능을 물려받기 때문에 Child 쪽 변화와 관련이 있다.
하지만 Child2는 같은 부모를 둔 형제 클래스일 뿐이므로, Child2가 바뀐다고 GrandChild가 직접 영향을 받지는 않는다.



핵심 특징

  • 공통 부분은 조상에서 관리하고, 개별 부분은 자손에서 관리하는 것이 자연스럽다.
  • 조상이 바뀌면 자손도 영향을 받는다.
  • 자손이 바뀐다고 조상이 영향을 받지는 않는다.
  • 같은 부모를 둔 형제 클래스끼리는 서로 직접 상속관계가 아니다.
  • GrandChild는 Child의 변화에는 영향을 받을 수 있지만, Child2의 변화에는 직접 영향을 받지 않는다.
  • 상속관계가 넓어질수록 변경 영향도 같이 넓어진다.

예제: InheritanceCommonTest

class Parent {
    void login() { // 공통 기능은 부모에 둠
        System.out.println("로그인 기능");
    }
}

class Child extends Parent {
    void childMethod() { // Child만의 기능
        System.out.println("Child의 기능");
    }
}

class Child2 extends Parent {
    void child2Method() { // Child2만의 기능
        System.out.println("Child2의 기능");
    }
}

class GrandChild extends Child { // GrandChild는 Child를 상속
                                 // → Parent의 기능 + Child의 기능 사용 가능
}

public class InheritanceCommonTest {
    public static void main(String[] args) {
        Child c1 = new Child();
        Child2 c2 = new Child2();
        GrandChild gc = new GrandChild();

        c1.login();        // Parent의 login() 사용
        c2.login();        // Parent의 login() 사용
        gc.login();        // Parent → Child → GrandChild 흐름으로 login() 사용 가능
        gc.childMethod();  // Child의 기능을 GrandChild도 사용 가능

        // gc.child2Method(); // 에러
        // GrandChild는 Child 계층에 속하므로 Child2의 기능은 직접 사용할 수 없음
    }

    // 출력결과
    // 로그인 기능
    // 로그인 기능
    // 로그인 기능
    // Child의 기능
}

이 코드는 상속관계에서 누구의 기능을 물려받는지를 보여준다.

  • login()은 Parent에 있으므로 Child, Child2, GrandChild 모두 사용할 수 있다.
  • childMethod()는 Child에 있으므로 GrandChild도 사용할 수 있다.
  • 반대로 child2Method()는 Child2에만 있으므로 GrandChild는 사용할 수 없다.

즉, GrandChild는 Child 쪽 계층에 속해 있고, Child2와는 직접적인 상속관계가 아니다.



헷갈리기 쉬운 부분

Child와 Child2가 같은 Parent를 상속받는다고 해서 둘이 서로 특별한 관계가 생기는 것은 아니다.


또 GrandChild는 이름만 보면 Parent의 손자 클래스이기도 하지만,
실제로 바로 물려받는 대상은 Child다.
그래서 GrandChild는 Child의 변화에는 영향을 받을 수 있어도,
형제 계층인 Child2의 변화에는 직접 영향을 받지 않는다.


부모에 멤버를 하나 추가하면 그 부모를 상속받는 자식들까지 같이 영향을 받는다.
그래서 상속은 편리하지만, 아무 클래스끼리 연결하면 안 된다.




포함 관계

개념

클래스 사이의 관계는 상속만 있는 것이 아니다.
포함은 한 클래스가 다른 클래스를 멤버로 가지고 있는 관계다.

쉽게 말하면 부품처럼 가지고 있는 구조다.


예를 들어 자동차를 생각하면,

  • 자동차는 엔진이다 → 어색함
  • 자동차는 엔진을 가지고 있다 → 자연스러움

이럴 때는 상속이 아니라 포함이다.

즉, 포함은 “다른 클래스를 내 안에 넣어서 함께 사용하는 방식”이라고 보면 된다.



핵심 특징

  • 한 클래스가 다른 클래스를 멤버변수로 가진다.
  • 큰 클래스를 작은 클래스들의 조합으로 만든다.
  • 필요한 부품을 나누어 관리할 수 있다.
  • 코드를 역할별로 나누기 쉬워서 유지보수에 유리하다.

예제: CompositeTest

class Engine {
    void start() {
        System.out.println("엔진을 켠다.");
    }
}

class Car {
    Engine engine = new Engine(); // Car가 Engine 객체를 멤버로 포함

    void drive() {
        engine.start();            // Car가 직접 엔진 역할을 하는 것이 아니라
                                   // 자신이 가지고 있는 Engine 객체의 메서드를 호출
        System.out.println("자동차가 달린다.");
    }
}

public class CompositeTest {
    public static void main(String[] args) {
        Car car = new Car();       // Car 객체 생성
                                   // → 내부에 포함된 Engine 객체도 함께 준비됨

        car.drive();               // drive() 호출
                                   // → engine.start() 실행
                                   // → 그 다음 "자동차가 달린다." 출력
    }

    // 출력결과
    // 엔진을 켠다.
    // 자동차가 달린다.
}

이 코드는 포함의 가장 기본 형태를 보여준다.

  • Car 안에 Engine engine이 있다.
  • 즉, Car는 Engine을 상속받은 것이 아니라 멤버로 가지고 있는 것이다.
  • drive()가 실행되면, 자동차가 직접 엔진 역할을 하는 것이 아니라
    자신이 가지고 있는 engine 객체의 start()를 호출한다.

여기서 봐야 할 가장 중요한 부분은 이 줄이다.

Engine engine = new Engine();

이렇게 다른 클래스 타입의 변수를 멤버로 선언해서 가지고 있으면 포함 관계다.



헷갈리기 쉬운 부분

포함과 상속이 헷갈리면 문장으로 바꿔 보면 된다.

  • Circle is a Shape → 자연스러움 → 상속
  • Car has an Engine → 자연스러움 → 포함

즉, is-a면 상속, has-a면 포함으로 보면 된다.


또 포함은
“내가 그 자체가 되는 것”이 아니라
“내가 그것을 가지고 사용하는 것”이라는 점이 중요하다.

그래서 Car는 Engine이 아니고, Engine을 가지고 있는 클래스다.




단일 상속

개념

자바는 단일 상속만 허용한다.
즉 한 클래스는 부모 클래스를 하나만 가질 수 있다.



핵심 특징

  • 여러 부모를 동시에 상속받는 다중 상속은 허용하지 않는다.
  • 이유는 구조가 복잡해지고, 같은 이름의 멤버가 충돌할 수 있기 때문이다.
  • 여러 기능이 필요하더라도, 부모-자식 관계가 자연스러운 클래스 하나만 상속하고 나머지는 포함 관계로 푸는 경우가 많다.

예제: SingleInheritanceTest

class Tv {
    void powerOn() {
        System.out.println("TV 전원 켜기");
    }
}

class VCR {
    void play() {
        System.out.println("비디오 재생");
    }
}

// class TVCR extends Tv, VCR { }  // 에러
// 자바는 부모 클래스를 두 개 동시에 상속할 수 없음

class TVCR extends Tv {           // TVCR은 Tv를 상속
    VCR vcr = new VCR();          // VCR 기능은 포함 관계로 해결

    void playVideo() {
        vcr.play();               // 포함한 VCR 객체의 기능 호출
    }
}

public class SingleInheritanceTest {
    public static void main(String[] args) {
        TVCR tvcr = new TVCR();   // TVCR 객체 생성
                                  // → Tv 기능은 상속으로 사용
                                  // → VCR 기능은 내부 객체로 사용

        tvcr.powerOn();           // Tv에게서 물려받은 기능
        tvcr.playVideo();         // 포함한 VCR 기능 호출
    }

    // 출력결과
    // TV 전원 켜기
    // 비디오 재생
}

이 코드는 자바에서 두 클래스를 동시에 상속할 수 없기 때문에,
하나는 상속으로 받고 나머지는 포함으로 처리하는 방식을 보여준다.



헷갈리기 쉬운 부분

상속을 많이 하면 더 강력해 보인다고 생각하기 쉽지만 그렇지 않다.
상속은 많을수록 좋은 것이 아니라, 정확하게 필요한 곳에만 써야 한다.




Object 클래스

개념

Object는 모든 클래스의 최고 조상이다.
자바에서 클래스를 만들 때 다른 클래스를 상속하지 않아도, 실제로는 자동으로 Object를 상속받는다.

그래서 자바의 모든 클래스는 결국 Object와 연결된다.
이 말은 곧, 어떤 클래스를 만들든 Object가 가진 기본 메서드를 함께 물려받는다는 뜻이다.

대표적으로 toString(), equals(), hashCode() 같은 메서드가 있다.
즉, 우리가 만든 클래스도 특별히 적지 않아도 이런 메서드를 기본적으로 사용할 수 있다.



핵심 특징

  • 모든 클래스의 맨 위에는 결국 Object가 있다.
  • 코드에 extends Object를 직접 쓰지 않아도 자동으로 이어진다.
  • 그래서 어느 클래스든 Object의 메서드를 사용할 수 있다.
  • Object는 모든 객체가 공통으로 가져야 할 기본 기능을 제공한다.



예제: ObjectTest

class Tv { } // extends Object를 쓰지 않았지만 실제로는 Object와 연결됨

public class ObjectTest {
    public static void main(String[] args) {
        Tv tv = new Tv();              // 우리가 만든 객체 생성

        System.out.println(tv.toString()); 
        // Tv 안에 toString()을 직접 만들지 않았는데도 호출 가능
        // 이유: Tv가 Object를 자동으로 상속받기 때문
    }

    // 출력결과
    // Tv@해시값
}

이 코드는 Tv 클래스에 아무 메서드도 만들지 않았는데도toString()을 호출할 수 있다는 점을 보여준다.

  • Tv 클래스 안에는 toString()이 없다.
  • 그런데도 tv.toString()이 가능하다.
  • 그 이유는 Tv가 자동으로 Object를 상속받기 때문이다.
  • 즉, toString()은 Object로부터 물려받은 메서드다.

출력 결과에 보이는 Tv@해시값 형태는 기본 toString()이 만들어 주는 문자열이다.
그래서 직접 오버라이딩하지 않으면 보통 이런 식으로 출력된다.



헷갈리기 쉬운 부분

코드에 extends Object가 안 보여도 Object와 연결되지 않은 클래스는 없다고 보면 된다.


또 toString(), equals(), hashCode()는 내가 직접 만든 적이 없어도 사용할 수 있다.
이유는 이 메서드들이 원래 Object에 들어 있기 때문이다.

instanceof Object가 true로 나오는 것도 같은 이유다.
모든 클래스는 결국 Object의 자손이기 때문이다.




오버라이딩

개념

오버라이딩은 부모에게서 상속받은 메서드를 자식 클래스에 맞게 다시 정의하는 것이다.
부모 메서드를 그대로 쓰면 안 맞는 경우에, 이름은 그대로 두고 내용만 바꾸는 것이다.

왜 필요한지 생각해보면 쉽다.
부모에서 만든 기능이 자식에게도 무조건 딱 맞는 것은 아니다.
자식의 역할이 더 구체적이면, 같은 이름의 메서드라도 자식에 맞게 동작이 달라져야 한다.



핵심 특징

  • 부모에게서 받은 메서드를 자식 쪽에서 다시 정의한다.
  • 메서드의 선언부는 같아야 한다.
    • 이름이 같아야 한다.
    • 매개변수가 같아야 한다.
    • 리턴타입이 같아야 한다.
  • 접근 제어자를 더 좁게 바꾸면 안 된다.
  • 부모보다 더 많은 예외를 선언할 수 없다.
  • static 메서드와 인스턴스 메서드를 서로 바꿔서 오버라이딩할 수 없다.
  • 매개변수가 달라지면 오버라이딩이 아니라 오버로딩이다.
  • @Override를 붙이면 오버라이딩 여부를 확인하기 쉽다.

예제: OverrideTest

class Point {
    int x;
    int y;

    String getLocation() { // 부모가 만든 위치 문자열 메서드
        return "x : " + x + ", y : " + y;
    }
}

class Point3D extends Point {
    int z;

    @Override
    String getLocation() { // 부모 메서드를 자식에 맞게 다시 정의
        return "x : " + x + ", y : " + y + ", z : " + z;
    }
}

public class OverrideTest {
    public static void main(String[] args) {
        Point3D p = new Point3D();
        p.x = 10;   // 부모에게서 물려받은 x
        p.y = 20;   // 부모에게서 물려받은 y
        p.z = 30;   // 자식이 추가한 z

        System.out.println(p.getLocation()); 
        // Point3D 객체에서 getLocation()을 호출하면
        // 부모 버전이 아니라 자식이 다시 정의한 메서드가 실행됨
    }

    // 출력결과
    // x : 10, y : 20, z : 30
}

이 코드는 부모의 getLocation()을 자식에 맞게 다시 만든 예제다.
Point는 2차원 좌표고, Point3D는 3차원 좌표이기 때문에 그대로 쓰면 부족하다.
그래서 자식 쪽에서 z까지 포함하도록 다시 정의한 것이다.



헷갈리기 쉬운 부분

오버라이딩과 오버로딩은 다르다.

  • 오버라이딩 → 상속받은 메서드를 다시 정의
  • 오버로딩 → 같은 이름의 메서드를 새로 추가

예를 들어 매개변수가 달라지면 오버라이딩이 아니라 오버로딩이다.
즉 선언부가 달라지는 순간, 부모 메서드를 바꾼 것이 아니라 새 메서드를 만든 것이다.




super

개념

super는 부모의 멤버를 가리킬 때 쓰는 참조 표현이다.
this와 비슷해 보이지만 역할은 다르다.

자식 객체가 만들어지면, 그 안에는 자식이 만든 부분만 있는 것이 아니라 부모로부터 물려받은 부분도 함께 들어 있다.
이때 this는 현재 객체 전체를 가리키고, super는 그 안에서 부모로부터 물려받은 부분을 구별해서 가리킬 때 사용한다.


그래서 부모와 자식에 같은 이름의 변수나 메서드가 있을 때 super가 특히 중요해진다.



핵심 특징

  • this는 현재 객체 자신을 가리킨다.
  • super는 현재 객체 안에 있는 부모 쪽 멤버를 가리킬 때 사용한다.
  • 부모와 자식에 같은 이름의 멤버가 있을 때 구별할 수 있다.
  • 오버라이딩한 메서드 안에서 부모 메서드를 다시 호출할 때도 쓴다.

예제: SuperTest

class Parent {
    int x = 10; // 부모의 x
}

class Child extends Parent {
    int x = 20; // 자식의 x

    void printValue() {
        System.out.println(x);       // 가까운 곳의 x → Child의 x
        System.out.println(this.x);  // 현재 객체의 자식 쪽 x
        System.out.println(super.x); // 부모 Parent의 x
    }
}

public class SuperTest {
    public static void main(String[] args) {
        Child c = new Child(); // Child 객체 생성
        c.printValue();        // 같은 이름의 x가 두 개 있을 때 누구를 가리키는지 확인
    }

    // 출력결과
    // 20
    // 20
    // 10
}

이 코드는 부모와 자식에 같은 이름의 x가 있을 때 어느 쪽 값을 읽는지 보여준다.

  • 그냥 x라고 쓰면 현재 클래스 기준으로 가까운 Child의 x를 본다.
  • this.x도 현재 객체의 자식 쪽 x를 가리킨다.
  • super.x는 부모인 Parent에 있는 x를 가리킨다.

즉, super는 “현재 객체 안에서 부모 쪽 멤버를 꺼내 보겠다”는 뜻으로 이해하면 된다.

예제: SuperMethodTest

class Point {
    int x;
    int y;

    String getLocation() {
        return "x : " + x + ", y : " + y;
    }
}

class Point3D extends Point {
    int z;

    @Override
    String getLocation() {
        return super.getLocation() + ", z : " + z;
        // 부모가 만든 x, y 부분은 그대로 재사용
        // 자식은 자기에게 필요한 z만 추가
    }
}

public class SuperMethodTest {
    public static void main(String[] args) {
        Point3D p = new Point3D();
        p.x = 1;
        p.y = 2;
        p.z = 3;

        System.out.println(p.getLocation());
        // Point3D의 getLocation() 호출
        // → 내부에서 super.getLocation() 실행
        // → 부모가 만든 문자열 뒤에 z 정보 추가
    }

    // 출력결과
    // x : 1, y : 2, z : 3
}

이 코드는 super가 메서드에서도 왜 필요한지 보여준다.

  • Point는 이미 x, y를 문자열로 만드는 getLocation()을 가지고 있다.
  • Point3D는 여기에 z만 더해서 확장하고 싶다.
  • 그래서 부모가 만든 부분은 super.getLocation()으로 그대로 재사용하고, 자식에 필요한 z만 뒤에 붙인다.

이렇게 하면 부모가 만든 기능을 다시 처음부터 쓰지 않아도 된다.



헷갈리기 쉬운 부분

super는 부모 객체를 따로 하나 더 만드는 것이 아니다.
지금 사용 중인 같은 객체 안에서 부모로부터 물려받은 부분을 가리키는 것이다.


또 부모와 자식에 이름이 겹치지 않으면 굳이 super를 쓸 필요는 없다.
상속받은 멤버는 원래 그냥 사용할 수 있기 때문이다.


즉, super는 보통 아래 두 경우에 가장 많이 쓴다.

  • 부모와 자식의 멤버 이름이 같을 때
  • 오버라이딩한 메서드 안에서 부모 메서드를 다시 쓰고 싶을 때



super()와 생성자 호출

개념

super()는 부모 생성자를 호출하는 문장이다.
자식 객체를 만들면 자식 부분만 생기는 것이 아니라, 그 안에 포함된 부모 부분도 먼저 만들어져야 한다.


그래서 자식 생성자가 실행될 때는 항상 먼저 부모 생성자가 호출되어 부모 쪽 초기화가 끝나야 한다.

쉽게 말하면 자식 객체는 한 번에 뚝 생기는 게 아니라,
1. 부모 부분 먼저 준비
2. 그다음 자식 부분 준비

이 순서로 만들어진다.



핵심 특징

  • 자식 생성자의 첫 줄에서는 this() 또는 super()가 와야 한다.
  • 직접 쓰지 않으면 컴파일러가 super()를 자동으로 넣는다.
  • 부모에 기본 생성자 Parent()가 없으면 자동 호출이 실패해서 에러가 날 수 있다.
  • 이런 경우 super(값)처럼 맞는 부모 생성자를 직접 호출해야 한다.

예제: SuperConstructorOrderTest

class Parent {
    Parent() {
        System.out.println("Parent 생성자 호출");
    }
}

class Child extends Parent {
    Child() {
        // super(); 가 첫 줄에 자동으로 들어감
        System.out.println("Child 생성자 호출");
    }
}

public class SuperConstructorOrderTest {
    public static void main(String[] args) {
        Child c = new Child();
        // new Child()
        // → Child 생성자 시작
        // → 먼저 Parent 생성자 호출
        // → 부모 쪽 준비 완료
        // → 그 다음 Child 생성자 실행
    }

    // 출력결과
    // Parent 생성자 호출
    // Child 생성자 호출
}

이 코드는 생성자 호출 순서를 가장 직접적으로 보여준다.

  • new Child()를 했지만 Child()만 바로 실행되지 않는다.
  • 먼저 부모 생성자인 Parent()가 호출된다.
  • 부모 쪽 준비가 끝난 다음에 자식 생성자인 Child()가 실행된다.

즉, 자식 객체를 만들 때는 항상 부모 생성자가 먼저 실행된다는 점이 핵심이다.

예제: SuperConstructorTest

class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x; // 부모가 자기 멤버 x 초기화
        this.y = y; // 부모가 자기 멤버 y 초기화
    }
}

class Point3D extends Point {
    int z;

    Point3D(int x, int y, int z) {
        super(x, y); // 부모 생성자 호출
                     // → 부모 쪽 x, y 먼저 저장
        this.z = z;  // 그 다음 자식이 자기 멤버 z 저장
    }
}

public class SuperConstructorTest {
    public static void main(String[] args) {
        Point3D p = new Point3D(10, 20, 30);
        // new Point3D(10, 20, 30)
        // → Point3D 생성자 호출
        // → super(10, 20) 실행
        // → 부모의 x, y 초기화
        // → 자식의 z 초기화
        // → 객체 생성 완료

        System.out.println(p.x); // 부모에게서 물려받은 x
        System.out.println(p.y); // 부모에게서 물려받은 y
        System.out.println(p.z); // 자식이 추가한 z
    }

    // 출력결과
    // 10
    // 20
    // 30
}

이 코드에서 핵심은 super(x, y);이다.

  • 부모 클래스 Point가 가진 x, y는 부모 생성자가 초기화한다.
  • 자식 클래스 Point3D가 추가한 z는 자식 생성자가 초기화한다.

즉, 부모 멤버는 부모 생성자가 맡고, 자식 멤버는 자식 생성자가 맡는 구조다.


그래서 super(x, y)는 부모 부분을 먼저 제대로 준비하는 문장이라고 이해하면 된다.



헷갈리기 쉬운 부분

super와 super()는 완전히 다르다.

  • super → 부모의 변수나 메서드를 가리킬 때 사용
  • super() → 부모 생성자를 호출할 때 사용

또 자식 생성자에서 super()를 안 써도 된다고 생각하기 쉬운데, 실제로는 안 쓰면 컴파일러가 자동으로 넣는다.

그래서 부모에 기본 생성자가 없으면 아래처럼 에러가 난다.

class Parent {
    Parent(int x) { }
}

class Child extends Parent {
    Child() {
        // super(); 가 자동으로 들어가는데 Parent()가 없어서 에러
    }
}

이럴 때는 자동 호출에 맡기면 안 되고, 직접 맞는 부모 생성자를 불러야 한다.

class Parent {
    Parent(int x) { }
}

class Child extends Parent {
    Child() {
        super(10);
    }
}

여기서 중요한 건 자식 생성자가 부모 생성을 건너뛸 수 없다는 점이다.
부모 부분이 먼저 준비되지 않으면 자식 객체도 완성될 수 없기 때문이다.




헷갈리기 쉬운 부분 한 번에 정리

1. 상속과 포함

  • 상속은 ~이다
  • 포함은 ~을 가지고 있다

자연스럽게 말이 되면 그 관계가 맞는 경우가 많다.



2. 생성자는 상속되지 않는다

부모 생성자가 자식 안으로 복사되는 것이 아니다.
자식 객체를 만들 때 부모 생성자가 먼저 호출되는 것이다.



3. 오버라이딩과 오버로딩은 다르다

  • 선언부가 같고 내용만 바꾸면 오버라이딩
  • 이름은 같지만 매개변수가 다르면 오버로딩



4. super와 super()는 다르다

  • super.x, super.method() → 부모 멤버 접근
  • super() → 부모 생성자 호출



5. 상속은 무조건 많이 쓰는 것이 아니다

상속은 편리하지만, 조상의 변경이 자손 전체에 영향을 줄 수 있다.
그래서 정말 부모-자식 관계로 보는 게 맞을 때만 쓰는 것이 중요하다.
그 외에는 포함으로 푸는 편이 더 안전한 경우가 많다.




한 번에 정리

상속은 기존 클래스를 바탕으로 새 클래스를 만드는 방법이다.
공통 기능은 부모에 두고, 자식은 필요한 기능만 더해서 확장한다.

클래스 사이의 관계를 잡을 때는
먼저 상속으로 볼지 포함으로 볼지 판단해야 한다.
자바는 단일 상속만 허용하므로, 하나만 상속하고 나머지는 포함으로 푸는 경우가 많다.

모든 클래스는 결국 Object와 연결되어 있고,
부모 메서드를 자식에 맞게 바꾸는 것이 오버라이딩이다.
부모 멤버를 구별할 때는 super, 부모 생성자를 호출할 때는 super()를 사용한다.


핵심은 이것이다.
공통 부분은 조상에서 관리하고, 자식은 자기에게 필요한 부분만 추가한다.
이 흐름만 잡히면 상속 전체가 훨씬 쉽게 보인다.

0개의 댓글