타입 변환이란 데이터 타입을 다른 데이터 타입으로 변환하는 것을 말한다. 예를 들어 byte 타입을 int 타입으로 변환하거나 반대로 int 타입을 byte 타입으로 변환하는 행위를 말한다. 타입 변환에는 두 가지 종류가 있다. 하나는 자동(묵시적) 타입 변환이고 다른 하나는 강제(명시적) 타입 변환이다.


2.3.1 자동 타입 변환

자동 타입 변환(Promotion)은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다. 자동 타입 변환은 작은 크기를 가지는 타입이 큰 크기를 가지는 타입에 저장될 때 발생한다.


큰 크기 타입과 작은 크기 타입의 구분은 사용하는 메모리 크기이다. 예를 들어 byte 타입은 1byte 크기를 가지고, int 타입은 4byte 크기를 가지므로 int 타입이 큰 크기 타입이고, byte 타입이 작은 크기 타입이다. 크기별로 타입을 정리하면 다음과 같다.


byte(1) < short(2) < int(4) < long(8) < float(4) < double(8)


float은 4byte 크기인데 int(4byte)와 long(8byte)보다 더 큰 타입으로 표시했다. 그 이유는 표현할 수 있는 값의 범위가 float이 더 크기 때문이다. 이것만 주의하면 데이터 타입의 크기를 비교하는 것은 그리 어렵지 않을 것이다. 다음 코드를 보자.

byte byteValue = 10;

int intValue = byteValue;    //자동 타입 변환이 일어난다.


byteValue는 byte 타입 변수이므로 1byte 크기를 가진다. 그리고 intValue는 int 타입 변수이므로 4byte 크기를 가진다. 따라서 byte타입 byteValue는 int타입 intValue로 자동 타입 변환된다. 메모리에서 값이 복사되는 모양을 그림으로 표현하면 다음과 같다.



int타입(4byte)




자동 타입 변환

byte타입(1byte)

00000000

00000000

00000000

00000000


00001010


자동 타입 변환이 발생되면 변환 이전의 값과 변환 이후의 값은 동일하다. 즉, 변환 이전의 값은 변환 이후에도 손실 없이 그대로 보존된다. 이것은 작은 그릇의 물을 큰 그릇으로 옮기더라도 물의 양은 변하지 않는다는 것과 유사하다. 정수 타입이 실수 타입으로 변환하는 것은 무조건 자동 타입 변환이 된다. 실수 타입으로 변환된 이후의 값은 정수값이 아닌 .0이 붙은 실수값이 된다. 다음 코드에서 intValue가 doubleValue에 저장되면 200은 200.0으로 저장된다.


int intValue = 200;

double doubleValue = intValue;    //200.0


char 타입의 경우 int 타입으로 자동 변환되면 유니코드 값이 int 타입에 저장된다.


char charValue = 'A';

int intValue = charValue;    //65가 저장


자동 타입 변환에서 단 하나의 예외가 있는데, char가 2byte의 크기를 가지지만, char의 범위는 0~65535이므로 음수가 저장될 수 없다. 따라서 음수가 저장될 수 있는 byte타입을 char타입으로 자동 변환시킬 수 없다.


byte byteValue = 65;

char charValue = byteValue; (x)        //컴파일 에러

char charData = (char)byteData; (o)    //강제 타입 변환

다음은 자동 타입 변환이 생기는 다양한 코드들이다.




2.3.2 강제 타입 변환

큰 크기의 타입은 작은 크기의 타입으로 자동 타입 변환을 할 수 없다. 예를 들어, 4byte인 int타입을 1byte인 byte 타입에 담을 수 없다. 마치 큰 그릇의 물을 작은 그릇 안에 모두 넣을 수 없는 것과 동일한 이치이다. 하지만 큰 그릇을 작은 그릇 사이즈로 쪼개어서 한 조각만 작은 그릇에 넣는다면 가능하다. 즉 int 타입을 4개의 byte로 쪼갠 다음, 끝에 있는 1byte만 byte 타입 변수에 저장하는 것은 가능하다. 이와 같이 강제적으로 큰 데이터 타입을 작은 데이터 타입으로 쪼개어서 저장하는 것을 강제 타입 변환(캐스팅: Casting)이라고 한다. 강제 타입 변환은 캐스팅 연산자 ()를 사용하는데, 괄호 안에 들어가는 타입은 쪼개는 단위이다.


강제 타입 변환

  ←

작은 크기 타입 = (작은 크기 타입) 큰 크기 타입


