Java에서 형 변환(Type Casting)은 한 타입의 객체나 변수를 다른 타입으로 변환할 때 사용됩니다. 한 데이터 타입을 다른 타입으로 변환하거나 할당할 때 두 타입이 서로 호환되지 않을 수 있으며, 호환된다면 자연스럽게 변환이 이루어지지만 그렇지 않은 경우에는 데이터 손실이 발생할 가능성이 있습니다.
Java 형 변환의 종류
Java의 형 변환은 크게 두 가지로 분류됩니다.
- 확장 캐스팅(Widening Casting, 암시적) – 자동 타입 변환
- 축소 캐스팅(Narrowing Casting, 명시적) – 명시적인 변환 필요
확장 캐스팅 (작은 타입 → 큰 타입)
확장 형 변환(Widening Type Conversion)은 두 타입이 서로 호환되고 대상 타입이 원본 타입보다 클 때 발생합니다. 즉, 두 타입이 호환되며 대상 타입이 원본 타입보다 큰 경우에 적용되며, 개발자가 별도로 캐스팅 연산자를 작성하지 않아도 컴파일러가 자동으로 변환을 처리해 줍니다.
예제 1: 기본 타입의 확장 캐스팅
public class ImplicitCastingExample {
public static void main(String args[]) {
byte i = 40;
// 아래 변환에는 캐스팅이 필요 없음
short j = i;
int k = j;
long l = k;
float m = l;
double n = m;
System.out.println("byte value : " + i);
System.out.println("short value : " + j);
System.out.println("int value : " + k);
System.out.println("long value : " + l);
System.out.println("float value : " + m);
System.out.println("double value : " + n);
}
}실행 결과
byte value : 40 short value : 40 int value : 40 long value : 40 float value : 40.0 double value : 40.0
위 예제에서 볼 수 있듯이 byte → short → int → long → float → double 순서로 작은 타입에서 큰 타입으로 변환될 때는 어떠한 캐스팅 연산자 없이도 값이 안전하게 전달됩니다.
클래스 타입의 확장 캐스팅
형 변환은 기본 타입뿐만 아니라 클래스 타입에도 적용됩니다. 아래 예제에서는 Child 클래스(작은 타입)의 인스턴스를 Parent 클래스(큰 타입) 변수에 할당하고 있습니다. 자식 클래스는 부모 클래스보다 하위 타입이므로 업캐스팅(upcasting) 시 캐스팅 연산자가 필요하지 않습니다.
예제 2: 클래스 타입의 확장 캐스팅
class Parent {
public void display() {
System.out.println("Parent class display() called");
}
}
public class Child extends Parent {
public static void main(String args[]) {
Parent p = new Child();
p.display();
}
}실행 결과
Parent class display() method called
축소 캐스팅 (큰 타입 → 작은 타입)
반대로 큰 타입을 작은 타입에 할당할 때는 명시적 캐스팅(Explicit Casting)이 반드시 필요합니다. 축소 캐스팅 시에는 데이터 손실이 발생할 수 있으므로 컴파일러가 자동으로 변환해 주지 않으며, 개발자가 직접 캐스팅 연산자를 명시해야 합니다.
예제 1: 기본 타입의 축소 캐스팅
public class ExplicitCastingExample {
public static void main(String args[]) {
double d = 30.0;
// 아래 변환에는 명시적 캐스팅이 필요함
float f = (float) d;
long l = (long) f;
int i = (int) l;
short s = (short) i;
byte b = (byte) s;
System.out.println("double value : " + d);
System.out.println("float value : " + f);
System.out.println("long value : " + l);
System.out.println("int value : " + i);
System.out.println("short value : " + s);
System.out.println("byte value : " + b);
}
}실행 결과
double value : 30.0 float value : 30.0 long value : 30 int value : 30 short value : 30 byte value : 30
double에서 byte로 갈수록 타입의 크기가 줄어들기 때문에 매 단계마다 (float), (long), (int)와 같은 캐스팅 연산자를 반드시 붙여야 합니다.
클래스 타입의 축소 캐스팅
클래스 타입에서도 마찬가지로 큰 타입(부모 클래스)을 작은 타입(자식 클래스)에 할당할 때는 명시적 캐스팅, 즉 다운캐스팅(downcasting)이 필요합니다.
예제 2: 클래스 타입의 축소 캐스팅
class Parent {
public void display() {
System.out.println("Parent class display() method called");
}
}
public class Child extends Parent {
public void display() {
System.out.println("Child class display() method called");
}
public static void main(String args[]) {
Parent p = new Child();
Child c = (Child) p;
c.display();
}
}실행 결과
Child class display() method called
정리: 확장 캐스팅 vs 축소 캐스팅
| 구분 | 확장 캐스팅 (암시적) | 축소 캐스팅 (명시적) |
|---|---|---|
| 변환 방향 | 작은 타입 → 큰 타입 | 큰 타입 → 작은 타입 |
| 캐스팅 연산자 | 불필요 (자동 변환) | 필요 (직접 명시) |
| 데이터 손실 | 없음 | 발생 가능 |
| 클래스 관점 | 업캐스팅 (자식 → 부모) | 다운캐스팅 (부모 → 자식) |
핵심을 요약하면, 작은 타입에서 큰 타입으로의 변환은 컴파일러가 안전하다고 판단하여 자동으로 처리해 주지만, 큰 타입에서 작은 타입으로의 변환은 데이터 손실 위험이 있기 때문에 반드시 개발자가 명시적으로 캐스팅해 주어야 합니다. 특히 클래스 타입의 다운캐스팅은 실제 객체가 해당 타입이 아닐 경우 ClassCastException이 발생할 수 있으므로, 필요하다면 instanceof 연산자로 타입을 확인한 후 캐스팅하는 것이 안전합니다.