상속·생성자·오버라이딩
extends 관계·상속 메모리·오버라이딩·접근 제어·super와 생성자 순서를 게시글 계층으로 한 번에 연결합니다.
상속은 여러 클래스에 공통인 상태와 행동을 부모 클래스에 두고 자식이 재사용·확장하는 문법입니다.
단순 코드 복사 제거보다 “자식은 부모의 한 종류인가”라는 타입 관계가 먼저입니다.
게시판에서 텍스트·코드·영상 형식을 모두 게시글로 다룰 때 상속 후보가 생깁니다.
class Post {
void publish() {
System.out.println("게시글 공개");
}
}
class TextPost extends Post {
}
TextPost textPost = new TextPost();
textPost.publish();TextPost extends Post는 텍스트 게시글이 게시글의 한 종류임을 나타냅니다.
자식에 publish를 다시 쓰지 않아도 부모의 메서드를 사용할 수 있습니다.
상태를 생성자로 받아야 할 때는 부모 초기화가 먼저 필요하므로, 이제 실패 예를 통해 super(...)의 위치를 확인합니다.
첫 코드는 문법의 핵심만 보인 조각입니다. 아래의 src/·lab/ 예제는 각각 별도 파일과 별도 클래스 정의를 사용합니다. 같은 이름의 Post를 한 파일에 모두 모아 넣지 말고, 정상 실행 예제와 컴파일 실패 예제를 나누어 확인합니다.
부모 생성자 호출
public final class MissingParentConstructor {
public static void main(String[] args) {
CodePost post = new CodePost();
System.out.println(post);
}
private static class Post {
Post(String title) {
System.out.println(title);
}
}
private static final class CodePost extends Post {
}
}error: constructor Post in class Post cannot be applied to given typesCodePost에 생성자를 쓰지 않으면 컴파일러가 기본 생성자와 첫 문장 super()를 가정합니다.
부모 Post에는 매개변수 없는 생성자가 없으므로 호출할 수 없습니다.
자식 생성자에서 부모가 요구하는 값을 명시해야 합니다.
CodePost(String title) {
super(title);
}상속 인스턴스에는 부모 부분과 자식 부분이 함께 만들어지므로 두 생성자 모두 실행돼야 합니다.
부모 상태를 먼저 초기화한 뒤 자식 상태를 초기화합니다.
이 순서 설명은 부모 생성자가 정상 반환한 경우입니다. 아래 PostInheritance에서 new CodePost("로그인 구현", 0, "feature/login")으로 조회 수를 0으로 바꾸면 부모 검사가 예외를 던집니다. 그러면 this.branch = branch에 도달하지 못하고 생성 식도 정상 반환하지 않습니다. 부모 검사는 통과했지만 branch가 잘못된 경우까지 이 부모 생성자가 검사하는 것은 아닙니다.
extends와 기능 확장
public final class PostInheritance {
public static void main(String[] args) {
TextPost textPost = new TextPost("가입 인사", 40, 120);
CodePost codePost = new CodePost("로그인 구현", 50, "feature/login");
textPost.publish();
textPost.previewBody();
codePost.publish();
codePost.commit();
}
private static class Post {
private final String title;
private final int viewCount;
Post(String title, int viewCount) {
if (title.isBlank() || viewCount <= 0) throw new IllegalArgumentException();
this.title = title;
this.viewCount = viewCount;
}
void publish() {
System.out.println("publish=" + title + ":views=" + viewCount);
}
protected String title() {
return title;
}
}
private static final class TextPost extends Post {
private final int bodyLength;
TextPost(String title, int viewCount, int bodyLength) {
super(title, viewCount);
this.bodyLength = bodyLength;
}
void previewBody() {
System.out.println("preview=" + title() + ":chars=" + bodyLength);
}
}
private static final class CodePost extends Post {
private final String branch;
CodePost(String title, int viewCount, String branch) {
super(title, viewCount);
this.branch = branch;
}
void commit() {
System.out.println("commit=" + title() + ":" + branch);
}
}
}publish=가입 인사:views=40
preview=가입 인사:chars=120
publish=로그인 구현:views=50
commit=로그인 구현:feature/login두 자식 객체에는 Post에 선언한 title·viewCount 상태가 함께 존재합니다. 두 자식은 publish를 상속하고 자기 필드와 행동을 추가합니다.
자바 클래스는 extends 뒤에 부모 하나만 선택할 수 있습니다.
클래스 다중 상속을 허용하면 두 부모가 같은 메서드를 제공할 때 어느 구현을 사용할지 모호한 다이아몬드 문제가 생길 수 있습니다.
여러 역할 조합은 뒤에서 인터페이스 다중 구현으로 다룹니다.
private 필드는 자식의 상속 멤버가 아니므로 상속만으로 직접 사용할 수 없습니다. 이 예제처럼 같은 최상위 클래스 안에 중첩한 타입 사이에는 별도의 private 접근 허용 규칙이 적용됩니다.
부모가 보호해야 할 상태는 private으로 두고 title() 같은 protected 또는 public 행동으로 필요한 협력을 제공합니다.
protected를 모든 자식 편의용 공개 필드처럼 쓰면 부모 불변식을 우회하므로 메서드 경계를 우선합니다.
상속 객체의 구조
new CodePost(...)를 실행하면 하나의 객체 참조를 얻지만 그 인스턴스 안에는 Post에서 정의한 상태와 CodePost에서 정의한 상태가 모두 있습니다.
메서드를 찾을 때 현재 타입에서 시작해 부모 방향으로 올라갑니다.
codePost.commit()은 CodePost에서 찾습니다.codePost.publish()는 CodePost에 없으므로 Post에서 찾습니다.- 부모에서 자식 방향으로는 임의로 내려가 메서드를 찾지 않습니다.
객체가 두 개 만들어진다는 뜻은 아닙니다.
하나의 상속 인스턴스 안에 클래스 계층에 대응하는 부분이 함께 초기화된다고 이해합니다.
메서드 코드는 각 객체에 복사되지 않고 클래스 정보로 관리됩니다.
PostInheritance에서 생성한 codePost 한 객체에 Post 선언의 title와 viewCount, CodePost 선언의 branch가 함께 존재한다. 선언별 묶음은 별도 객체나 실제 메모리 배치를 뜻하지 않는다.
부모와 자식의 필드는 한 CodePost 객체에 존재합니다
PostInheritance의 new CodePost("로그인 구현", 50, "feature/login") 생성이 끝난 뒤입니다.
codePost가 가리키는 것은 CodePost 객체 하나입니다.
- Post에 선언한 필드
title = "로그인 구현",viewCount = 50- CodePost에 선언한 필드
branch = "feature/login"
상자는 필드를 선언한 클래스를 구분한 것입니다. 별도 부모 객체나 실제 메모리 주소·배치를 뜻하지 않습니다. private 필드의 상태가 객체에 존재하는 것과 그 필드가 자식의 상속 멤버인지 여부는 다릅니다.
메서드 오버라이딩
public final class OverridePost {
public static void main(String[] args) {
TextPost textPost = new TextPost("가입 인사", 40, 120);
CodePost codePost = new CodePost("로그인 구현", 50, "feature/login");
textPost.publish();
codePost.publish();
}
private static class Post {
protected final String title;
protected final int viewCount;
Post(String title, int viewCount) {
this.title = title;
this.viewCount = viewCount;
}
void publish() {
System.out.println("post=" + title + ":views=" + viewCount);
}
}
private static final class TextPost extends Post {
private final int bodyLength;
TextPost(String title, int viewCount, int bodyLength) {
super(title, viewCount);
this.bodyLength = bodyLength;
}
@Override
void publish() {
System.out.println("text=" + title + ":chars=" + bodyLength);
}
}
private static final class CodePost extends Post {
private final String branch;
CodePost(String title, int viewCount, String branch) {
super(title, viewCount);
this.branch = branch;
}
@Override
void publish() {
super.publish();
System.out.println("code=branch/" + branch);
}
}
}text=가입 인사:chars=120
post=로그인 구현:views=50
code=branch/feature/login메서드 이름, 매개변수 목록, 반환 호환성이 부모 메서드와 맞으면 오버라이딩합니다.
@Override는 필수 문법은 아니지만 서명이 어긋나 새 메서드가 되는 실수를 컴파일러가 잡게 하므로 붙입니다.
자식 오버라이딩 메서드 안에서 super.publish()로 부모 구현을 명시적으로 호출할 수 있습니다.
publish 오버라이딩과 super.publish의 실제 출력
TextPost의 한 출력 뒤 CodePost는 자식 진입→super.publish 출력→자식 출력으로 이어집니다. 현재 구현과 누적 출력이 변화하는 실제 두 호출입니다.
OverridePost의 textPost.publish는 자식 구현의 한 줄을 출력하고, codePost.publish는 super.publish로 부모의 한 줄을 출력한 다음 자식의 한 줄을 출력한다. 두 main 호출은 서로 다른 객체를 대상으로 한다.
자식의 publish 구현과 super 호출에 따라 출력이 달라집니다
OverridePost의 main은 textPost.publish() 다음에 codePost.publish()를 호출합니다.
| 호출 대상 | 실행하는 구현 | 해당 호출의 출력 |
|---|---|---|
textPost.publish() | TextPost.publish()만 실행합니다. | text=가입 인사:chars=120 |
codePost.publish() | CodePost.publish() 안에서 super.publish()로 Post.publish()를 먼저 실행한 뒤 자식의 출력문을 실행합니다. | post=로그인 구현:views=50
|
textPost.publish()TextPost.publish()만 실행하여text=가입 인사:chars=120을 출력합니다.codePost.publish()CodePost.publish()의super.publish()가Post.publish()를 실행하여post=로그인 구현:views=50을 출력합니다. 돌아온 뒤code=branch/feature/login을 출력합니다.
super.publish()는 같은 CodePost 객체를 수신자로 부모 구현을 선택합니다. 부모 객체를 따로 만들지 않으며, TextPost의 호출에는 super.publish()가 없습니다.
이 예의 조건
- TextPost 호출이 정상 종료된 뒤 다른 CodePost를 호출합니다. 상자 줄 분리는 실제 출력 줄이 아닙니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 별도 TextPost 호출은 자식의 한 줄을 출력한다. 다음 CodePost 호출은 super.publish의 부모 출력 뒤 자식으로 정상 복귀하여 출력한다. super는 같은 수신 객체를 사용하며 첫 호출과 대상이 다르다. 긴 문자열의 상자 줄 분리는 실제 출력 줄이 아니다. |
| 구분 | 내용 |
|---|---|
| 설명 | 별도 TextPost 호출은 자식의 한 줄을 출력한다. 다음 CodePost 호출은 super.publish의 부모 출력 뒤 자식으로 정상 복귀하여 출력한다. super는 같은 수신 객체를 사용하며 첫 호출과 대상이 다르다. 긴 문자열의 상자 줄 분리는 실제 출력 줄이 아니다. |
| 전체 읽기 정보 | publish 오버라이딩과 super.publish의 실제 출력 publish 오버라이딩과 super.publish의 실제 출력 OverridePost의 textPost.publish는 자식 구현의 한 줄을 출력하고, codePost.publish는 super.publish로 부모의 한 줄을 출력한 다음 자식의 한 줄을 출력한다. 두 main 호출은 서로 다른 객체를 대상으로 한다. 자식의 publish 구현과 super 호출에 따라 출력이 달라집니다 OverridePost 의 main 은 textPost.publish() 다음에 codePost.publish() 를 호출합니다. 두 객체의 publish 구현 선택과 출력 순서 호출 대상 실행하는 구현 해당 호출의 출력 textPost.publish() TextPost.publish() 만 실행합니다. text=가입 인사:chars=120 codePost.publish() CodePost.publish() 안에서 super.publish() 로 Post.publish() 를 먼저 실행한 뒤 자식의 출력문을 실행합니다. post=로그인 구현:views=50 code=branch/feature/login textPost.publish() TextPost.publish() 만 실행하여 text=가입 인사:chars=120 을 출력합니다. codePost.publish() CodePost.publish() 의 super.publish() 가 Post.publish() 를 실행하여 post=로그인 구현:views=50 을 출력합니다. 돌아온 뒤 code=branch/feature/login 을 출력합니다. super.publish() 는 같은 CodePost 객체를 수신자로 부모 구현을 선택합니다. 부모 객체를 따로 만들지 않으며, TextPost 의 호출에는 super.publish() 가 없습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| Text 자식 호출 textPost.publish가 자식 구현만 실행합니다. |
|
|
| Code 부모 호출 codePost.publish 안에서 super.publish를 실행합니다. |
|
|
| Code 복귀 출력 부모에서 정상 복귀한 뒤 자식 문장을 실행합니다. |
|
|
publish 오버라이딩과 super.publish의 실제 출력
TextPost의 한 출력 뒤 CodePost는 자식 진입→super.publish 출력→자식 출력으로 이어집니다. 현재 구현과 누적 출력이 변화하는 실제 두 호출입니다.
OverridePost의 textPost.publish는 자식 구현의 한 줄을 출력하고, codePost.publish는 super.publish로 부모의 한 줄을 출력한 다음 자식의 한 줄을 출력한다. 두 main 호출은 서로 다른 객체를 대상으로 한다.
자식의 publish 구현과 super 호출에 따라 출력이 달라집니다
OverridePost의 main은 textPost.publish() 다음에 codePost.publish()를 호출합니다.
| 호출 대상 | 실행하는 구현 | 해당 호출의 출력 |
|---|---|---|
textPost.publish() | TextPost.publish()만 실행합니다. | text=가입 인사:chars=120 |
codePost.publish() | CodePost.publish() 안에서 super.publish()로 Post.publish()를 먼저 실행한 뒤 자식의 출력문을 실행합니다. | post=로그인 구현:views=50
|
textPost.publish()TextPost.publish()만 실행하여text=가입 인사:chars=120을 출력합니다.codePost.publish()CodePost.publish()의super.publish()가Post.publish()를 실행하여post=로그인 구현:views=50을 출력합니다. 돌아온 뒤code=branch/feature/login을 출력합니다.
super.publish()는 같은 CodePost 객체를 수신자로 부모 구현을 선택합니다. 부모 객체를 따로 만들지 않으며, TextPost의 호출에는 super.publish()가 없습니다.
이 예의 조건
- TextPost 호출이 정상 종료된 뒤 다른 CodePost를 호출합니다. 상자 줄 분리는 실제 출력 줄이 아닙니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 별도 TextPost 호출은 자식의 한 줄을 출력한다. 다음 CodePost 호출은 super.publish의 부모 출력 뒤 자식으로 정상 복귀하여 출력한다. super는 같은 수신 객체를 사용하며 첫 호출과 대상이 다르다. 긴 문자열의 상자 줄 분리는 실제 출력 줄이 아니다. |
| 구분 | 내용 |
|---|---|
| 설명 | 별도 TextPost 호출은 자식의 한 줄을 출력한다. 다음 CodePost 호출은 super.publish의 부모 출력 뒤 자식으로 정상 복귀하여 출력한다. super는 같은 수신 객체를 사용하며 첫 호출과 대상이 다르다. 긴 문자열의 상자 줄 분리는 실제 출력 줄이 아니다. |
| 전체 읽기 정보 | publish 오버라이딩과 super.publish의 실제 출력 publish 오버라이딩과 super.publish의 실제 출력 OverridePost의 textPost.publish는 자식 구현의 한 줄을 출력하고, codePost.publish는 super.publish로 부모의 한 줄을 출력한 다음 자식의 한 줄을 출력한다. 두 main 호출은 서로 다른 객체를 대상으로 한다. 자식의 publish 구현과 super 호출에 따라 출력이 달라집니다 OverridePost 의 main 은 textPost.publish() 다음에 codePost.publish() 를 호출합니다. 두 객체의 publish 구현 선택과 출력 순서 호출 대상 실행하는 구현 해당 호출의 출력 textPost.publish() TextPost.publish() 만 실행합니다. text=가입 인사:chars=120 codePost.publish() CodePost.publish() 안에서 super.publish() 로 Post.publish() 를 먼저 실행한 뒤 자식의 출력문을 실행합니다. post=로그인 구현:views=50 code=branch/feature/login textPost.publish() TextPost.publish() 만 실행하여 text=가입 인사:chars=120 을 출력합니다. codePost.publish() CodePost.publish() 의 super.publish() 가 Post.publish() 를 실행하여 post=로그인 구현:views=50 을 출력합니다. 돌아온 뒤 code=branch/feature/login 을 출력합니다. super.publish() 는 같은 CodePost 객체를 수신자로 부모 구현을 선택합니다. 부모 객체를 따로 만들지 않으며, TextPost 의 호출에는 super.publish() 가 없습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| Text 자식 호출 textPost.publish가 자식 구현만 실행합니다. |
|
|
| Code 부모 호출 codePost.publish 안에서 super.publish를 실행합니다. |
|
|
| Code 복귀 출력 부모에서 정상 복귀한 뒤 자식 문장을 실행합니다. |
|
|
필드는 오버라이딩되지 않습니다.
부모와 자식에 같은 이름 필드를 만들면 두 저장 위치가 생겨 참조 타입에 따른 숨김 문제가 생깁니다.
같은 개념 상태는 부모에 하나만 두는 편이 낫습니다.
super 참조
this는 현재 객체의 현재 클래스 관점, super는 같은 객체의 부모 클래스 관점에서 멤버를 찾습니다.
@Override
void publish() {
super.publish();
System.out.println("child extension");
}super가 별도의 부모 객체 참조를 반환하는 것은 아닙니다.
오버라이딩된 자식 메서드 대신 부모 구현을 선택하는 특별한 키워드입니다.
이 장의 생성자들은 super(...)를 먼저 호출해 부모 초기화를 위임합니다. Java 25에서는 초기 생성 문맥의 제약을 지키는 실행문이 그 앞에 올 수 있으므로, 모든 생성자에서 첫 문장이어야 하는 것은 아닙니다.
this(...)로 같은 클래스 생성자에 위임했다면 그 위임 경로의 최종 생성자가 부모를 호출합니다.
이 예제에서는 자식 생성자가 먼저 super로 위임하므로 부모 생성자의 필드 대입이 끝난 뒤 자식 생성자의 나머지 필드 대입을 실행합니다.
부모 생성자에서 오버라이딩 가능한 메서드를 호출하면 아직 자식 필드가 초기화되기 전에 자식 구현이 실행될 수 있으므로 피합니다.
예시
다음 코드는 위험한 호출 순서를 관찰하기 위한 독립 예제입니다. 부모 생성자에서 이미 자식의 report()가 선택되지만, 자식의 필드 초기화 식은 아직 실행되지 않았습니다.
public final class ConstructorCallOrder {
public static void main(String[] args) {
Child child = new Child();
child.report();
}
private static class Parent {
Parent() { report(); }
void report() {}
}
private static final class Child extends Parent {
private String branch = "feature/login";
@Override void report() {
System.out.println("branch=" + branch);
}
}
}branch=null
branch=feature/login첫 출력은 부모 생성 중의 호출입니다. branch는 참조 필드의 기본값인 null이며, 부모 생성자가 끝난 뒤 자식 필드 초기화 식이 실행됩니다. 둘째 출력은 new Child()가 정상적으로 끝난 뒤입니다. 생성자 안의 가상 호출을 피하고 생성이 완료된 객체의 행동을 호출하는 이유가 여기 있습니다.
원문의 super 호출이 초기화하는 부모 상태와 자식 상태
CodePost가 super로 부모 검사/대입을 끝낸 뒤 복귀하여 branch를 대입합니다. 부모 예외 시 자식 대입에 미도달하는 실제 초기화 순서를 보여 줄 수 있습니다.
PostInheritance의 CodePost 생성자는 super(title, viewCount)를 먼저 호출하고, Post가 두 필드를 대입하여 반환한 뒤 branch를 대입한다. 이 순서는 원문의 생성자 본문에 한정한다.
이 생성자는 부모 필드 대입 뒤 자식 필드를 대입합니다
PostInheritance의 CodePost("로그인 구현", 50, "feature/login") 생성에서 소스에 적힌 대입 순서를 읽습니다.
| 원문 순서 | 실행하는 코드 | 이 생성에서 정하는 값 |
|---|---|---|
| 부모 초기화 위임 | super(title, viewCount) | Post 생성자에 "로그인 구현"과 50을 전달합니다. |
| Post 생성자 |
| title = "로그인 구현", viewCount = 50 |
| CodePost 생성자로 복귀 | this.branch = branch | branch = "feature/login"을 대입합니다. |
- 부모 초기화 위임
super(title, viewCount)가Post생성자에"로그인 구현",50을 전달합니다. - Post 생성자
title.isBlank() || viewCount <= 0검사 뒤this.title = title,this.viewCount = viewCount로"로그인 구현",50을 대입합니다. - CodePost 생성자로 복귀
this.branch = branch로"feature/login"을 대입합니다.
이 입력은 부모 검사를 통과합니다. 표는 본문에 명시한 Post와 CodePost의 대입 순서이며, 생성자 전체의 JVM 호출 기록이나 모든 Java 생성자에 대한 순서 보장이 아닙니다. 이 원문은 super 앞에 실행문을 두지 않습니다.
이 예의 조건
- 원 CodePost 생성자는 super 앞 실행문이 없으며 모든 Java 생성자의 불변 순서로 확대하지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 원 PostInheritance의 유효 입력은 CodePost 생성자→super로 Post 검사/필드 대입→정상 반환→branch 대입으로 진행한다. 모든 Java 생성자의 불변 순서가 아니며 예외 시 마지막 대입은 없다. |
| 구분 | 내용 |
|---|---|
| 설명 | 원 PostInheritance의 유효 입력은 CodePost 생성자→super로 Post 검사/필드 대입→정상 반환→branch 대입으로 진행한다. 모든 Java 생성자의 불변 순서가 아니며 예외 시 마지막 대입은 없다. |
| 전체 읽기 정보 | 원문의 super 호출이 초기화하는 부모 상태와 자식 상태 원문의 super 호출이 초기화하는 부모 상태와 자식 상태 PostInheritance의 CodePost 생성자는 super(title, viewCount)를 먼저 호출하고, Post가 두 필드를 대입하여 반환한 뒤 branch를 대입한다. 이 순서는 원문의 생성자 본문에 한정한다. 이 생성자는 부모 필드 대입 뒤 자식 필드를 대입합니다 PostInheritance 의 CodePost("로그인 구현", 50, "feature/login") 생성에서 소스에 적힌 대입 순서를 읽습니다. CodePost 생성자의 명시적 super 호출과 그 뒤의 대입 원문 순서 실행하는 코드 이 생성에서 정하는 값 부모 초기화 위임 super(title, viewCount) Post 생성자에 "로그인 구현" 과 50 을 전달합니다. Post 생성자 title.isBlank() || viewCount <= 0 검사 this.title = title this.viewCount = viewCount title = "로그인 구현" , viewCount = 50 CodePost 생성자로 복귀 this.branch = branch branch = "feature/login" 을 대입합니다. 부모 초기화 위임 super(title, viewCount) 가 Post 생성자에 "로그인 구현" , 50 을 전달합니다. Post 생성자 title.isBlank() || viewCount <= 0 검사 뒤 this.title = title , this.viewCount = viewCount 로 "로그인 구현" , 50 을 대입합니다. CodePost 생성자로 복귀 this.branch = branch 로 "feature/login" 을 대입합니다. 이 입력은 부모 검사를 통과합니다. 표는 본문에 명시한 Post 와 CodePost 의 대입 순서이며, 생성자 전체의 JVM 호출 기록이나 모든 Java 생성자에 대한 순서 보장이 아닙니다. 이 원문은 super 앞에 실행문을 두지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 자식 생성 진입 CodePost가 title과 viewCount를 super에 넘깁니다. |
|
|
| 부모 검사·기록 Post 검사를 통과해 부모 필드를 대입합니다. |
|
|
| 자식으로 복귀 정상 super 반환 뒤 branch를 대입합니다. |
|
|
원문의 super 호출이 초기화하는 부모 상태와 자식 상태
CodePost가 super로 부모 검사/대입을 끝낸 뒤 복귀하여 branch를 대입합니다. 부모 예외 시 자식 대입에 미도달하는 실제 초기화 순서를 보여 줄 수 있습니다.
PostInheritance의 CodePost 생성자는 super(title, viewCount)를 먼저 호출하고, Post가 두 필드를 대입하여 반환한 뒤 branch를 대입한다. 이 순서는 원문의 생성자 본문에 한정한다.
이 생성자는 부모 필드 대입 뒤 자식 필드를 대입합니다
PostInheritance의 CodePost("로그인 구현", 50, "feature/login") 생성에서 소스에 적힌 대입 순서를 읽습니다.
| 원문 순서 | 실행하는 코드 | 이 생성에서 정하는 값 |
|---|---|---|
| 부모 초기화 위임 | super(title, viewCount) | Post 생성자에 "로그인 구현"과 50을 전달합니다. |
| Post 생성자 |
| title = "로그인 구현", viewCount = 50 |
| CodePost 생성자로 복귀 | this.branch = branch | branch = "feature/login"을 대입합니다. |
- 부모 초기화 위임
super(title, viewCount)가Post생성자에"로그인 구현",50을 전달합니다. - Post 생성자
title.isBlank() || viewCount <= 0검사 뒤this.title = title,this.viewCount = viewCount로"로그인 구현",50을 대입합니다. - CodePost 생성자로 복귀
this.branch = branch로"feature/login"을 대입합니다.
이 입력은 부모 검사를 통과합니다. 표는 본문에 명시한 Post와 CodePost의 대입 순서이며, 생성자 전체의 JVM 호출 기록이나 모든 Java 생성자에 대한 순서 보장이 아닙니다. 이 원문은 super 앞에 실행문을 두지 않습니다.
이 예의 조건
- 원 CodePost 생성자는 super 앞 실행문이 없으며 모든 Java 생성자의 불변 순서로 확대하지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 원 PostInheritance의 유효 입력은 CodePost 생성자→super로 Post 검사/필드 대입→정상 반환→branch 대입으로 진행한다. 모든 Java 생성자의 불변 순서가 아니며 예외 시 마지막 대입은 없다. |
| 구분 | 내용 |
|---|---|
| 설명 | 원 PostInheritance의 유효 입력은 CodePost 생성자→super로 Post 검사/필드 대입→정상 반환→branch 대입으로 진행한다. 모든 Java 생성자의 불변 순서가 아니며 예외 시 마지막 대입은 없다. |
| 전체 읽기 정보 | 원문의 super 호출이 초기화하는 부모 상태와 자식 상태 원문의 super 호출이 초기화하는 부모 상태와 자식 상태 PostInheritance의 CodePost 생성자는 super(title, viewCount)를 먼저 호출하고, Post가 두 필드를 대입하여 반환한 뒤 branch를 대입한다. 이 순서는 원문의 생성자 본문에 한정한다. 이 생성자는 부모 필드 대입 뒤 자식 필드를 대입합니다 PostInheritance 의 CodePost("로그인 구현", 50, "feature/login") 생성에서 소스에 적힌 대입 순서를 읽습니다. CodePost 생성자의 명시적 super 호출과 그 뒤의 대입 원문 순서 실행하는 코드 이 생성에서 정하는 값 부모 초기화 위임 super(title, viewCount) Post 생성자에 "로그인 구현" 과 50 을 전달합니다. Post 생성자 title.isBlank() || viewCount <= 0 검사 this.title = title this.viewCount = viewCount title = "로그인 구현" , viewCount = 50 CodePost 생성자로 복귀 this.branch = branch branch = "feature/login" 을 대입합니다. 부모 초기화 위임 super(title, viewCount) 가 Post 생성자에 "로그인 구현" , 50 을 전달합니다. Post 생성자 title.isBlank() || viewCount <= 0 검사 뒤 this.title = title , this.viewCount = viewCount 로 "로그인 구현" , 50 을 대입합니다. CodePost 생성자로 복귀 this.branch = branch 로 "feature/login" 을 대입합니다. 이 입력은 부모 검사를 통과합니다. 표는 본문에 명시한 Post 와 CodePost 의 대입 순서이며, 생성자 전체의 JVM 호출 기록이나 모든 Java 생성자에 대한 순서 보장이 아닙니다. 이 원문은 super 앞에 실행문을 두지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 자식 생성 진입 CodePost가 title과 viewCount를 super에 넘깁니다. |
|
|
| 부모 검사·기록 Post 검사를 통과해 부모 필드를 대입합니다. |
|
|
| 자식으로 복귀 정상 super 반환 뒤 branch를 대입합니다. |
|
|
상속 선택 기준
CodePost is a Post는 자연스럽지만 Board is a Scanner는 자연스럽지 않습니다.
단지 Scanner 메서드를 재사용하려고 상속하면 타입 의미가 왜곡됩니다.
“가지고 사용한다” 관계는 구성으로 표현합니다.
final class BoardCli {
private final Scanner scanner;
}상속은 부모 계약을 자식이 지킬 수 있어야 합니다.
부모를 기대하는 위치에 자식을 넣어도 핵심 의미가 깨지지 않아야 다형성으로 확장할 수 있습니다.
공통 코드가 조금 있다는 이유만으로 계층을 만들면 부모 변경이 모든 자식에 퍼지고 잘못된 결합이 생깁니다.
게시글 계층의 부모 배열
public final class InheritedPostArray {
public static void main(String[] args) {
Post[] posts = {
new TextPost("가입 인사", 40),
new CodePost("로그인 구현", 50)
};
int total = 0;
for (Post post : posts) {
post.publish();
total += post.viewCount();
}
System.out.println("total=" + total);
}
private static class Post {
private final String title;
private final int viewCount;
Post(String title, int viewCount) {
this.title = title;
this.viewCount = viewCount;
}
int viewCount() {
return viewCount;
}
protected String title() {
return title;
}
void publish() {
System.out.println("post=" + title);
}
}
private static final class TextPost extends Post {
TextPost(String title, int viewCount) {
super(title, viewCount);
}
@Override
void publish() {
System.out.println("text=" + title());
}
}
private static final class CodePost extends Post {
CodePost(String title, int viewCount) {
super(title, viewCount);
}
@Override
void publish() {
System.out.println("code=" + title());
}
}
}text=가입 인사
code=로그인 구현
total=90부모 타입 배열에 서로 다른 자식 참조를 넣고 공통 메서드를 호출했습니다.
왜 자식 구현이 선택되는지는 다음 문서부터 다형적 참조와 동적 디스패치로 자세히 다룹니다.
연습 문제
부모 Post에 title과 publish(), 자식 TextPost에 bodyLength, ImagePost에 imageCount를 두세요.
각 자식이 publish를 오버라이딩하고 생성자에서 super로 title을 전달합니다.
해설 보기
public final class PostInheritanceExercise {
public static void main(String[] args) {
Post[] posts = {
new TextPost("가입 인사", 300),
new ImagePost("행사 사진", 4)
};
for (Post post : posts) {
post.publish();
}
}
private static class Post {
protected final String title;
Post(String title) {
this.title = title;
}
void publish() {
System.out.println("publish=" + title);
}
}
private static final class TextPost extends Post {
private final int bodyLength;
TextPost(String title, int bodyLength) {
super(title);
this.bodyLength = bodyLength;
}
@Override
void publish() {
System.out.println("text=" + title + ":chars=" + bodyLength);
}
}
private static final class ImagePost extends Post {
private final int imageCount;
ImagePost(String title, int imageCount) {
super(title);
this.imageCount = imageCount;
}
@Override
void publish() {
System.out.println("image=" + title + ":count=" + imageCount);
}
}
}text=가입 인사:chars=300
image=행사 사진:count=4공통 title과 타입 계약은 부모에, 게시글 형식별 상태와 표현은 자식에 있습니다.
자식 생성자는 부모 초기화를 먼저 위임합니다.