instanceof와 오버라이딩
instanceof와 패턴 매칭으로 다운캐스팅 경계를 안전하게 만들고 실제 객체의 오버라이딩 메서드가 우선되는 실행 규칙을 추적합니다.
다운캐스팅 전에 실제 객체가 원하는 타입과 호환되는지 확인하면 ClassCastException을 피할 수 있습니다.
instanceof는 이 검사를 표현하고, 패턴 매칭은 성공한 범위에서 자식 변수를 함께 만듭니다.
다형성의 더 중요한 축은 부모 참조에서도 실제 객체의 오버라이딩 메서드가 실행된다는 점입니다.
@Override 검증
public final class BadOverrideSignature {
private static class Post {
void publish() {
System.out.println("board");
}
}
private static final class CodePost extends Post {
@Override
void publish(int viewCount) {
System.out.println("code=" + viewCount);
}
}
}error: method does not override or implement a method from a supertype매개변수 없는 부모 publish과 int 매개변수가 있는 자식 publish은 서로 다른 메서드입니다.
@Override가 없으면 새 오버로드로 컴파일되어 부모 참조 호출은 계속 부모 구현을 실행합니다.
애노테이션을 붙이면 개발자의 재정의 의도와 실제 서명이 다를 때 즉시 실패합니다.
오버라이딩 조건에는 같은 이름과 매개변수 목록, 호환되는 반환 타입, 부모보다 좁지 않은 접근 범위가 포함됩니다.
private 메서드는 자식에서 보이지 않으므로 오버라이딩 대상이 아닙니다.
static 메서드는 인스턴스 동적 디스패치가 아니라 클래스 기준으로 숨겨집니다.
instanceof 타입 검사
public final class SafeInstanceof {
public static void main(String[] args) {
inspect(new CodePost("feature/login"));
inspect(new TextPost(120));
}
private static void inspect(Post post) {
if (post instanceof CodePost) {
CodePost codePost = (CodePost) post;
System.out.println("branch=" + codePost.branch);
} else if (post instanceof TextPost) {
TextPost textPost = (TextPost) post;
System.out.println("bodyLength=" + textPost.bodyLength);
}
}
private static class Post {}
private static final class CodePost extends Post {
private final String branch;
CodePost(String branch) { this.branch = branch; }
}
private static final class TextPost extends Post {
private final int bodyLength;
TextPost(int bodyLength) { this.bodyLength = bodyLength; }
}
}branch=feature/login
bodyLength=120post instanceof CodePost는 참조가 null이 아니고 실제 객체가 CodePost 또는 그 하위 타입이면 true입니다.
성공한 분기 안에서 다운캐스팅이 안전합니다.
null은 어떤 참조 타입 instanceof에도 false이므로 별도 캐스팅 예외가 나지 않습니다.
패턴 매칭
public final class PatternMatchingInstanceof {
public static void main(String[] args) {
printDetail(new CodePost("feature/login"));
printDetail(new Post());
}
private static void printDetail(Post post) {
if (post instanceof CodePost codePost) {
System.out.println("branch=" + codePost.branch());
return;
}
System.out.println("general");
}
private static class Post {}
private static final class CodePost extends Post {
private final String branch;
CodePost(String branch) { this.branch = branch; }
String branch() { return branch; }
}
}branch=feature/login
general패턴 변수 codePost는 instanceof가 true인 경로에서만 존재합니다.
실패한 경로에서는 CodePost라는 보장이 없으므로 사용할 수 없습니다.
조기 반환과 결합하면 이후 코드에서 반대 조건이 보장되는 흐름도 컴파일러가 분석합니다.
패턴 매칭은 캐스팅 문법 중복을 줄이지만 구체 타입 분기 자체를 없애지는 않습니다.
게시글 종류가 늘 때마다 분기를 수정한다면 공통 오버라이딩 메서드로 옮길 수 있는 행동인지 검토합니다.
동적 메서드 탐색
public final class OverrideDispatch {
public static void main(String[] args) {
Post first = new TextPost();
Post second = new CodePost();
run(first);
run(second);
}
private static void run(Post post) {
System.out.println(post.publish());
}
private static class Post {
String publish() {
return "general";
}
}
private static final class TextPost extends Post {
@Override
String publish() {
return "text";
}
}
private static final class CodePost extends Post {
@Override
String publish() {
return "code";
}
}
}text
coderun의 매개변수 타입은 Post이므로 publish 호출이 컴파일됩니다.
실행할 때 첫 번째 객체는 TextPost 구현을, 두 번째 객체는 CodePost 구현을 선택합니다.
메서드 오버라이딩은 다형적 참조보다 우선해 실제 객체의 가장 구체적인 구현을 실행합니다.
이 규칙 덕분에 클라이언트가 if (post instanceof ...)로 종류를 나누지 않아도 됩니다.
새 자식 타입은 publish 규칙을 구현하고 기존 run에 그대로 전달됩니다.
필드와 static 메서드
public final class FieldAndMethodDispatch {
public static void main(String[] args) {
Parent value = new Child();
System.out.println("field=" + value.name);
System.out.println("method=" + value.name());
System.out.println("static=" + value.staticName());
}
private static class Parent {
String name = "parent";
String name() { return "parent"; }
static String staticName() { return "parent"; }
}
private static final class Child extends Parent {
String name = "child";
@Override String name() { return "child"; }
static String staticName() { return "child"; }
}
}field=parent
method=child
static=parent필드와 static 메서드는 참조 변수의 정적 타입 Parent를 기준으로 선택되고, 인스턴스 메서드만 실제 Child 객체의 오버라이딩을 선택합니다.
따라서 다형적 행동은 필드가 아니라 인스턴스 메서드로 표현해야 합니다.
연습 문제
Post 배열에서 CodePost이면 branch, TextPost이면 bodyLength를 패턴 매칭 instanceof로 출력하세요.
그 밖의 게시글은 general을 출력합니다.
해설 보기
public final class PatternDetailReport {
public static void main(String[] args) {
Post[] values = {
new CodePost("main"),
new TextPost(80),
new Post()
};
for (Post value : values) {
print(value);
}
}
private static void print(Post value) {
if (value instanceof CodePost codePost) {
System.out.println("branch=" + codePost.branch);
} else if (value instanceof TextPost textPost) {
System.out.println("bodyLength=" + textPost.bodyLength);
} else {
System.out.println("general");
}
}
private static class Post {}
private static final class CodePost extends Post {
private final String branch;
CodePost(String branch) { this.branch = branch; }
}
private static final class TextPost extends Post {
private final int bodyLength;
TextPost(int bodyLength) { this.bodyLength = bodyLength; }
}
}branch=main
bodyLength=80
general패턴 변수는 각 성공 분기 안에서만 유효해 별도 명시적 캐스트가 필요 없습니다.