왜 필요한가 자동 형변환 강제 캐스팅 연산의 함정 정리 표 📝 문제풀이
◀ 이전 📋 목차 CH 05 ▶
🔄 CHAPTER 04 · 입문

타입을 바꾸는 기술, 형변환

자바는 타입에 엄격한 언어예요. 그래서 서로 다른 자료형끼리 대입하거나 계산하려면 타입을 맞춰 주는 작업이 필요해요. 이걸 형변환(타입 캐스팅)이라고 부릅니다. 규칙만 잡으면 어렵지 않아요. 천천히 따라오세요.

🎯 이 장을 끝내면
🤔
왜 필요한가
형변환은 왜 필요할까?
자바가 "타입에 깐깐한" 언어라서 생기는 이야기예요.

지난 장(CH 03)에서 int, double, char 같은 자료형(타입)을 배웠어요. 각 타입은 담을 수 있는 값의 종류와 크기가 정해져 있어요. 자바는 타입이 다르면 그냥 섞어 쓰지 못하게 막는 언어예요. 그래서 서로 다른 타입끼리 대입하거나 계산할 때는 타입을 하나로 맞춰 줘야 하는데, 그 작업이 바로 형변환(type conversion / type casting)이에요.

🥤 비유 — 크기가 다른 컵에 물 옮기기 타입을 물컵이라고 생각해봐요. int는 작은 컵, double은 큰 컵이에요.
작은 컵 → 큰 컵: 아무 문제 없이 그냥 부으면 돼요. 넘칠 일이 없죠. → 이게 자동 형변환.
큰 컵 → 작은 컵: 넘칠 수 있으니 "넘쳐도 괜찮아, 강제로 옮길게"라고 내가 직접 선언해야 해요. → 이게 강제 캐스팅. 즉 형변환의 방향(작은↔큰)에 따라, 자바가 알아서 해주느냐 / 내가 직접 시켜야 하느냐가 갈려요. 이 큰 그림만 잡고 시작하면 됩니다.

아래 코드처럼 타입이 안 맞으면 자바는 컴파일 단계에서 에러를 내거나, 규칙에 따라 변환을 요구해요. 그래서 형변환은 선택이 아니라 "타입이 다를 때 반드시 거치는 관문"이에요.

double d = 10;      // int 값 10 → double로 자동 변환 (작은→큰, OK)
int i = 3.9;        // ❌ 컴파일 에러! double(큰) → int(작은)은 자동으로 안 됨
int j = (int) 3.9;  // ✅ (int)로 "강제로 바꿀게"라고 선언 → j는 3
🔤
컴파일 에러(compile error) — 프로그램을 실행하기도 전에, 자바가 코드를 번역(컴파일)하는 단계에서 "이 코드는 규칙에 안 맞아요"라며 막아 버리는 오류예요. 위 int i = 3.9;처럼 타입이 안 맞으면 아예 번역이 안 돼서 실행조차 못 해요. 무서워 보이지만, 사실은 실수를 일찍 잡아 주는 고마운 안전장치예요.
형변환 타입 캐스팅 자동 변환 강제 변환 데이터 손실 오버플로우 int double char
🧭
이번 장의 핵심 질문 딱 하나. "작은 타입에서 큰 타입으로 가는가, 그 반대인가?" 이 방향만 판단할 수 있으면 형변환의 90%는 끝난 거예요.
⬆️
자동 형변환
작은 타입 → 큰 타입 (묵시적)
넓은 그릇에 담는 거라 값이 안전해요. 자바가 알아서 해줘요.

자동 형변환(묵시적 형변환, implicit conversion)작은(좁은) 타입의 값을 큰(넓은) 타입에 담을 때 자바가 알아서 자동으로 해주는 변환이에요. 내가 아무것도 안 써도 되고, 값이 손실되지 않아요. 작은 그릇의 물을 큰 그릇에 붓는 거라 넘칠 일이 없거든요.

타입의 "크기 순서"는 대략 아래 방향이에요. 오른쪽으로 갈수록 더 넓은 타입이라, 이 방향으로는 자동 변환이 됩니다.

🐣
byte / short
아주 작은 정수
🔢
int
기본 정수
📏
long
큰 정수
🌊
float
실수
🐳
double
가장 넓은 실수

