개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

기초개념 › Java

상속

extends는 무엇을 물려주는가

상속은 class Dog extends Animal처럼 클래스 이름 뒤에 extends와 다른 클래스를 적어서 쓰는 문법입니다. extends 뒤의 Animal이 부모 클래스, Dog가 자식 클래스이고, Dog는 Animal에 적힌 메서드와 필드를 물려받습니다. 그래서 Dog에 적지 않은 eat()을 Dog 객체로 부를 수 있습니다. 물려받은 메서드를 Dog에 같은 이름과 같은 매개변수로 다시 적으면 Dog 객체에서는 Dog의 것이 실행되는데, 이것이 재정의(오버라이딩)입니다. 그리고 extends를 썼기 때문에 Dog 객체를 Animal 타입 변수에 담을 수 있습니다. 이 실습은 extends를 지우거나 eat()을 다시 적고 변수의 타입을 바꿔 가며 같은 코드를 컴파일하고 실행해, 컴파일러가 무엇을 알려 주는지와 어느 eat이 실행되는지를 봅니다.

실행으로 대조 · JDK 21에서 여덟 가지 조합을 javax.tools로 컴파일하고 실행해 대조

이 실습에 나오는 개념

상속
기존 클래스(상위 클래스)의 필드와 메서드를 물려받아 새 클래스(하위 클래스)를 정의하는 것이다. 공통 코드를 재사용하고 하위 클래스에서 필요한 부분만 추가하거나 재정의한다. 자바의 클래스는 단일 상속만 가능하며, 상위 클래스 변경이 하위 클래스 전체에 영향을 주므로 "is-a" 관계가 분명할 때만 쓰는 것이 좋다.
extends
extends는 클래스 선언에서 그 클래스가 어느 클래스를 물려받는지 밝히는 키워드이다. class Dog extends Animal처럼 클래스 이름 뒤에 쓰고, 뒤에 적은 클래스(Animal)가 부모 클래스, 선언하는 클래스(Dog)가 자식 클래스가 된다. 클래스는 extends 뒤에 하나만 적을 수 있다. final이 붙은 클래스는 extends로 물려받을 수 없다. extends를 쓰면 부모 클래스가 자식 클래스의 상위 타입이 되어, 자식의 객체를 부모 타입의 변수에 담을 수 있다. extends를 쓰지 않은 클래스는 Object를 부모 클래스로 한다.
부모 클래스와 자식 클래스
부모 클래스(상위 클래스, superclass)는 다른 클래스가 extends로 물려받는 클래스이고, 자식 클래스(하위 클래스, subclass)는 extends로 부모를 밝힌 클래스이다. class Dog extends Animal에서 Animal이 Dog의 부모 클래스이다. 자식 클래스는 부모 클래스에 적힌 메서드 가운데 접근할 수 있는 것을 물려받아, 자식에 적지 않아도 자식의 객체로 부를 수 있다. 자식에서 같은 이름과 같은 매개변수의 메서드를 다시 적으면 물려받지 않고 그것으로 바꾸어 쓰는데, 이것이 재정의(오버라이딩)이다. 부모의 부모처럼 위로 이어진 모든 클래스도 자식의 부모 클래스(상위 클래스)라고 부른다.
오버라이딩
상위 클래스(또는 인터페이스)의 메서드를 하위 클래스에서 같은 시그니처로 다시 정의하는 것이다. 실행 시점에 실제 객체의 타입에 맞는 메서드가 호출되어 다형성을 구현한다. 접근 범위를 더 좁힐 수 없고, static·private·final 메서드는 오버라이딩할 수 없다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 컴파일러가 소스를 위에서부터 읽습니다. 먼저 class Animal을 읽고, Animal에 eat 메서드가 있다는 것을 알게 됩니다. 다음에 class Dog extends Animal 줄을 읽고, Dog가 Animal의 자식이며 Animal에 적힌 것을 물려받는다고 기억해 둡니다. extends 뒤에 적은 Animal이 부모 클래스입니다. Dog 안에는 bark 메서드를 직접 적었습니다.
  2. 컴파일러가 Dog d = new Dog(); 줄을 확인합니다. Dog 객체를 Dog 타입 변수에 담는 줄이므로 문제가 없습니다. 변수 d의 타입이 Dog이므로, d로 부르는 메서드는 Dog 타입에서 찾습니다.
  3. 컴파일러가 d.eat(); 줄을 확인합니다. d의 타입이 Dog이므로 Dog에서 eat 메서드를 찾는데, Dog 안에는 eat이 없습니다. Dog는 Animal을 물려받으므로 Animal에서 이어서 찾고, Animal에 적힌 eat을 찾아 통과합니다. Dog에 적지 않은 eat을 Dog 객체로 부를 수 있는 이유가 이것입니다.
  4. 컴파일이 성공해 실행할 Main 클래스 파일이 만들어집니다. 여기까지는 컴파일러가 줄마다 쓸 수 있는지 확인한 것이고, 아직 eat은 한 번도 실행되지 않았습니다.
  5. java Main으로 실행합니다. main이 시작되고, new Dog()가 Dog 객체를 만들어 변수 d에 담습니다.
  6. d.eat(); 줄이 실행됩니다. d가 가리키는 객체는 Dog 객체이므로, 실행할 eat을 그 객체의 클래스인 Dog에서 먼저 찾습니다. Dog에는 eat이 없으므로 Animal에서 찾아, Animal에 적힌 eat이 실행되어 "먹는다"를 출력합니다.
  7. d.bark(); 줄이 실행됩니다. Dog 객체의 bark는 Dog에 직접 적은 메서드이므로 그대로 실행되어 "멍멍"을 출력합니다.