다음 코드를 보자. int 타입 intValue 변수는 4byte이므로 1byte 크기를 가지는 byte 타입 byteValue 변수에 저장할 수 없다. 그래서 강제적으로 (byte) 캐스팅 연산자를 사용해서 int 타입 intValue를 1byte씩 쪼개고, 끝에 있는 1byte만 byteValue 변수에 저장한다.


int intValue = 103029770;

byte byteValue = (byte) intValue;        //강제 타입 변환(캐스팅)


끝 1byte만 byte 타입 변수에 담게 되므로 원래 int 값은 보존되지 않는다. 하지만 int 값이 끝 1byte로만 표현이 가능하다면 byte 타입으로 변환해도 같은 값이 유지될 수 있다. 이럴 경우 강제 타입 변환이 의미 있게 된다. 예를 들어 int 타입 변수에 10을 저장할 경우, 4byte 중 끝 1byte로 10을 충분히 표현할 수 있으므로 앞 3byte는 모두 0으로 채워진다. 이것을 byte 타입으로 강제 타입 변환할 경우 앞의 3byte는 버려지고 끝 1byte만 byte 타입 변수에 저장되기 때문에 10이 그대로 유지된다.


다른 예로 long 타입 변수에 300이 저장되어 있을 경우, 8byte 중 끝의 4byte로 300을 충분히 표현 할 수 있으므로 이것을 int 타입으로 강제 타입 변환하면 앞의 4byte는 버려지고 끝의 4byte만 int 타입 변수에 저장되어 300이 그대로 유지된다.


long longValue = 300;

int intValue = (int) longValue;        //intValue는 300이 그대로 저장된다.


int 타입은 char 타입으로 자동 변환되지 않기 때문에 강제 타입 변환을 사용해야 한다. int 타입에 저장된 값이 유니코드 범위(0~65535)라면 다음과 같이 (char) 캐스팅 연사자를 사용해서 char 타입으로 변환할 수 있다. char 타입으로 변환된 값을 출력하면 유니코드에 해당하는 문자가 출력된다.


int intValue = 'A';

char charValue = (char) intValue;

System.out.println(charValue);


실수 타입(float, double)은 정수 타입(byte, short, int, long)으로 자동 변환되지 않기 때문에 강제 타입 변환을 사용해야 한다. 이 경우 소수점 이하부분은 버려지고, 정수 부분만 저장된다.


double doubleValue = 3.14;

int intValue = (int) doubleValue;        //intValue는 정수 부분인 3만 저장된다.



강제 타입 변환에서 주의할 점은 사용자로부터 입력받은 값을 변환할 때 값의 손실이 발생하면 안된다는 것이다. 강제 타입 변환을 하기 전에 우선 안전하게 값이 보존될 수 있는지 검사하는 것이 좋다. 아래 예제는 byte 타입으로 변환하기 전에 변환될 값이 byte 타입으로 변환된 후에도 값의 손실이 발생하지 않는지 검사해서 올바른 타입 변환이 되도록 한다.



8라인에서 사용된 연산식과 if문은 3장과 4장을 학습하면 자연스럽게 알게 되므로 여기서는 변수 i의 값이 byte 타입의 최소값보다 작은지, 최대값보다 큰지를 조사해서 하나라도 해당이 된다면 9~10라인을 실행시키고, 그렇지 않을 경우 12~13라인을 실행시킨다는 것만 알아두자. i는 128이므로 Byte.MAX_VALUE인 127보다 크기 때문에 if문의 조건식이 true가 되어 6~7라인만 실행된다. 자바는 코드에서 데이터 값을 검사하기 위해 boolean과 char 타입을 제외하고 모든 기본 타엡 대해 최대값(max)과 최소값(min)을 다음과 같이 상수로 제공하고 있다. 어떤 정수값과 실수값을 다른 타입으로 변환하고자 할 때는 변환될 타입의 최소값과 최대값을 벗어나는지 반드시 검사하고, 만약 벗어난다면 타입 변환을 하지 말아야 한다.


기본 타입

최대값 상수

최소값 상수

byte

Byte.MAX_VALUE

Byte.MIN_VALUE

short

Short.MAX_VALUE

Short.MIN_VALUE

int

Integer.MAX_VALUE

Integer.MIN_VALUE

long

Long.MAX_VALUE

Long.MIN_VALUE

float

Float.MAX_VALUE

Float.MIN_VALUE

double

Double.MAX_VALUE

Double.MIN_VALUE


