본문으로 건너뛰기

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 → intbyte b=1; int i=b;자동없음
int → longint i=1; long l=i;자동없음
int → doubleint i=5; double d=i;자동없음 (5.0)
char → intchar c='A'; int i=c;자동없음 (65)
double → int(int)3.14강제소수점 손실
long → int(int)100L강제범위 초과 시 손실
int → char(char)65강제없음 ('A')
String → intInteger.parseInt("42")메서드없음
int → StringString.valueOf(42)메서드없음

정리

  • 자동 형변환: 작은 → 큰 타입 (byte→short→int→long→float→double), 안전
  • 강제 형변환: (타입) 값, 큰 → 작은 타입, 데이터 손실 가능
  • 소수 → 정수: 소수점 버림(반올림 아님), Math.round() 사용 시 반올림
  • char ↔ int: 유니코드 값으로 상호 변환 가능
  • String ↔ 숫자: Integer.parseInt(), String.valueOf() 등 메서드 사용
  • 평균 계산: (double) 합계 / 개수 — 형변환 필수!