실행 결과

코드

class Animal {
    void eat() {
        System.out.println("먹는다");
    }
}

// 상속: Dog는 Animal에 적힌 것을 물려받는다
class Dog extends Animal {
    void bark() {
        System.out.println("멍멍");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.eat();
        d.bark();
    }
}

이 모형의 전제

컴파일러와 실행 환경은 JDK 21.0.12.1(javac 21)입니다. 여덟 가지 조합을 모두, 화면의 코드와 같은 소스(줄 단위로 같은 글)를 javax.tools로 컴파일하고, 컴파일에 성공한 것은 클래스 파일을 새 클래스 로더로 읽어 Main.main을 실행해 표준 출력을 잡은 결과와 대조했습니다. 대조한 것은 컴파일 성공 여부, 첫 번째 오류의 진단 코드(compiler.err.cant.resolve.location.args와 compiler.err.prob.found.req를 화면의 두 가지 오류 설명으로 옮겼습니다), 오류의 개수와 오류가 난 줄, Main 클래스 파일이 만들어졌는지, 실행했을 때의 출력입니다. "실행된 eat"은 짐작이 아니라 출력으로 가렸습니다. Animal의 eat은 "먹는다"를, Dog의 eat은 "개가 먹는다"를 출력하게 써 두고, 실제 출력이 어느 쪽인지 읽었습니다. 단계 문장이 기대는 사실도 따로 실행해 확인했습니다. Animal 타입 변수로 Dog에만 적은 bark()를 부르면 컴파일 오류가 나는 것(그래서 Animal 변수 코드에는 d.bark() 줄이 없습니다), Dog에 eat을 다시 적어도 Animal 객체로 부르는 eat은 여전히 "먹는다"를 출력하는 것, extends가 없을 때도 Dog에 직접 적은 bark는 문제없이 쓸 수 있는 것입니다. 문서는 Java Language Specification(Java SE 21)을 직접 열어 확인했습니다. extends 절이 직접 부모 클래스를 정하고 자식 클래스(subclass)라 부른다는 것은 8.1.4절, 클래스가 부모 클래스의 접근할 수 있는 메서드 가운데 자기가 같은 시그니처로 다시 적지 않은 것을 물려받는다는 것은 8.4.8절, 자식 클래스가 부모의 메서드를 같은 시그니처로 다시 적은 것이 재정의(overrides)라는 것은 8.4.8.1절, 부모 클래스가 자식 클래스의 상위 타입이라는 것은 4.10.2절(그래서 자식 객체를 부모 타입 변수에 담을 수 있습니다), 실행할 때는 변수의 타입이 아니라 대상 객체의 실제 클래스에서 시작해 위로 올라가며 재정의한 메서드를 찾는다는 것은 15.12.4.4절을 따랐습니다. 명세 5.1.5절과 5.2절(대입 문맥에서 자식 타입의 값을 부모 타입 변수에 담는 변환)은 이 실습에서 열어 확인하지 못했습니다. 오류의 종류, 개수, 어느 줄에서 나는지는 명세가 정한 것이 아니라 이 javac에서 관찰한 것입니다. extends 없이 Animal 변수에 담을 때는 Dog에 eat을 다시 적었는지와 상관없이 같은 줄에서 같은 오류가 하나 났습니다. 화면의 코드는 클래스가 한 파일에 같이 있는 모양이고 접근 제어자는 줄였으며, @Override는 쓰지 않았습니다. 모든 클래스는 extends를 쓰지 않아도 Object를 물려받지만 이 실습은 다루지 않았고, "extends가 없으면 Dog에는 자기가 적은 것만 있다"는 Animal에 적힌 것에 대해서 한 말입니다. 클래스가 부모의 접근할 수 있는 필드도 물려받는다는 것은 8.3절에서 확인했지만 이 실습은 메서드만 실행으로 보였습니다. 부모의 필드 쓰기, 생성자, super 키워드, private 멤버가 물려지지 않는 것, 자식이 부모 생성자를 부르는 순서도 이 실습은 다루지 않았습니다. 부모 생성자가 실행되는 순서와 super는 "세부 동작 실습: 상속과 오버라이딩"에서 다룹니다.

앞 실습: static · 다음 실습: 인터페이스

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요