캐스팅 (Casting), 업캐스팅과 다운캐스팅
한눈에 보기
캐스팅은 값이나 참조를 다른 타입으로 취급하는 변환이다. 기본형 확대와 축소 변환, 업캐스팅과 다형성, 다운캐스팅과 instanceof 패턴 매칭, ClassCastException 예방법을 다룬다.
최종 업데이트 2026.09.17
캐스팅(Casting)은 어떤 값이나 참조를 다른 타입으로 취급하도록 변환하는 것으로, 자바에서는 기본형 사이의 형 변환과 참조형 사이의 형 변환으로 나뉜다. 참조형 캐스팅 중 자식 타입을 부모 타입으로 바라보는 것을 업캐스팅(Upcasting), 부모 타입 참조를 다시 구체적인 자식 타입으로 되돌리는 것을 다운캐스팅(Downcasting)이라 한다. 업캐스팅은 항상 안전해 자동으로 일어나지만 다운캐스팅은 실패 가능성이 있어 명시적 문법과 타입 검사가 필요하다.
기본형 캐스팅
기본형 사이의 변환은 표현 범위가 넓어지는 방향이면 컴파일러가 자동으로 처리하며 이를 확대 변환(widening conversion)이라 한다. int를 long이나 double에 대입하는 경우가 여기에 해당한다. 반대로 범위가 좁아지는 축소 변환(narrowing conversion)은 값 손실이 생길 수 있으므로 반드시 괄호를 붙인 명시적 캐스팅을 요구한다.
int i = 100;
long l = i; // 확대 변환, 자동
double d = l; // 확대 변환, 자동
double pi = 3.99;
int truncated = (int) pi; // 3, 소수부가 잘려 나간다
byte overflow = (byte) 130; // -126, 상위 비트가 잘려 나간다
축소 변환에서 실수는 소수부가 버려지고, 정수는 하위 바이트만 남기 때문에 부호까지 뒤집힐 수 있다. long을 int로 줄여 담는 코드에서 오버플로가 조용히 발생하는 것이 흔한 버그 지점이다.
업캐스팅과 다형성
상속 관계에서 자식 클래스의 인스턴스는 언제나 부모 타입으로 취급할 수 있다. 이것이 업캐스팅이며, is-a 관계가 항상 성립하기 때문에 캐스팅 연산자를 쓰지 않아도 자동으로 일어난다. 업캐스팅을 하면 부모 타입에 선언된 멤버만 보이게 되지만, 오버라이딩(overriding)된 메서드를 호출하면 실제 객체의 메서드가 실행된다. 이 동적 바인딩이 다형성(polymorphism)의 핵심이다.
class Animal {
void sound() { System.out.println("..."); }
}
class Dog extends Animal {
void sound() { System.out.println("멍멍"); }
void fetch() { System.out.println("공을 물어온다"); }
}
Animal animal = new Dog(); // 업캐스팅, 캐스팅 연산자 불필요
animal.sound(); // "멍멍", 실제 객체 기준으로 실행
// animal.fetch(); // 컴파일 오류, Animal 타입에는 없는 메서드
중요한 것은 업캐스팅이 객체 자체를 바꾸지 않는다는 점이다. 힙(heap)에 있는 객체는 여전히 Dog이며, 스택의 참조 변수가 어떤 타입의 렌즈로 그 객체를 바라보는지만 달라진다. JVM은 가상 메서드 호출 시 객체 헤더가 가리키는 클래스 정보의 가상 메서드 테이블(vtable)을 따라 실제 구현을 찾아 실행한다.
다운캐스팅과 instanceof
부모 타입 참조로 받아 둔 객체에서 자식 고유의 기능을 쓰려면 다운캐스팅이 필요하다. 다운캐스팅은 컴파일러가 안전성을 보장할 수 없으므로 명시적 캐스팅 연산자를 요구하고, 실행 시점에 실제 객체 타입이 맞지 않으면 ClassCastException을 던진다. 따라서 다운캐스팅 전에는 instanceof 연산자로 실제 타입을 확인하는 것이 정석이다.
Animal animal = new Dog();
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 전통적인 검사 후 캐스팅
dog.fetch();
}
// Java 16부터 정식 도입된 instanceof 패턴 매칭
if (animal instanceof Dog dog) {
dog.fetch(); // 검사와 바인딩을 한 번에 처리
}
Animal cat = new Animal();
Dog wrong = (Dog) cat; // 실행 시 ClassCastException
Java 16에서 정식화된 instanceof 패턴 매칭(pattern matching)을 쓰면 검사와 캐스팅과 변수 선언이 한 문장으로 합쳐져 실수 여지가 줄어든다. Java 21에서는 switch 문에서도 타입 패턴을 쓸 수 있어, 타입에 따라 분기하는 코드를 더 간결하게 작성할 수 있다.
업캐스팅과 다운캐스팅 비교
| 구분 | 업캐스팅 | 다운캐스팅 |
|---|---|---|
| 방향 | 자식 타입에서 부모 타입으로 | 부모 타입에서 자식 타입으로 |
| 명시적 캐스팅 | 불필요, 자동 변환 | 필수 |
| 안전성 | 항상 안전 | 실제 타입이 다르면 ClassCastException |
| 사용 가능한 멤버 | 부모에 선언된 멤버만 | 자식 고유 멤버까지 |
| 주 용도 | 다형성, 공통 타입으로 일괄 처리 | 구체 타입 고유 기능 접근 |
주의할 점
다운캐스팅과 instanceof 분기가 코드 곳곳에 반복된다면 설계를 의심해야 한다. 타입별 분기는 다형성으로, 즉 부모 타입에 공통 메서드를 선언하고 자식이 각자 오버라이딩하는 방식으로 대체하는 것이 객체지향적으로 바람직하다. 또한 제네릭 도입 이전 코드처럼 Object로 받아 캐스팅하는 방식은 컴파일 타임 타입 안전성을 잃으므로, 컬렉션에는 반드시 제네릭 타입을 지정해 캐스팅 자체를 없애는 것이 좋다. 형제 클래스끼리는 상속 관계가 없으므로 캐스팅이 아예 성립하지 않으며 컴파일 오류가 발생한다는 점도 기억해 두어야 한다.
면접에서 이렇게 물어본다
- 업캐스팅과 다운캐스팅의 차이를 설명하고, 다운캐스팅에만 명시적 캐스팅이 필요한 이유를 말해 보라.
- 업캐스팅된 참조로 오버라이딩된 메서드를 호출하면 어떤 메서드가 실행되는가, JVM은 이를 어떻게 결정하는가.
- ClassCastException은 언제 발생하며 어떻게 예방할 수 있는가.
- instanceof 패턴 매칭이 기존 방식과 비교해 어떤 장점이 있는지 설명해 보라.
- 코드에 다운캐스팅이 많아지는 것이 왜 설계상 나쁜 신호인지 다형성 관점에서 설명해 보라.
Java에서 같이 읽기
- 오토 박싱과 오토 언박싱 (Auto Boxing and Unboxing)오토 박싱은 기본형 값을 래퍼 클래스 객체로, 오토 언박싱은 그 반대로 자동 변환하는 기능이다. Integer 캐시, == 비교 함정, NullPointerException 위험과 성능 비용을 JVM 메모리 관점에서 정리한다.
- Call by Value와 Call by Reference자바는 기본형이든 참조형이든 언제나 값에 의한 호출만 사용하며, 참조형은 객체가 아니라 참조값이 복사되어 전달된다. swap 실패 예제와 스택 프레임 관점으로 그 근거를 설명한다.
- 자바 컴파일 과정 (Java Compile Process)javac가 소스를 바이트코드로 바꾸고 JVM이 로딩, 링킹, 초기화 후 인터프리터와 JIT로 실행하는 전체 흐름을 정리한다. 클래스 파일 구조와 부모 위임 모델, 계층형 컴파일까지 다룬다.
- 컴포지션과 상속 (Composition vs Inheritance)컴포지션은 객체를 필드로 소유하고 위임해 재사용하는 기법이고 상속은 구현을 물려받는 기법이다. 취약한 기반 클래스 문제와 is-a, has-a 판단 기준으로 컴포지션 우선 원칙을 설명한다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기