CATEGORY

카테고리 (607)
AI (12)
Language & Specs (259)
FrameWork (34)
Library (19)
App (41)
Git (8)
Build & Dependency (2)
AWS (15)
DataBase (45)
OS (33)
Tool (16)
IT (120)
반응형
SEEMINGLY ONLINE

Seemingly
Online

이모저모 방방곡곡 두루두루 개발지식 저장소

RECENT POSTS

Language & Specs/Java

자바 ClassCastException 원인과 해결 — cannot be cast to 완벽 정리

반응형

ClassCastException객체를 실제 타입이 아닌 다른 타입으로 강제 캐스팅할 때 런타임에 터지는 예외입니다. 예를 들어 String이 들어 있는 Object(Integer)로 캐스팅하면 class java.lang.String cannot be cast to class java.lang.Integer 메시지와 함께 죽습니다. 원인은 대부분 세 가지입니다: 제네릭 없는(raw) 컬렉션, 다운캐스팅 전 타입 확인 누락, 서로 다른 숫자 래퍼 타입(Integer↔Double) 혼동. 해결은 instanceof로 캐스팅 전에 타입을 확인하거나, 컬렉션에 제네릭을 붙여 컴파일 단계에서 막는 것입니다. (기준: OpenJDK 21)

ClassCastException은 언제 발생하나

자바에서 캐스팅에는 두 종류가 있습니다. 하위 타입을 상위 타입으로 올리는 업캐스팅(항상 안전)과, 상위 타입 참조를 하위 타입으로 내리는 다운캐스팅입니다. ClassCastException은 이 다운캐스팅에서 실제 객체가 그 타입의 인스턴스가 아닐 때 발생합니다. 컴파일은 통과하지만 실행 중에 터지는 게 핵심입니다.

Object o = "hello";        // 실제 타입은 String
Integer i = (Integer) o;   // 컴파일 OK, 실행 시 ClassCastException

OpenJDK 21에서 위 코드를 실제로 돌리면 다음 메시지가 나옵니다.

