타입 변환이란 데이터 타입을 다른 데이터 타입으로 변환하는 것을 말한다. 예를들어 byte 타입을 int 타입으로 변환하거나 반대로 int 타입을 byte 타입으로 변환하는 행위를 말한다. 타입 변환에는 두가지 종류가 있다. 하나는 자동 (묵시적) 타입 변환이고 다른 하나는 강제 (명시적) 타입 변환이다.
1. 자동 타입 변환
자동 타입 변환(Promotion) 은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것이다.
float은 4byte 크기이지만 int와 long 보다 큰 타입으로 표시되는데 그이유는 표현할수 있는 값의 범위가 float이 더 크기 때문이다.
이것만 주의하면 데이터 타입의 크기를 비교하는것은 그리 어렵지않다. 다음코드를보자
-------------------------------------
byte byteValue = 10;
int intValue = byteValue; //자동 타입 변환이 일어난다.
-------------------------------------
byteValue는 byte타입 변수이므로 1byte 크기를 가진다. 그리고 intValue는 int 타입 변수이므로 4byte 크기를 가진다. 따라서 byte 타입 byteValue는 int 타입 intValue로 자동 타입 변환된다. 메모리에서 값이 복사되는 모양을 표현하면 다음과 같다.
int 타입 (4byte) 자동타입변환 byte타입 (1byte)
00000000 00000000 00000000 00001010 <= 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번에서 설명)
-----------------------------------------------------------------------------
다음은 자동 타입 변환이 생기는 다양한 코드들이다.
----------------------------------[ PromotionExample.java ] 자동 타입 변환 --------------------------------------------------
public class PromotionExample {
public static void main(String[] args) {
byte byteValue = 10;
int intValue = byteValue;
System.out.println(intValue);
char charValue = '가';
intValue = charValue;
System.out.println("가의 유니코드=" + intValue);
intValue = 500;
long longValue = intValue;;
System.out.println(longValue);
intValue = 200;
double doubleValue = intValue;
System.out.println(doubleValue);
}
}
2. 강제 타입 변환
큰 크기의 타입은 작은 크기의 타입으로 자동 타입 변환을 할 수 없다. 예를들어 4byte인 int 타입을 1byte인 byte타입에 담을 수 없다. 마치 큰그릇의 물을 작은 그릇안에 모두 넣을 수 없는것과 동일한 이치이다. 하지만 큰그릇을 작은 그릇사이즈로 쪼개어서 한조각만 작을그릇에 넣는다면 가능하다. 즉 int 타입을 4개의 byte로 쪼갠다음, 끝에 있는 1byte만 byte 타입 변수에 저장하는 것은 가능하다. 이와 같이 강제적으로 큰 데이터 타입을 작은 데이터 타입으로 쪼개어서 저장하는 것을 강제 타입 변환(캐스팅 : Casting) 이라고 한다. 강제 타입 변환은 캐스팅 연산자 () 을 사용하는데, 괄호 안에 들어가는 타입은 쪼개는 단위이다.
다음 코드를 보면 int 타입 intValue 변수는 4 byte이므로 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이 그대로 유지된다.
-----------------------------------------------------------------------------------------------
int intValue = 10;
byte byteValue = (byte) intValue; //byteValue 는 10이 그대로 저장된다.
-----------------------------------------------------------------------------------------------
다른예로 long 타입 변수에 300이 저장되어 있을 경우, 8byte 중 끝의 4 byte로 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 = 65;
char charValue = (char) intValue;
System.out.printIn(charValue); /////// A
-----------------------------------------------------------------------------------------------
실수 타입(float, double) 은 정수 타입 (byte, short, int, long) 으로 자동 변환되지 않기 떄문에 강제 타입 변환을 사용해야 한다. 이 경우 소수점 이하 부분은 버려지고, 정수 부분만 저장된다.
-----------------------------------------------------------------------------------------------
double doubleValue = 3.14;
int intValue = (int) doubleValue; // intValue는 정수부분인 3만 저장되게 된다.
------------------------------------------------------------------------------------------------
------------------------[ CastingExample.java ] 강제 타입 변환------------------------------------------------------
public class CastingExample {
public static void main(String[] args) {
int intValue = 44032;
char charValue = (char) intValue;
System.out.println(charValue);
long longValue = 500;
intValue = (int) longValue;
System.out.println(intValue);
double doubleValue = 3.14;
intValue = (int) doubleValue;
System.out.println(intValue);
}
}
Console
가
500
3
강제 타입 변환을 하는데 여러가지 이유가 있겠지만 일반적인이유가 값의 손실이 발생하지않고 낮은크기의 타입변수로 바꾸기 위해서인데 값이 손실이되면 의미가 없어지기 떄문에 강제 타입변환을 하는 이유가 없어진다. 그렇기때문에 우선 안전하게 값이 보존될 수 있는지 검사하는 것이 좋다.
아래 예제는 byte 타입으로 변환하기 전에 변환될 값이 byte 타입으로 변환된 후에도 값의 손실이 발생하지 않는지 검새해서 올바른 타입 변환이 되도록 하는 예제이다.
--------------------------[ CheckValueBeforeCasting.java ] 변환으로 인한 데이터 손실이 발생되지 않도록 한다.----------------------------------------
public class CheckValueBeforeCasting {
public static void main(String[] args) {
int i = 128;
if( (i<Byte.MIN_VALUE) || (i>Byte.MAX_VALUE) ) { // if( (i<-128)||(i>127) ) { 과 동일하다.
System.out.println("byte 타입으로 변환할 수 없습니다.");
System.out.println("값을 다시 확인해 주세요");
} else {
byte b = (byte) i;
System.out.println(b);
}
}
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Console
byte 타입으로 변환할 수 없습니다.
값을 다시 확인해 주세요
//int i 변수에 -128~ 127 정수가들어가면 그 정수가 출력된다.
----------------------------------------------------------------------------------------------------------------------------------------------------------------
5라인에 사용된 if문은 3장과 4장을 학습하면 자연스럽게 알게 되므로 여기서는 변수 i의 값이 byte 타입의 최소값보다 작은지, 최대값보다 큰지를 조사해서 하나라도 해당이 된다면 6~7라인을 실행시키고, 그렇지 않을 경우 9~10 라인을 실행시킨다는것만 알아두면 된다.
i는 128 이므로 Byte.Max_VALUE 인 127 보다 크기 떄문에 if 문의 조건식이 ture 가 되어 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 이 출력이 되어야 제대로 변환이 된 것이다.
--------------------------[ FromIntToFloat.java ] 정수 타입을 실수 타입으로 변환할 때 정밀도 손실을 피한다. ----------------------
public class FromIntToFloat {
public static void main(String[] args) {
int num1 = 123456780;
int num2 = 123456780;
float num3 = num2;
num2 = (int) num3;
int result = num1 - num2;
System.out.println(result);
}
}
---------------------------------------------------------------------------------------------------------------------------------------------
Console
-4
---------------------------------------------------------------------------------------------------------------------------------------------
그러나 실행 결과를 보면 엉뚱하게도 0 이나오지 않는다. 이러한 결과가 나온 이유는 6라인인데 int 값을 float 타입으로 자동 변환하면서 문제가 발생했기 때문이다. float 타입은 다음과 같이 비트수가 할당되어 있기 때문이다
float : 부호(1bit) + 지수(8bit) + 가수(23bit)
int 값을 손실 없이 float 타입의 값으로 변환할 수 있으려면 가수 23 비트로 표현 가능한 값이어야 한다. 123456780은 23 비트로 표현할 수 없기 때문에 근사치로 변환된다. 즉 정밀도 손실이 발생한다는 것이다. 그렇기 떄문에 float 값을 다시 int 타입으로 변환하면, 원래의 int 값을 얻지 못한다. 따라서 9라인에서 num1 과 num2 는 동일한 값이 아니다. 해결책은 모든 int 값을 실수타입으로 안전하게 변환시키는 double 타입을 사용하는 것이다. double 타입은 다음과 같이 비트 수가 할당되어 있다.
double : 부호(1bit) + 지수(11bit) +가수(52)
int의 크기는 32 비트이므로 double의 가수 52 비트보다는 작기 때문에 어떠한 int 값이라도 안전하게 정밀도 손실 없이 double 타입으로 면환될 수 있다. 그래서 double 값을 원래 int 타입으로 변환해도 손실 없이 복원된다. 이전 예제의 6라인을 수정하고 다시 실행하면 다음과 같다.
--------------------------[ FromIntToDouble.java ] 정수 타입을 실수 타입으로 변환할 때 정밀도 손실을 피한다. ----------------------
public class FromIntToDouble {
public static void main(String[] args) {
int num1 = 123456780;
int num2 = 123456780;
double num3 = num2;
num2 = (int) num3;
int result = num1 - num2;
System.out.println(result);
}
}
---------------------------------------------------------------------------------------------------------------------------------------------
Console
0
---------------------------------------------------------------------------------------------------------------------------------------------
3. 연산식에서의 자동 타입 변환
연산은 기본적으로 같은 타입의 피 연산자(operand) 간에만 수행되기 떄문에 서로 다른 타입의 피 연산자가 있을 경우 두 피 연산자중 크기가 큰 타입으로 자동 변환된 후 연산을 수행한다.
예를들어 int 타입 피연산자와 double 타입 피연산자를 덧셈 연산하면 먼저 int 타입 피연산자가 double 타입으로 자동 변환되고 연산을 수행한다. 당연히 연산의 결과도 double타입이 된다.
--------------------------------------------------------------------------------------
int intValue = 10;
double doubleValue = 5.5;
double result = intValue + doubleValue ; // result에 15.5 가 저장
--------------------------------------------------------------------------------------
만약 꼭 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 타입이 된다.
예를들어 char 타입의 연산 결과는 int 타입으로 산출되므로 int 타입변수에 결과를 저장해야 한다. 연산의 결과를 다시 문자로 출력하거나 저장하기 위해서는 int 결과값을 char 타입으로 강제 변환 (casting) 해야 한다.
---------------------------------------------------------------------------------------------------
char ai = 'A';
int result = ai + 1; //'A'의 유니코드보다 1이 큰 유니코드가 저장
char na = (char) result ; //'B'가 저장됨
-------------------------------------------------------------------------------------------------
만약 피연산자중 하나가 long 타입이라면 다른 피연산자도 long 타입으로 자동 타입 변환되고 연산의 결과는 long 타입이 된다.
float타입과 float타입을 연산하면 결과는 float 타입으로 나오지만 , 피연산자중에 실수 리터럴이나 double타입이 있다면 다른 피연산자도 double타입으로 자동 타입 변환되어 연산되므로 결과는 double 타입으로 산출된다.
------------------------------[ OperationsPromotionExample.java ] 연산식에서 자동 타입 변환 -----------------------------------------------------------
public class OperationsPromotionExample {
public static void main(String[] args) {
byte byteValue1 = 10;
byte byteValue2 = 20;
//byte byteValue3 = byteValue1 + byteValue2; //컴파일 에러
int intValue1 = byteValue1 + byteValue2;
System.out.println(intValue1);
char charValue1 = 'A';
char charValue2 = 1;
//char charValue3 = charValue1 + charValue2; //컴파일 에러
int intValue2 = charValue1 + charValue2;
System.out.println("유니코드=" + intValue2);
System.out.println("출력문자=" + (char)intValue2);
int intValue3 = 10;
int intValue4 = intValue3/4;
System.out.println(intValue4);
int intValue5 = 10;
//int intValue6 = 10 / 4.0; //컴파일 에러
double doubleValue = intValue5 / 4.0;
System.out.println(doubleValue);
}
}
Console
30
유니코드=66
출력문자=B
2
2.5
------------------------------------------------------------------------------------------------------------------------------------------------
'legacy' 카테고리의 다른 글
이것이java다 2.1 변수 (0) | 2023.04.16 |
---|---|
이것이java다 2.2 데이터 타입 (0) | 2023.04.16 |
이것이java다 2. 확인문제 풀이 (0) | 2023.04.16 |
이것이java다 3.1 연산자와 연산식 (0) | 2023.04.16 |
이것이java다 3.2 연산의 방향과 우선순위 (0) | 2023.04.16 |