Ch 2.5 형변환 (Type Casting)
프로그래밍을 하다 보면 서로 다른 타입의 변수나 리터럴 간에 연산을 수행해야 할 때가 있습니다. 이런 경우, 먼저 연산을 수행하기 전에 값을 같은 타입으로 맞춰 주어야 합니다. 이처럼 변수나 리터럴의 타입을 다른 타입으로 변환하는 것을 형변환(Type Casting) 이라고 합니다.
1. 형변환의 두 가지 종류
| 종류 | 설명 | 위험성 | 문법 |
|---|---|---|---|
| 자동(묵시적) 형변환 | 컴파일러가 자동으로 수행 | 데이터 손실 없음 | 별도 문법 없음 |
| 강제(명시적) 형변환 | 개발자가 직접 지정 | 데이터 손실 가능 | (타입) 값 |
2. 자동 형변환 (Implicit Casting)
작은 타입 → 큰 타입 으로 변환될 때 안전하므로 컴파일러가 자동으로 처리합니다.
자동 형변환 방향 (왼쪽 → 오른쪽으로 자동 변환)
byte → short → int → long → float → double
↑
char ─┘
float(4 bytes)가 long(8 bytes)보다 오른쪽인 이유: float는 부동소수점 방식으로 더 넓은 범위 를 표현하기 때문입니다. (단, 정밀도는 long이 더 높을 수 있습니다.)
public class ImplicitCasting {
public static void main(String[] args) {
// byte → int 자동 변환
byte b = 100;
int i = b; // 자동 형변환 (데이터 손실 없음)
System.out.println("byte → int: " + i); // 100
// int → long 자동 변환
int intVal = 300;
long longVal = intVal;
System.out.println("int → long: " + longVal); // 300
// int → double 자동 변환
int num = 5;
double dbl = num;
System.out.println("int → double: " + dbl); // 5.0
// long → float 자동 변환
long bigNum = 1_000_000_000L;
float flt = bigNum;
System.out.println("long → float: " + flt); // 1.0E9 (정밀도 손실 주의!)
// char → int 자동 변환
char ch = 'A';
int unicode = ch; // 'A'의 유니코드값 65
System.out.println("char → int ('A'): " + unicode); // 65
}
}
자동 형변환이 일어나는 상황
public class AutoCastingSituations {
public static void main(String[] args) {
// 상황 1: 산술 연산 시 - 더 큰 타입으로 맞춤
int i = 10;
long l = 20L;
long result1 = i + l; // int가 long으로 자동 변환
System.out.println("int + long = " + result1); // 30
// 상황 2: byte + byte → int (byte 연산은 int로 변환됨!)
byte a = 10, b = 20;
// byte sum = a + b; // 에러! byte + byte의 결과는 int
int sum = a + b; // 정상
System.out.println("byte + byte = " + sum); // 30 (int)
// 상황 3: 메서드 인자 자동 변환
printDouble(5); // int 5를 double로 자동 변환하여 전달
printDouble(3L); // long 3을 double로 자동 변환
// 상황 4: 삼항 연산자에서 자동 변환
int x = 10;
double d = (x > 5) ? 1 : 2.0; // int 1이 double로 자동 변환
System.out.println("삼항 연산자 결과: " + d); // 1.0
}
static void printDouble(double d) {
System.out.println("double: " + d);
}
}
3. 강제 형변환 (Explicit Casting)
큰 타입 → 작은 타입 으로 변환할 때는 데이터 손실이 생길 수 있으므로 개발자가 명시적으로 지정해야 합니다.
// 문법: (변환할타입) 값
double d = 85.7;
int score = (int) d; // 소수점 이하 버림 (반올림 아님!)
public class ExplicitCasting {
public static void main(String[] args) {
// double → int: 소수점 버림
double d = 85.7;
int score = (int) d;
System.out.println("85.7 → int: " + score); // 85 (버림!)
// long → int: 범위 초과 시 데이터 손실
long bigLong = 3_000_000_000L; // int 범위 초과
int intFromLong = (int) bigLong;
System.out.println("30억 → int: " + intFromLong); // 이상한 값!
// 범위 안의 long → int: 안전
long safeLong = 100L;
int intFromSafe = (int) safeLong;
System.out.println("100L → int: " + intFromSafe); // 100 (OK)
// float → int
float f = 3.99f;
int truncated = (int) f;
System.out.println("3.99f → int: " + truncated); // 3 (버림!)
// int → byte: 데이터 손실
int exceed = 300;
byte b = (byte) exceed;
System.out.println("300 → byte: " + b); // 44 (손실!)
}
}
강제 형변환 시 소수점은 버려집니다 (반올림 아님). 3.99를 int로 변환하면 4가 아니라 3입니다.
반올림하려면 Math.round() 또는 (int)(d + 0.5)를 사용하세요.
4. 정수-실수 형변환
public class IntDoubleConversion {
public static void main(String[] args) {
// int → double: 자동 변환 (정확)
int intVal = 7;
double doubleVal = intVal;
System.out.println("7 → double: " + doubleVal); // 7.0
// double → int: 소수점 버림
double pi = 3.14159;
int truncated = (int) pi;
System.out.println("3.14159 → int: " + truncated); // 3
// 반올림하고 싶다면?
int rounded = (int) Math.round(pi);
System.out.println("3.14159 반올림: " + rounded); // 3
double piLarge = 3.7;
int roundedLarge = (int) Math.round(piLarge);
System.out.println("3.7 반올림: " + roundedLarge); // 4
// 정수 나눗셈 함정!
int a = 5, b = 2;
int wrongResult = a / b; // 정수 나눗셈 → 2 (소수점 버림)
double correctResult = (double) a / b; // double 나눗셈 → 2.5
System.out.println("5 / 2 (int): " + wrongResult); // 2
System.out.println("5 / 2 (double): " + correctResult); // 2.5
}
}
5. char-int 형변환과 아스키/유니코드 활용
char는 내부적으로 정수(유니코드)로 저장되므로 int와 상호 변환이 가능합니다.
public class CharIntConversion {
public static void main(String[] args) {
// char → int: 유니코드 값 확인
char ch = 'A';
int unicode = ch; // 자동 변환
System.out.println("'A' = " + unicode); // 65
// int → char: 유니코드로 문자 생성
int code = 65;
char fromCode = (char) code; // 강제 변환 필요
System.out.println("65 = '" + fromCode + "'"); // 'A'
// 알파벳 대소문자 변환 (차이가 32)
char upper = 'A';
char lower = (char)(upper + 32); // A(65) + 32 = a(97)
System.out.println("A → a: " + lower);
// 반대로
char lowerB = 'b';
char upperB = (char)(lowerB - 32);
System.out.println("b → B: " + upperB);
// char 산술 연산
System.out.println('A' + 1); // 66 (int 결과)
System.out.println((char)('A' + 1)); // B (char로 변환)
// 숫자 문자 → 정수 변환
char digitChar = '7';
int digit = digitChar - '0'; // '7'(55) - '0'(48) = 7
System.out.println("'7' - '0' = " + digit); // 7
// 주요 유니코드 값
System.out.println("'0' = " + (int)'0'); // 48
System.out.println("'A' = " + (int)'A'); // 65
System.out.println("'a' = " + (int)'a'); // 97
System.out.println("'가' = " + (int)'가'); // 44032
}
}
6. 문자열 ↔ 숫자 변환
실무에서 매우 자주 사용되는 패턴입니다. 사용자 입력은 항상 문자열이므로 숫자로 변환하는 작업이 필수적입니다.
public class StringNumberConversion {
public static void main(String[] args) {
// === 문자열 → 숫자 ===
String strInt = "42";
String strDouble = "3.14";
String strLong = "9999999999";
String strBool = "true";
int parsedInt = Integer.parseInt(strInt);
double parsedDouble = Double.parseDouble(strDouble);
long parsedLong = Long.parseLong(strLong);
boolean parsedBool = Boolean.parseBoolean(strBool);
System.out.println("parseInt: " + parsedInt); // 42
System.out.println("parseDouble: " + parsedDouble); // 3.14
System.out.println("parseLong: " + parsedLong); // 9999999999
System.out.println("parseBoolean: " + parsedBool); // true
// === 숫자 → 문자열 ===
int num = 100;
double dbl = 3.14;
// 방법 1: String.valueOf()
String fromInt1 = String.valueOf(num);
String fromDbl1 = String.valueOf(dbl);
// 방법 2: 래퍼클래스 .toString()
String fromInt2 = Integer.toString(num);
String fromDbl2 = Double.toString(dbl);
// 방법 3: "" + 숫자 (가장 간단하지만 권장되지 않음)
String fromInt3 = "" + num;
System.out.println("valueOf: " + fromInt1); // "100"
System.out.println("toString: " + fromInt2); // "100"
System.out.println("concat: " + fromInt3); // "100"
// 잘못된 형변환 처리
try {
int bad = Integer.parseInt("abc"); // 숫자가 아닌 문자열!
} catch (NumberFormatException e) {
System.out.println("에러: " + e.getMessage()); // For input string: "abc"
}
}
}
문자열 → 숫자 변환 실패 시 NumberFormatException이 발생합니다. 외부 입력(사용자 입력, 파일 등)을 변환할 때는 반드시 예외 처리를 하거나 숫자 형식인지 검증하세요.
7. 형변환 시 주의사항
오버플로우 주의
public class CastingWarnings {
public static void main(String[] args) {
// long → int 강제 변환 시 오버플로우
long largeNum = 3_000_000_000L; // 30억 (int 범위 초과)
int small = (int) largeNum;
System.out.println("30억 → int: " + small); // -1294967296 (데이터 손실!)
// 안전하게 변환하려면 범위 확인 먼저
if (largeNum <= Integer.MAX_VALUE && largeNum >= Integer.MIN_VALUE) {
int safe = (int) largeNum;
System.out.println("안전한 변환: " + safe);
} else {
System.out.println("int 범위 초과! long을 유지합니다.");
}
// float → int 정밀도 손실
float floatVal = 16_777_217f; // 2^24 + 1 (float로 정확히 표현 불가)
int fromFloat = (int) floatVal;
System.out.println("16777217f → int: " + fromFloat); // 16777216 (오차!)
// 연산 도중 오버플로우
int price = 1_000_000;
int quantity = 3_000;
int totalWrong = price * quantity; // int 범위 초과! 오버플로우
long totalRight = (long) price * quantity; // long으로 먼저 형변환
System.out.println("잘못된 계산: " + totalWrong); // -1294967296
System.out.println("올바른 계산: " + totalRight); // 3000000000
}
}
정밀도 손실 주의
public class PrecisionLoss {
public static void main(String[] args) {
// long → float 변환 시 정밀도 손실 가능
long precise = 123_456_789_012_345L;
float imprecise = precise; // 자동 변환이지만 정밀도 손실!
long backToLong = (long) imprecise;
System.out.println("원래 long: " + precise); // 123456789012345
System.out.println("float으로 변환: " + imprecise); // 1.2345679E14 (손실!)
System.out.println("다시 long으로: " + backToLong); // 123456791642112 (손실!)
// double은 더 정밀하지만 여전히 한계 있음
double d = precise;
long backToLong2 = (long) d;
System.out.println("double로 변환: " + d); // 1.2345678901234E14
System.out.println("다시 long으로: " + backToLong2); // 더 정확하지만 완전하지 않음
}
}
8. instanceof 연산자와 참조형 형변환
참조형에서도 형변환이 일어납니다. 상속 관계에서 부모 타입 ↔ 자식 타입 변환 시 사용합니다.
public class InstanceofExample {
static class Animal {
void speak() { System.out.println("..."); }
}
static class Dog extends Animal {
void speak() { System.out.println("멍멍!"); }
void fetch() { System.out.println("공 가져옴!"); }
}
static class Cat extends Animal {
void speak() { System.out.println("야옹!"); }
}
public static void main(String[] args) {
Animal animal = new Dog(); // 자동 형변환 (업캐스팅): Dog → Animal
animal.speak(); // 멍멍! (다형성)
// animal.fetch(); // 에러! Animal 타입에는 fetch() 없음
// instanceof로 타입 확인 후 강제 형변환 (다운캐스팅)
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 강제 형변환 (다운캐스팅)
dog.fetch(); // 멍멍!
}
// Java 16+ Pattern Matching - 더 간결한 방법
if (animal instanceof Dog d) {
d.fetch(); // 형변환 없이 바로 사용
}
// ClassCastException 예시
Animal cat = new Cat();
try {
Dog wrongDog = (Dog) cat; // Cat을 Dog로 변환 불가!
} catch (ClassCastException e) {
System.out.println("ClassCastException: " + e.getMessage());
}
}
}
참조형 강제 형변환 전에 반드시 instanceof로 타입을 확인 하세요. 잘못된 형변환은 ClassCastException 런타임 에러를 발생시킵니다.
9. 실전 예제: 평균 계산 시 형변환 필요성
public class AverageCalculation {
public static void main(String[] args) {
int[] scores = {85, 92, 78, 96, 88, 74, 91, 83, 79, 95};
// === 잘못된 방법 (정수 나눗셈) ===
int total = 0;
for (int score : scores) {
total += score;
}
int wrongAverage = total / scores.length; // 정수 나눗셈: 소수점 버림
System.out.println("잘못된 평균: " + wrongAverage); // 86 (실제: 86.1)
// === 올바른 방법 1: 나누기 전에 형변환 ===
double correctAverage1 = (double) total / scores.length;
System.out.printf("올바른 평균1: %.2f%n", correctAverage1); // 86.10
// === 올바른 방법 2: 변수 타입을 double로 선언 ===
double total2 = 0;
for (int score : scores) {
total2 += score; // int가 double로 자동 변환
}
double correctAverage2 = total2 / scores.length;
System.out.printf("올바른 평균2: %.2f%n", correctAverage2); // 86.10
// 최고점과 최저점
int max = scores[0], min = scores[0];
for (int score : scores) {
if (score > max) max = score;
if (score < min) min = score;
}
System.out.println("최고점: " + max);
System.out.println("최저점: " + min);
System.out.printf("중앙값 근사: %.1f%n", (max + min) / 2.0);
// 학점 계산 (형변환 활용)
System.out.println("\n=== 각 학생 학점 ===");
for (int i = 0; i < scores.length; i++) {
char grade;
if (scores[i] >= 90) grade = 'A';
else if (scores[i] >= 80) grade = 'B';
else if (scores[i] >= 70) grade = 'C';
else grade = 'D';
System.out.printf("학생 %2d: %3d점 → %c%n", i + 1, scores[i], grade);
}
}
}
출력:
잘못된 평균: 86
올바른 평균1: 86.10
올바른 평균2: 86.10
최고점: 96
최저점: 74
중앙값 근사: 85.0
=== 각 학생 학점 ===
학생 1: 85점 → B
학생 2: 92점 → A
학생 3: 78점 → C
학생 4: 96점 → A
학생 5: 88점 → B
...
10. 형변환 요약 표
| 변환 방향 | 예시 | 자동/강제 | 데이터 손실 |
|---|---|---|---|
| byte → int | byte b=1; int i=b; | 자동 | 없음 |
| int → long | int i=1; long l=i; | 자동 | 없음 |
| int → double | int i=5; double d=i; | 자동 | 없음 (5.0) |
| char → int | char c='A'; int i=c; | 자동 | 없음 (65) |
| double → int | (int)3.14 | 강제 | 소수점 손실 |
| long → int | (int)100L | 강제 | 범위 초과 시 손실 |
| int → char | (char)65 | 강제 | 없음 ('A') |
| String → int | Integer.parseInt("42") | 메서드 | 없음 |
| int → String | String.valueOf(42) | 메서드 | 없음 |
정리
- 자동 형변환: 작은 → 큰 타입 (byte→short→int→long→float→double), 안전
- 강제 형변환:
(타입) 값, 큰 → 작은 타입, 데이터 손실 가능 - 소수 → 정수: 소수점 버림(반올림 아님),
Math.round()사용 시 반올림 - char ↔ int: 유니코드 값으로 상호 변환 가능
- String ↔ 숫자:
Integer.parseInt(),String.valueOf()등 메서드 사용 - 평균 계산:
(double) 합계 / 개수— 형변환 필수!