Exception in thread "main" java.lang.ClassCastException:
class java.lang.String cannot be cast to class java.lang.Integer
(java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
💡 메시지 뒷부분의 module … loader는 자바 9 이상에서 추가된 정보입니다. 같은 이름의 클래스가 다른 클래스로더에서 로드돼 캐스팅이 실패하는 경우(플러그인·WAS 환경)를 구분하려고 로더까지 찍어 줍니다. 자바 8까지는 java.lang.String cannot be cast to java.lang.Integer처럼 짧게만 나왔습니다.

원인 1. 제네릭 없는 raw 컬렉션 — 가장 흔한 함정

실무에서 ClassCastException정작 캐스팅을 쓰지도 않았는데 터지는 경우가 이겁니다. 제네릭 타입 파라미터를 빼먹은 raw 컬렉션에 엉뚱한 타입을 넣으면, 컴파일러가 get() 자리에 보이지 않는 캐스팅을 자동으로 끼워 넣기 때문입니다.

List raw = new ArrayList();   // raw type — 경고만 뜨고 컴파일됨
raw.add("str");
raw.add(42);                  // Integer가 섞여 들어감

List<String> list = raw;      // 대입은 통과
String s = list.get(1);       // ← 여기서 ClassCastException
                              //   Integer cannot be cast to String

예외가 터지는 지점이 add(42)가 아니라 한참 뒤 get(1)이라 원인을 찾기 어렵습니다. 컬렉션에는 항상 제네릭을 붙이세요. List<String>로 선언하면 add(42)가 아예 컴파일되지 않아, 런타임이 아닌 컴파일 단계에서 막힙니다.

원인 2. 숫자 래퍼 타입 혼동 (Integer ↔ Double)

ObjectNumber에 담긴 값을 잘못된 래퍼 타입으로 캐스팅해도 터집니다. 10은 오토박싱으로 Integer가 되는데, 이걸 Double로 캐스팅하면 실패합니다.

Object o = 10;              // Integer.valueOf(10)
Double d = (Double) o;      // ClassCastException:
                            //   Integer cannot be cast to Double

// JSON 파싱, DB 결과 매핑 등에서 특히 자주 만남
double v = ((Number) o).doubleValue();  // 이렇게 Number 경유로 변환

박싱 타입끼리는 상속 관계가 없어서 캐스팅으로 못 바꿉니다. 값 변환이 목적이라면 NumberintValue()·doubleValue()를 쓰세요.

해결. instanceof로 캐스팅 전에 확인한다

다운캐스팅이 꼭 필요하면, 캐스팅하기 전에 instanceof로 실제 타입을 확인합니다. 자바 16부터 정식 도입된 instanceof 패턴 매칭을 쓰면 확인과 변수 선언을 한 줄로 끝낼 수 있습니다.

Object o = getValue();

// 자바 16+ : 패턴 매칭 — 확인 즉시 i에 캐스팅된 값이 담김
if (o instanceof Integer i) {
    System.out.println(i * 2);
} else {
    System.out.println("Integer 아님, 건너뜀");
}

// 예전 방식 (자바 15 이하)
if (o instanceof Integer) {
    Integer i = (Integer) o;
    System.out.println(i * 2);
}
⚠️ null instanceof 무엇이든은 항상 false입니다. 그래서 instanceof 가드는 NullPointerException까지 겸사겸사 막아 줍니다. 반대로 아무 확인 없이 null을 캐스팅하는 것 자체는 예외가 아닙니다(ClassCastException이 아니라 이후 사용 시점에 NPE).

원인별 정리

상황 터지는 곳 해결
raw 컬렉션에 다른 타입 혼입 get() 호출 시점 제네릭 List<String> 선언
확인 없이 다운캐스팅 캐스팅 문장 instanceof 패턴 매칭
Integer를 Double로 캐스팅 캐스팅 문장 ((Number)o).doubleValue()

자주 묻는 질문 (FAQ)

Q. ClassCastException은 checked 예외인가요, unchecked 예외인가요?
unchecked 예외입니다. ClassCastExceptionRuntimeException을 상속하므로(Object → Throwable → Exception → RuntimeException → ClassCastException) throws에 선언하거나 try-catch로 강제로 잡을 필요가 없습니다. 잡는 대신 instanceof로 미리 막는 것이 정석입니다.

Q. 컴파일은 되는데 왜 실행할 때만 터지나요?
캐스팅의 타입 검사는 대부분 런타임에 이뤄지기 때문입니다. 컴파일러는 "상위 타입 참조를 하위 타입으로 내리는 것"이 문법적으로 가능한지만 보고, 그 참조가 실제로 어떤 객체를 가리키는지는 실행해 봐야 압니다.

Q. 캐스팅을 안 쓰는 게 제일 좋나요?
가능하면 그렇습니다. 컬렉션·메서드에 제네릭을 제대로 붙이면 다운캐스팅 자체가 거의 필요 없어집니다. 값이 있을 수도 없을 수도 있는 경우는 캐스팅 대신 자바 Optional의 map·orElseGet으로 다루면 런타임 예외를 더 줄일 수 있습니다.

마무리

ClassCastException은 결국 "이 객체가 그 타입이 아니다"라는 신호입니다. 메시지의 X cannot be cast to Y에서 X가 실제 타입, Y가 내가 기대한 타입이므로, 이 두 이름만 보면 어디서 잘못 캐스팅했는지 바로 좁혀집니다. 근본 예방은 제네릭을 빠짐없이 붙이고, 다운캐스팅 앞에 instanceof 패턴 매칭을 두는 것입니다.


📚 참고 출처 (2026년 7월 20일 확인)
· ClassCastException (Java SE 21 API 문서)
· 예외 메시지는 OpenJDK 21.0.1에서 직접 컴파일·실행해 확인

반응형

COMMENTS