이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › Java › 클래스와 객체
상속과 오버라이딩
자식 객체를 만들 때 부모 생성자는 언제 실행되나
자식 클래스의 객체 하나에는 부모에게서 물려받은 필드와 자식이 선언한 필드가 함께 들어 있습니다. new를 하면 부모 생성자가 먼저 끝나고, 그다음에야 자식의 필드 초기화식과 생성자 본문이 실행됩니다. 이 순서 때문에 super(...)를 꼭 써야 하는 경우가 생기고, 부모 생성자가 오버라이딩된 메서드를 부르면 아직 비어 있는 자식 필드가 보입니다.
실행으로 대조 · JDK 21에서 서른여섯 가지 조합을 컴파일하고 실행해 컴파일 결과와 출력을 대조
이 실습에 나오는 개념
- 상속
- 기존 클래스(상위 클래스)의 필드와 메서드를 물려받아 새 클래스(하위 클래스)를 정의하는 것이다. 공통 코드를 재사용하고 하위 클래스에서 필요한 부분만 추가하거나 재정의한다. 자바의 클래스는 단일 상속만 가능하며, 상위 클래스 변경이 하위 클래스 전체에 영향을 주므로 "is-a" 관계가 분명할 때만 쓰는 것이 좋다.
- 오버라이딩
- 상위 클래스(또는 인터페이스)의 메서드를 하위 클래스에서 같은 시그니처로 다시 정의하는 것이다. 실행 시점에 실제 객체의 타입에 맞는 메서드가 호출되어 다형성을 구현한다. 접근 범위를 더 좁힐 수 없고, static·private·final 메서드는 오버라이딩할 수 없다.
- 생성자
- 객체를 new로 만들 때 한 번 실행되어 필드를 처음 상태로 맞추는 코드다. 이름이 클래스 이름과 같고 반환 타입을 적지 않는다. 받는 인자가 다르면 한 클래스에 여러 개를 둘 수 있으며, new에 넘긴 인자에 맞는 것이 선택된다. 생성자 본문은 필드가 기본값으로 채워지고 필드 초기화식과 초기화 블록이 실행된 뒤에 실행된다.
- super
- 자식 클래스 안에서 부모 클래스의 것을 가리키는 키워드다. 생성자의 첫 문장으로 super(인자)를 쓰면 그 인자에 맞는 부모 생성자를 부르고, 쓰지 않으면 컴파일러가 인자 없는 super()를 자동으로 넣는다. 그래서 부모에 인자 없는 생성자가 없으면 자식 생성자에서 super(인자)를 직접 써야 하며, 쓰지 않으면 컴파일 오류가 난다. 메서드 안에서 super.메서드()라고 쓰면 오버라이딩으로 가려진 부모의 메서드를 부른다.
- 필드의 기본값
- 객체가 만들어질 때 값을 따로 주지 않은 필드에 자동으로 들어가는 값이다. int 같은 숫자 타입은 0, boolean은 false, String을 비롯한 참조 타입은 null이다. 필드 초기화식이나 생성자가 값을 넣기 전까지는 이 값이 보인다. 메서드 안의 지역 변수에는 기본값이 없어서, 값을 넣지 않고 읽으면 컴파일 오류가 난다.
이렇게 바꿔 보세요
- 처음 설정으로 — 부모 생성자가 describe()를 부릅니다. sound에 "멍멍"을 넣었는데 sound=null이 찍힙니다.
- 실행 순서만 보기 — 부모 생성자, 자식의 필드 초기화식, 자식 생성자 본문 순서입니다.
- super를 쓰지 않으면 — 컴파일러가 super()를 넣어 부모의 Animal()을 부릅니다.
- 부모에 Animal()이 없는데 super를 쓰지 않으면 — 자동으로 들어간 super()가 부를 생성자가 없어 컴파일 오류입니다.
- super.describe()로 부모 것도 부르기 — 오버라이딩한 메서드 안에서 부모의 원래 동작을 먼저 실행합니다.
- 부모 생성자가 둘일 때 — super에 넘긴 인자에 맞는 부모 생성자가 선택됩니다.
처음 설정으로 실행하면
- 컴파일: Dog() 생성자의 첫 줄을 봅니다. super("바둑이")는 문자열 하나를 받는 부모 생성자 Animal(String name)을 부르겠다는 뜻입니다. Animal에 이 생성자가 있으므로 컴파일됩니다.
- new Dog() 실행: 힙에 Dog 객체 하나의 자리를 잡습니다. 이 객체 안에는 Animal에게서 물려받은 name과 Dog가 선언한 sound가 함께 들어 있고, 둘 다 기본값 null로 채워집니다.
- Dog() 생성자의 첫 줄 super("바둑이"): Dog의 필드 초기화식과 생성자 본문을 실행하기 전에 부모 생성자인 Animal(String name) 쪽으로 먼저 넘어갑니다. 물려받은 부분이 먼저 준비되어야 자식이 그것을 쓸 수 있기 때문입니다.
- Animal(String name) 본문의 this.name = name: 넘겨받은 "바둑이"가 name에 들어갑니다. sound는 아직 null입니다.
- System.out.println("Animal(String) 실행"): 부모 생성자의 본문이 실행 중입니다. Dog() 생성자의 본문은 아직 한 줄도 실행되지 않았습니다.
- Animal(String name) 본문의 describe(): 어느 describe()를 실행할지는 지금 만들고 있는 실제 객체의 타입으로 정해집니다. 실제 객체가 Dog이므로, 부모 생성자 안에서 불렀는데도 Dog가 오버라이딩한 describe()가 실행됩니다.
- Dog의 describe() 안의 println("sound=" + sound): Dog의 필드 초기화식 sound = "멍멍"은 부모 생성자가 끝난 뒤에야 실행됩니다. 지금 sound는 기본값 null이어서 출력은 sound=null입니다.
- Dog의 필드 초기화식 String sound = "멍멍": 부모 생성자가 끝나 Dog()로 돌아왔습니다. 이제야 Dog의 필드 초기화식이 실행되어 sound에 "멍멍"이 들어갑니다.
- System.out.println("Dog() 실행"): Dog() 생성자의 본문이 실행됩니다. 자식 생성자의 본문이 가장 마지막입니다.
- Dog d = new Dog(): 생성자가 모두 끝났습니다. 완성된 객체의 참조가 변수 d에 담깁니다.
- d.describe()로 부른 Dog의 describe() 안의 println("sound=" + sound): 객체가 다 만들어진 뒤이므로 sound에는 "멍멍"이 들어 있습니다. 출력은 sound=멍멍입니다.
실행 결과
- 실행 순서는 부모 생성자 Animal(String name) → Dog의 필드 초기화식 → Dog() 생성자의 본문입니다. 자식 생성자가 먼저 시작되지만, 첫 줄에서 부모 생성자로 넘어가 부모 쪽이 먼저 끝납니다.
- 함정: 부모 생성자가 부른 describe()는 Dog가 오버라이딩한 것이고, 그 시점에 Dog의 sound는 아직 초기화 전이라 null입니다. 코드에는 sound = "멍멍"이라고 적혀 있는데도 sound=null이 찍혔습니다. null인 필드의 메서드를 불렀다면 NullPointerException이 났을 것입니다.
- 그래서 생성자 안에서는 자식이 오버라이딩할 수 있는 메서드를 부르지 않는 것이 원칙입니다. 객체가 다 만들어진 뒤에 부른 d.describe()의 출력은 sound=멍멍으로 정상입니다.
- Dog가 describe()를 오버라이딩했으므로 Animal의 describe()는 실행되지 않습니다. name은 출력되지 않았습니다. 부모 것도 실행하려면 Dog의 describe() 안에서 super.describe()를 부릅니다.
코드
class Animal {
String name;
Animal(String name) {
this.name = name;
System.out.println("Animal(String) 실행");
describe(); // 오버라이딩된 메서드를 생성자에서 부른다
}
void describe() {
System.out.println("name=" + name);
}
}
class Dog extends Animal {
String sound = "멍멍";
Dog() {
super("바둑이"); // 부모의 Animal(String)을 부른다
System.out.println("Dog() 실행");
}
@Override
void describe() {
System.out.println("sound=" + sound);
}
}
Dog d = new Dog();
d.describe();
이 모형의 전제
서른여섯 가지 조합을 이 PC의 JDK 21에서 같은 코드로 실제로 컴파일하고 실행해, 컴파일 결과와 오류 메시지, 객체를 만드는 동안의 출력과 d.describe()의 출력을 대조했습니다. 자식에 생성자를 적지 않아도 부모에 인자 없는 생성자가 없으면 같은 오류가 난다는 것과, super(...)가 생성자의 첫 문장이어야 한다는 것(JDK 21 기준)도 컴파일로 확인했습니다. 더 새로운 JDK에서는 super(...) 앞에 일부 문장을 둘 수 있게 바뀌었다고 알려져 있으나 이 PC에서는 확인하지 못했습니다. 단순화한 점: Animal의 생성자도 맨 먼저 자기 부모인 Object의 생성자를 부르는데 이 단계는 뺐습니다. 어느 메서드가 선택되는지에 대한 자세한 규칙은 "다형성과 메서드 선택" 실습에서 다룹니다. 오류 메시지는 javac의 영어 메시지 첫 줄입니다.
앞 실습: static과 인스턴스 · 다음 실습: 다형성과 메서드 선택