강제 타입 변환에서 또 다른 주의점이 있다. 정수 타입을 실수 타입으로 변환할 때 정밀도 손실을 피해야 한다. 다음 예제를 보자. int 타입 변수 num1과 num2에 동일한 123456780 값을 저장시키고, num2를 float 타입으로 변환시킨 후, 다시 int 타입으로 변환해서 num2에 저장한다. 그리고 num1에서 num2를 뺀 결과를 변수 result에 저장하고 콘솔에 출력한다. 동일한 값을 뺐기 때문에 당연히 0이 출력되는 것이 맞다.



그러나 실행 결과를 보면 엉뚱하게도 0이 나오질 않는다. 이러한 결과가 나온 이유는 int 값을 floatt 타입으로 자동 변환하면서 문제가 발생했기 때문이다. float 타입은 다음과 같이 비트 수가 할당되어 있다.


float: 부호(1비트) + 지수(8비트) + 가수(23비트)


int 값을 손실 없이 float 타입의 값으로 변환할 수 있으려면 가수 23비트로 표현 가능한 값이어야 한다. 123456780은 23비트로 표현할 수 없기 때문에 근사치로 변환된다. 즉 정밀도 손실이 발생한다. 그렇기 때문에 float 값을 다시 int 타입으로 변환하면, 원래의 int 값을 얻지 못한다. 따라서 12라인에서 num1과 num2는 동일한 값이 아니다. 해결책은 모든 int 값을 실수 타입으로 안전하게 변환시키는 double 타입을 사용하는 것이다. double 타입은 다음과 같이 비트 수가 할당되어 있다.


double: 부호(1비트) + 지수(11비트) + 가수(52비트)


int의 크기는 32비트이므로 double의 가수 52비트보다는 작기 때문에 어떠한 int 값이라도 안전하게 정밀도 손실 없이 double 타입으로 변환될 수 있다. 그래서 double 값을 원래 int 타입으로 변환해도 손실 없이 복원된다. 이전 예제의 9라인을 수정하고 다시 실행하면 다음과 같다.




2.3.3 연산식에서의 자동 타입 변환

연산은 기본적으로 같은 타입의 피연산자(operand) 간에만 수행되기 때문에 서로 다른 타입의 피연산자가 있을 경우 두 피연산자 중 크기가 큰 타입으로 자동 변환된 후 연산을 수행한다. 예를 들어 int 타입 피연산자와 double 타입 피연산자를 덧셈 연산하면 먼저 int 타입 피연산자가 double 타입으로 자동 변환되고 연산을 수행한다. 당연히 연산의 결과는 double이 된다.


int intValue = 10;

double doubleValue = 5.5;

double result = intValue + doubleValue;        //result에 15.5가 저장

→ double 값으로 변환


만약 int 타입으로 꼭 연산을 해야 한다면 double 타입을 int 타입으로 강제 변환하고 덧셈 연산을 수행하면 된다.


int intValue = 10;

double doubleValue = 5.5;

int result = intValue +(int)doubleValue;        //result에 15가 저장


자바는 정수 연산일 경우 int 타입을 기본으로 한다. 그 이유는 피연산자를 4byte 단위로 저장하기 때문에 크기가 4byte보다 작은 타입(byte, char, short)은 4byte인 int 타입으로 변환된 후 연산이 수행된다. 따라서 연산의 결과도 int 타입이 된다.


int result =

byte 타입

char 타입

short 타입

int 타입

연산자(+, -, *, /, %)

byte 타입

char 타입

short 타입

int 타입


예를 들어 char 타입의 연산 결과는 int 타입으로 산출되므로 int 타입 변수에 결과를 저장해야 한다. 연산의 결과를 다시 문자로 출력하거나 저장하기 위해서는 int 결과값을 char 타입으로 강제 변환(casting)해야 한다.


char ai = 'A';

int result = ai + 1;        //'A'의 유니코드보다 1이 큰 유니코드가 저장

char na = (char)result;   //'B'가 저장됨


만약 피연산자 중 하나가 long 타입이라면 다른 피연산자도 long 타입으로 자동 타입 변환되고 연산의 결과는 long 타입이 된다.


long result

long 변수

연산자(+, -, *, /, %) 

byte 타입

char 타입

short 타입

int 타입


float 타입과 float 타입을 연산하면 연산의 결과는 float 타입으로 나오지만, 피연산자 중에 실수 리터럴이나 double 타입이 있다면 다른 피연산자도 double 타입으로 자동 타입 변환되어 연산되므로 결과는 double 타입으로 산출된다.


double result

실수 리터럴

double 변수

연산자(+, -, *, /, %)

byte 타입

char 타입

short 타입

int 타입

float 타입

double 타입



+ Recent posts