예를 들어 int 값을 double 변수에 대입하면, 자바가 자동으로 double로 바꿔서 담아 줘요. 굳이 (double)을 붙이지 않아도 됩니다.

int n = 10;
double d = n;        // int → double 자동 변환. d 는 10.0
System.out.println(d);   // 10.0  (정수 10이 실수 10.0으로)

double d2 = 10;      // 리터럴 10(int)도 자동으로 10.0 이 됨
long big = 100;      // int 100 → long 으로 자동 변환

float f = 5;         // int → float 자동 변환. f 는 5.0
double d3 = f;       // float → double 자동 변환 (더 넓은 실수로)
🔤
용어 — 리터럴(literal) 코드에 직접 적어 넣은 값 그 자체를 말해요. 10, 3.9, 'A'처럼요. 소수점이 없는 10int 리터럴, 10.0double 리터럴로 취급돼요. 위 double d2 = 10;에서 10이 바로 int 리터럴이고, 이게 자동으로 10.0이 되는 거예요.
💧 왜 값이 안 사라질까? 정수 10을 실수 10.0으로 바꿔도 값 자체는 그대로예요. 오히려 표현할 수 있는 범위가 더 넓어졌죠. 작은 컵의 물을 큰 컵에 부으면 물의 양은 안 변하고, 컵만 커진 것과 같아요. 그래서 자동 형변환은 안전(safe)합니다. 자바가 굳이 나에게 확인받지 않고 알아서 처리해 주는 이유예요.
💡
한 줄 정리. 작은 타입 → 큰 타입(예: intdouble)은 자동, 값 손실 없음. 아무것도 안 써도 됩니다.
⬇️
강제 캐스팅
큰 타입 → 작은 타입 (명시적)
넘칠 수 있으니 "내가 책임질게"라고 직접 선언해요.

강제 형변환(명시적 캐스팅, explicit casting)큰(넓은) 타입의 값을 작은(좁은) 타입에 담을 때 쓰는 방법이에요. 큰 컵의 물을 작은 컵에 부으면 넘칠 수 있죠? 그래서 자바는 자동으로 해주지 않고, 내가 변수 앞에 (바꿀타입)을 붙여서 "손실이 나도 괜찮아, 강제로 바꿀게"라고 직접 선언하게 합니다.

double d = 3.9;
int i = (int) d;     // double → int 강제 캐스팅
System.out.println(i);   // 3   ← 소수점 아래(.9)는 반올림이 아니라 그냥 "버림"!

int x = (int) 3.99;  // 3   (0.99 도 무조건 버림)
int y = (int) 9.0;   // 9
⚠️
반올림이 아니라 "버림(절삭)"이에요. (int)3.94가 아니라 3입니다. 소수점 아래를 그냥 잘라내는 것이지 반올림하는 게 아니에요. 이건 초보자가 가장 많이 헷갈리는 부분이니 꼭 기억하세요.

강제 캐스팅에는 두 가지 손실이 생길 수 있어요. 하나는 방금 본 소수점 버림이고, 다른 하나는 값이 작은 타입의 표현 범위를 넘어설 때 생기는 오버플로우(overflow)예요.

// (1) 소수점 손실
int a = (int) 12.75;   // 12  (.75 버려짐)

