I. 기본 이해
1. 명시적 형변환(Explicit Type Casting):
- 프로그래머가 직접 데이터 타입을 변환하도록 지정
- 코드에서 변환 의도가 명확히 보임
- (int), Integer.valueOf()
2. 암시적 형변환(Implicit Type Casting):
- 컴파일러가 자동으로 수행하는 타입 변환
- 데이터 손실이 없는 안전한 변환만 수행
- 작은 타입에서 큰 타입으로의 변환
3. 일상생활 비유
- 명시적: 물을 얼음틀에 부어 얼음으로 만드는 것 (의도적 변환)
- 암시적: 얼음이 상온에서 자연스럽게 물이 되는 것 (자연스러운 변환)
II. 명시적 형변환(Explicit Type Casting)
명시적 이라는 말에서부터 알 수 있지만 직접 코드를 만들어서 형변환을 하는 방식을 뜻한다.
1. 핵심 구성요소
1. 캐스팅 연산자 () 사용
가장 기본적이고 간단한 형변환 방식이다.
(변환하고자_하는_데이터타입)변수명
형식으로 변환을 한다.
// 1. 기본형 간의 형변환
double d = 85.4;
int score = (int)d; // 85
// 2. 정수형 간의 형변환
int num = 1000;
byte b = (byte)num; // -24 (데이터 손실 발생)
// 3. char와 int 간의 형변환
int ascii = 65;
char ch = (char)ascii; // 'A'
// 4. 실수형 간의 형변환
double pi = 3.14159;
float f = (float)pi; // 3.14159f
// 참조 형식의 캐스팅
Object obj = "Hello";
String str = (String)obj;
// 상속관계에서의 캐스팅
Animal animal = new Dog();
Dog dog = (Dog)animal;
클릭하여 코드 복사
- 컴파일 시점에 타입 체크
- 변환 실패 시 InvalidCastException 발생 가능
타입 안전성 보장
- Java는 강타입 언어이지만, 안전한 형변환(데이터 손실 없는)은 암시적으로 허용한다.
- 하지만 데이터 손실 가능성이 있는 경우에는 명시적 캐스팅이 필요하다.
// 큰 타입에서 작은 타입으로
double d = 3.14;
int i = (int)d; // 명시적 캐스팅 필요
long l = 100L;
int i2 = (int)l; // 명시적 캐스팅 필요
// 실수에서 정수로
float f = 3.14f;
int i3 = (int)f; // 명시적 캐스팅 필요
클릭하여 코드 복사
byte → short → int → long → float → double
char → int
위 방향으로의 변환은 자동(암시적)으로 가능하다.
클릭하여 코드 복사
데이터 범위 주의
- 이런 명시적 변환은 데이터 값의 범위를 고려해야한다.
// 데이터 타입별 범위
byte: -128 ~ 127
short: -32,768 ~ 32,767
int: -2,147,483,648 ~ 2,147,483,647
long: -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
float: ±3.4E+38
double: ±1.7E+308
클릭하여 코드 복사
다형성 구현
- 상속 관계에서 하위 클래스의 특정 메서드 사용시
- 인터페이스 구현체의 구체적 메서드 사용시
// 상위 클래스 참조변수를 하위 클래스로 변환할 때
Animal animal = new Dog(); // 업캐스팅(자동)
// Dog dog = animal; // 컴파일 에러
Dog dog = (Dog)animal; // 다운캐스팅(명시적)
// 안전한 방법
if (animal instanceof Dog) {
Dog dog = (Dog)animal;
dog.bark();
}
// 새로운 방식 (Java 16+)
if (animal instanceof Dog dog) { // 패턴 매칭
dog.bark();
}
클릭하여 코드 복사
업캐스팅(Animal animal = new Dog(); 부분)
- 자식 클래스의 객체를 부모 클래스 타입의 참조변수로 참조하는 것이다.
- "업캐스팅(Upcasting)" 또는 "자동 형변환"이라고 하는데
- 자동으로 형변환이 이루어지기 때문에 명시적인 형변환 연산자가 필요없다.
- 간단하게 말하면 Animal animal2 = (Animal)new Dog(); 같이 캐스팅 연산자 ()를 사용할 필요가 없다는 뜻이다.
- 왜냐면 자식 클래스는 부모 클래스의 모든 특성을 가지고 있기 때문이다.
- 자식 클래스라는 것은 부모 클래스를 이미 extends(상속) 받았다는 소리다.
- 하지만 타입이 부모 클래스로 한정되기 때문에 메서드는 부모의 메서드밖에 사용이 불가능하다.
- 즉, 자식 클래스에서 새로 정의한 메서드가 있다면 사용이 불가능하다.
다운캐스팅(Dog dog = (Dog)animal;부분)
- 다운캐스팅은 부모 타입의 참조변수를 자식 타입의 참조변수로 형변환하는 것이다.
- 앞서 업캐스팅의 경우 부모의 메서드만 사용이 가능하다고 말했는데 그런 문제를 해결하기 위해서
- 타입을 자식 클래스의 타입으로 변환하는 것이다.
// 잘못된 다운캐스팅의 예
Animal animal = new Animal();
Dog dog = (Dog)animal; // RuntimeException 발생!
// 그래서 항상 instanceof로 체크가 필요
if (animal instanceof Dog) {
Dog dog = (Dog)animal;
dog.fetch();
}
클릭하여 코드 복사
그런데 만약에 Animal animal = new Animal();이라는 코드가 있다면 어떨까?
- animal: 참조 변수 (reference variable)
- new Animal(): 실제 객체(인스턴스)
- Animal: 타입(클래스)
"Animal 타입의 참조 변수 animal이 선언되고, 새로 생성된 Animal 인스턴스를 참조한다."라고 할 수 있는데 이 경우에는 타입 자체가 부모 클래스이기 때문에 Dog dog = (Dog)animal; 로 형변환을 시도할 경우 오류가 발생하게 된다.
- 실제 메모리에는 Animal의 특성만 있음 (Dog의 특성은 전혀 없음)
- Dog 관련 데이터나 메서드가 없는 상태이기 때문이다.
그런데 Dog dog = (Dog)animal;로 Dog 타입으로 변환하려고 하면
- 형변환을 위해서 Dog의 메서드와 변수에 접근하려고 하지만
- animal이라는 참조 변수는 Dog 클래스의 정보를 포함하고 있지 않기 때문에
- 실제로는 존재하지 않는 Dog의 메서드와 변수에 접근하려하면서 ClassCastException 오류가 발생한다.
즉 있지도 않는 데이터에 접근하려고 했기 때문에 오류가 발생하는 것이다.
다운캐스팅을 하는 이유
기본적으로 상속을 받았기 때문에 Dog는 Animal의 모든 정보를 가지고 있다. 다만 업캐스팅 시에 타입 제한으로 인해서 본인의 다른 메서드에 접근을 하지 못하는 것 뿐이다. 이 경우 다운캐스팅으로 타입을 자신의 클래스로 변환하면 모든 메서드에 접근이 가능하다. 자식 메서드는 부모의 특성 + 자신의 특성 = 모든 특성을 보유하고 있기 때문이다.
class Animal {
String animalName = "동물";
void makeSound() { }
}
class Dog extends Animal {
String dogBreed = "강아지";
void fetch() { }
}
// 1. Animal animal = new Dog();
// 메모리에는: animalName, makeSound(), dogBreed, fetch() 모두 있음
// 사용 가능: animalName, makeSound()만 가능
// 2. Dog dog = (Dog)animal;
// 메모리에는: 위와 동일 (같은 객체)
// 사용 가능: animalName, makeSound(), dogBreed, fetch() 모두 가능
클릭하여 코드 복사
Animal animal = new Dog();
animal.animalName = "멍멍이"; // OK
// animal.dogBreed = "푸들"; // 컴파일 에러
// animal.fetch(); // 컴파일 에러
Dog dog = (Dog)animal;
dog.animalName = "멍멍이"; // OK (Animal의 것)
dog.dogBreed = "푸들"; // OK (Dog의 것)
dog.makeSound(); // OK (Animal의 것)
dog.fetch(); // OK (Dog의 것)
클릭하여 코드 복사
결과적으로 아래와 같다.
- Animal animal = new Dog();
- Dog 객체가 Animal과 Dog의 모든 데이터를 가지고 있지만, Animal 타입으로 선언되어 Animal의 멤버만 접근이 가능하다.
- Dog dog = (Dog)animal;
- 같은 객체에 대해 Dog와 Animal 클래스의 모든 멤버(메서드와 변수)에 접근이 가능하다.
class Animal {
void makeSound() {
System.out.println("동물 소리");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("멍멍");
}
}
Animal animal = new Dog();
animal.makeSound(); // "멍멍" 출력 - 실제 객체의 메서드가 호출됨
클릭하여 코드 복사
API 호환성
- 범용적인 Object 타입을 사용하는 메서드와 작업시
- 레거시 코드와의 호환성 유지
성능 최적화
- 필요한 경우에만 정밀한 타입 사용
- 메모리 사용 최적화
2. 변환 메서드
// Integer, Double 등의 래퍼 클래스 사용
String numberStr = "123";
int convertedNumber = Integer.valueOf(numberStr);
double convertedDouble = Double.valueOf("3.14");
// parseInt, parseDouble 사용
int parsedNumber = Integer.parseInt("123");
double parsedDouble = Double.parseDouble("3.14");
클릭하여 코드 복사
// String.valueOf() 사용
String str1 = String.valueOf(123);
String str2 = String.valueOf(3.14);
// toString() 사용
String str3 = Integer.toString(123);
String str4 = Double.toString(3.14);
클릭하여 코드 복사
String input = "123";
try {
int result = Integer.parseInt(input);
System.out.println("변환 성공: " + result);
} catch (NumberFormatException e) {
System.out.println("변환 실패");
}
클릭하여 코드 복사
3. 문자열 변환 메서드
String formatted = String.format("%.2f", 3.14159); // "3.14"
클릭하여 코드 복사
StringBuilder sb = new StringBuilder();
sb.append(123); // int를 String으로
클릭하여 코드 복사
int num = 65535;
byte b = (byte)(num & 0xFF); // 비트 마스킹
클릭하여 코드 복사
double d = 3.14;
long rounded = Math.round(d); // 반올림
int floor = (int)Math.floor(d); // 내림
클릭하여 코드 복사
String[] numbers = {"1", "2", "3"};
List<Integer> intList = Arrays.stream(numbers)
.map(Integer::parseInt)
.collect(Collectors.toList());
클릭하여 코드 복사
Optional<String> strOptional = Optional.of("123");
int value = strOptional.map(Integer::parseInt).orElse(0);
클릭하여 코드 복사
III. 암시적 형변환(Implicit Type Casting)
암시적 혹은 묵시적 형변환이라고 하는데 컴파일러가 자동으로 형변환을 수행하는 것을 말한다.
가장 큰 특징은
- 데이터 타입이 작은 타입에서 큰 타입으로 자동 변환 되는 것과
- 컴파일러가 자동으로 타입 변환을 수행하며, 데이터 손실 없이 안전하게 변환하는 것이다.
자바는 고정 너비 데이터 타입(Fixed-width Data Type)
언어다.
즉, "고정된 메모리 크기와 명확한 최대/최소 범위를 가진 데이터 타입을 가지고 있다"라고 할 수 있다.
데이터 표현 범위 : byte(1) < short(2) < int(4) < long(8) < float(4) < double(8)
알다시피 각 타입은 차지하고 있는 바이트의 크기가 각각 다르며 그에 따라 표현할 수 있는 데이터의 범위도 달라진다.
암시적 형변환은 데이터 손실이 발생하지 않는 방향, 즉 표현 범위가 더 작은 데이터 타입에서 더 큰 데이터 타입으로만 자동 변환이 이루어진다. 이때 단순히 바이트 크기가 아닌, 해당 타입이 표현할 수 있는 값의 범위가 기준이 된다. 큰 데이터 타입에서 작은 데이터 타입으로의 변환은 데이터 손실이 발생할 수 있으므로 암시적 변환이 이루어지지 않는 것이다. 만약에 이런 방식으로 암시적 변환이 일어난다면 데이터가 너무나도 쉽게 깨져버릴 것이다.
예를 들면 오버플로우(overflow)
같은 버그가 발생 할 수 있다.
IV. 형변환의 존재 이유와 목적
- 다양한 데이터 타입 간 상호운용성 확보
- 데이터 정확성 보장
- 타입 안정성 유지
V. 주의사항과 일반적 오류
- 데이터 손실 가능성 간과
- 잘못된 형식 변환으로 인한 예외
- 불필요한 형변환 남용
VI. 비교 분석
특성 | 명시적 형변환 | 암시적 형변환 |
---|---|---|
수행 주체 | 프로그래머 | 컴파일러 |
안전성 | 상대적으로 위험 | 안전 |
가독성 | 의도가 명확 | 숨겨진 동작 |
성능 | 추가 연산 필요 | 최적화됨 |
VII. 실무 활용
// 암시적 형변환
int num = 10;
long bigNum = num; // int → long
// 명시적 형변환
double pi = 3.14;
int intPi = (int)pi; // double → int
// 안전한 형변환
string numStr = "123";
int parsedNum = int.Parse(numStr);
try {
int convertedNum = Convert.ToInt32(numStr);
} catch (FormatException ex) {
Console.WriteLine("변환 실패");
}
클릭하여 코드 복사
베스트 프랙티스:
- 가능한 암시적 형변환 활용
- 데이터 손실 가능성 체크
- 예외 처리 구현
- 형변환 필요성 재고
관련 개념:
- 이전: 데이터 타입, 타입 시스템
- 후속: 박싱/언박싱, 제네릭스
'Java > 자바 학습' 카테고리의 다른 글
자바의 제어문(조건문, 반복문, 분기문) (0) | 2025.05.18 |
---|---|
연산자(Operators) (0) | 2025.05.18 |
지역변수와 멤버변수 (0) | 2025.05.17 |
2. 메모리 - 3. 가비지 컬렉션 기본 (0) | 2025.05.14 |
2. 메모리 - 2. 메서드 영역(Method Area) (0) | 2025.05.12 |