// (2) 오버플로우 — 범위를 벗어나면 "엉뚱한 값"이 됨
// byte 는 -128 ~ 127 까지만 표현 가능한데, 300은 그 범위를 넘음!
byte b = (byte) 300;   // 예상: 300  →  실제: 44  (완전히 다른 값!)
System.out.println(b); // 44
🔁 오버플로우는 왜 44가 될까? (개념만) byte-128 ~ 127까지 256칸만 표현할 수 있어요. 여기에 300을 억지로 넣으면, 마치 시계 바늘이 12를 넘으면 다시 1로 돌아가듯 값이 한 바퀴 돌아 엉뚱한 숫자(44)가 돼요. (300 - 256 = 44) 💡 핵심은 "정확한 계산"이 아니라, 작은 타입에 큰 값을 강제로 넣으면 값이 완전히 망가질 수 있다는 감각이에요. 그래서 강제 캐스팅은 "내가 범위를 확인했다"는 책임이 따라옵니다.
🚨
강제 캐스팅은 "위험을 감수하겠다는 선언"이에요. 자바가 자동으로 안 해주는 이유가 바로 이 손실·오버플로우 때문이에요. (타입)을 붙였다면, "값이 잘려도 괜찮다"는 걸 내가 확인했다는 뜻이 됩니다.
🔎 번외 — 캐스팅은 "값"을 바꿀 뿐, "원본 변수"는 안 바뀐다
int i = (int) d; 를 실행해도 원래의 d(double)는 그대로 3.9예요. 캐스팅은 그 순간 계산에 쓸 임시 값을 만들어 i에 담는 것이지, d 자체를 정수로 바꿔치기하는 게 아니에요.
double d = 3.9;
int i = (int) d;
System.out.println(i);   // 3   (i 에 담긴 값)
System.out.println(d);   // 3.9 (d 는 그대로!)
비유하자면, 큰 컵(d)의 물을 복사해서 작은 컵(i)에 옮겨 담은 거예요. 원본 컵은 여전히 가득 차 있죠.
연산의 함정
계산할 때 몰래 일어나는 형변환
초보자가 가장 자주 당하는 곳. 특히 나눗셈을 조심해요.

형변환은 대입할 때만이 아니라 계산(연산)할 때도 몰래 일어나요. 그런데 이걸 모르면 "분명히 맞게 짰는데 결과가 이상한" 일을 겪게 돼요. 대표적인 게 정수 나눗셈이에요.

🧮 규칙 ① — 정수 ÷ 정수 = 정수 (소수점 버림) 자바에서 정수끼리(int, int) 나누면 결과도 정수가 돼요. 소수점 아래는 그냥 버려집니다. 5 / 2는 우리가 기대하는 2.5가 아니라 2예요!
int a = 5 / 2;
System.out.println(a);       // 2    ← 2.5 가 아님! (정수÷정수=정수, .5 버림)

System.out.println(7 / 2);   // 3    (3.5 → 3)
System.out.println(10 / 3);  // 3    (3.33... → 3)
System.out.println(1 / 2);   // 0    (0.5 → 0)  ← 특히 조심!

그럼 2.5라는 실수 결과를 얻으려면 어떻게 할까요? 계산에 참여하는 둘 중 하나 이상을 실수(double)로 만들어 주면 돼요. 방법은 두 가지예요.

✅ 규칙 ② — 실수 결과를 얻는 두 가지 방법 방법 A. 한쪽을 강제 캐스팅(double)5 / 2 처럼 하나를 double로 바꾸면, 다른 한쪽도 자동으로 double로 승격돼서 실수 나눗셈이 됩니다.
방법 B. 리터럴을 실수로 쓰기 — 숫자 뒤에 .0을 붙여 5.0 / 2 처럼 처음부터 double로 쓰면 돼요. 💡 (double)나누기 전에 한쪽 타입을 바꾸는 거예요. (double)(5/2)처럼 괄호를 통째로 씌우면 너무 늦어요 — 이미 5/2가 정수 2로 계산된 뒤라 결과는 2.0이 됩니다.
System.out.println((double) 5 / 2);   // 2.5   ✅ 나누기 전에 5를 double로
System.out.println(5.0 / 2);          // 2.5   ✅ 5.0 은 처음부터 double
System.out.println(5 / 2.0);          // 2.5   ✅ 한쪽만 double이어도 됨

System.out.println((double)(5 / 2));  // 2.0   ❌ 이미 5/2=2 된 뒤라 소용없음
System.out.println(5 / 2);            // 2     ❌ 둘 다 정수라 정수 나눗셈
🧬 규칙 ③ — 타입이 섞이면 "더 큰 타입"으로 자동 승격 계산에서 서로 다른 타입이 만나면, 자바는 둘 중 더 넓은 타입으로 값을 자동으로 맞춘 뒤 계산해요. 이걸 자동 승격(promotion)이라고 해요.
예를 들어 int + doubleintdouble로 승격돼서, 결과도 double이 됩니다.
int i = 3;
double d = 2.0;
System.out.println(i + d);     // 5.0   (int 3 → double 3.0 으로 승격 후 계산)

System.out.println(10 + 3.5);  // 13.5  (int + double → double)
long L = 5;
System.out.println(L + 2);     // 7     (long + int → long, 결과 7)

마지막으로 자주 나오는 특별한 짝, charint예요. 자바에서 문자는 사실 내부적으로 숫자(유니코드 번호)로 저장돼요. 그래서 charint와 자유롭게 넘나들 수 있어요.

🔤
유니코드 · 아스키(Unicode · ASCII) — 컴퓨터가 문자를 저장하려고 글자마다 붙여 둔 고유 번호표예요. 예전엔 영어·기호 위주의 아스키(0~127번)를 썼고, 지금은 한글·이모지까지 아우르는 유니코드로 넓어졌어요. 자바의 char는 문자를 이 번호로 저장하기 때문에, 'A'의 정체가 곧 숫자 65인 거예요.
🔤 규칙 ④ — char 는 사실 숫자다 ('A' == 65) 문자 'A'는 내부적으로 숫자 65예요(유니코드/아스키). 'B'는 66, 'C'는 67 … 이렇게 이어져요.
그래서 charint와 계산하면 숫자로 취급되고, 반대로 숫자를 (char)로 캐스팅하면 그 번호의 문자가 나와요. 💡 'A' + 1은 문자열 "A1"이 아니라 정수 66이에요(65+1). 다시 문자 'B'로 보려면 (char)('A' + 1)처럼 char로 캐스팅해야 해요.
System.out.println('A' == 65);        // true   ('A' 의 정체는 65)
System.out.println('A' + 1);          // 66     (65 + 1, 정수로 계산됨!)
System.out.println((char) 65);        // A      (65번 문자 → 'A')
System.out.println((char)('A' + 1));  // B      (66 → 'B')

char c = 'A';
int code = c;                         // char → int 자동 변환. code 는 65
⚠️
가장 많이 틀리는 3종 세트.5/2 == 2(정수 나눗셈), ② (int)3.9 == 3(반올림 아님, 버림), ③ 'A'+1 == 66(문자가 아니라 정수). 이 셋만 확실히 잡아도 실수를 크게 줄일 수 있어요.
📋
정리 표
자동 vs 강제, 한눈에 비교
언제 손실이 나는지까지 표로 못 박아 둘게요.
구분자동(묵시적) 형변환강제(명시적) 캐스팅
방향작은 타입 → 큰 타입
(예: intdouble)
큰 타입 → 작은 타입
(예: doubleint)
쓰는 법아무것도 안 씀 (자바가 알아서)변수 앞에 (타입) 을 붙임
값 손실없음 (안전) 🟢있을 수 있음 (소수점 버림·오버플로우) 🔴
예시double d = 10; → 10.0int i = (int)3.9; → 3
비유작은 컵 → 큰 컵 (안 넘침)큰 컵 → 작은 컵 (넘칠 수 있음)
상황결과이유 / 손실 여부
double d = 10;10.0int→double 자동, 손실 없음
int i = (int)3.9;3강제, 소수점 버림(손실)
(byte)30044강제, 범위 초과 → 오버플로우
5 / 22정수÷정수=정수, .5 버림
(double)5 / 22.5한쪽 double 승격 → 실수 나눗셈
5.0 / 22.5double 리터럴로 실수 나눗셈
'A' + 166char→int(65), 정수로 계산
(char)65'A'int→char, 65번 문자
💡
표 한 장으로 외우기. "올라갈 땐 자동·안전, 내려갈 땐 강제·위험." 그리고 계산할 땐 "정수끼리는 정수, 하나라도 실수면 실수." 이 두 문장이 이번 장의 전부예요.
🧠 이 장 핵심 요약
📝
문제풀이 · 점검
배운 걸 가볍게 점검해봐요
시험이 아니라 "내가 이해했나" 확인용이에요. 틀려도 바로 해설이 나와요.
🧪
문제를 풀면 즉시 정답과 해설이 나오고, 위쪽 바에 점수가 쌓여요. 네 가지 유형(객관식 · O/X · 빈칸 · 코드결과)을 섞어 두었어요. 편하게 눌러보세요.
CH 05 ➕ 연산자 — 산술·비교·논리·대입 연산자와 우선순위를 한 번에 정리하기