모든 변수와 리터럴에는 타입이 존재하며, 리터럴은 타입이 있어야 값의 의미를 알 수 있다는 것도 배웠다.
프로그램을 작성하다보면, 서로 다른 타입 간의 연산을 진행
하는 일이 있는데,
이럴 때는 연산하기 전에 두 타입을 일치시켜야만한다.
"변수"와 "상수", "리터럴"의 타입을 다른 타입으로 변환 시키는 것을 "형변환(캐스팅)"이라고 한다.
(변환하고자하는 타입) 변환되는 변수,리터럴
(int) 10.0;
캐스팅에서 사용되는 ()
를 "캐스팅 연산자"라고 한다.
double d = 85.4;
int score = (int) d;
//해당 문장은 다음 변환과정을 거친다.
int score = (int) d;
int score = (int) 85.4; // 변수에 저장된 값 변환
int score = 85; // double 타입의 리터럴을 int 타입의 리터럴로 캐스팅
int score = 85; // int 타입 리터럴 85 를 int 타입의 변수 score에 대입
이 과정에서 중요한 점은,
() 형변환 연산자
는 기존 값을 변화시키않고 변환 된 새로운 값만을 반환하는 연산자라는 것이다.
public static void main(String[] args) {
double d = 85.4;
int score = (int) d;
System.out.printf("score = %d%n",score);
System.out.printf("d= %f",d);
/**
score = 85
d= 85.400000
*/
}
기본형은 boolean 을 제외한 "숫자로 표현할 수 있는 7개의 타입"들 간의 형변환(캐스팅)
만 가능하다.
또한 기본형을 참조형으로 변환할 수 없으며, 참조형은 참조형 간의 변환만 가능하다.
//해당 코드는 박싱과 언박싱이지, 캐스팅이 아니다.
Integer i = (Integer)10; // boxing, new Integer(10);
int i1 = (int)i;//un-boxing, i.intValue();
이는 Integer 같은 Wrapper 들의 필드와 생성자를 열어보면 알 수 있다. String 이 내부에 charSequence 를 가진 것을 떠오르게 하는 것 같다.
정수형 간의 형변환은 표현할 수 있는 bit 의 수
에 따라 달라진다.
"표현할 수 있는 bit가 더 많으므로"
값의 손실이 발생하지않는다."표현할 수 있는 bit가 더 적어지므로"
값의 손실이 발생할 수 있다.여기서 발생할 수 있다. 는
변환되는 타입이 표현할 수 있는 값의 범위보다 크거나 작은 값의 경우
에 해당한다.
- 더 큰 타입이 더 작은 타입으로 캐스팅되는 경우
- 더 큰 타입의 값이 더 작은 타입이 표현할 수 있는 범위의 값일 경우 =>
값 손실이 발생하지 않음.
- 더 큰타입의 값의 더 작은 타입이 표현할 수 있는 범위의 값보다 작거나 클경우 =>
값 손실 발생.
- 더 작은 타입이 더 큰타입으로 프로모션 되는 경우
- 양수의 프로모션 =>
빈 bit 는 0
- 음수의 프로모션 =>
빈 bit 는 1
public static void main(String[] args) {
int i = 10;
byte b = (byte) i;
System.out.printf("[int -> byte] i=%d -> b=%d%n",i,b);
i = 300;
b = (byte) i;
System.out.printf("[int -> byte] i=%d -> b=%d%n",i,b);
b = 10;
i = b;
System.out.printf("[byte -> int] i=%d -> b=%d%n",i,b);
b = -2;
i = b;
System.out.printf("[byte -> int] i=%d -> b=%d%n",i,b);
System.out.println("i="+Integer.toBinaryString(i));
/**
[int -> byte] i=10 -> b=10
[int -> byte] i=300 -> b=44
[byte -> int] i=10 -> b=10
[byte -> int] i=-2 -> b=-2
i=11111111111111111111111111111110
*/
}
double 타입을 float 타입으로 형변환할 때, "float 타입이 표현할 수 있는 범위보다 큰 값을 변환"하면 Infinity 가 되고, "float 타입이 표현할 수 있는 범위보다 작은 값을 변환"하면 0이 된다.
public static void main(String[] args) {
float f = 9.1234567f;
double d = 9.1234567;
double d2 = (double) f;
System.out.printf("f = %20.18f\n",f); // 정밀도를 넘어섰으므로 오차 발생
System.out.printf("d = %20.18f\n",d); // 정밀도에 따라 값이 보존 됨
System.out.printf("d2 = %20.18f\n",d2); // 오차가 일어난 float 리터럴을 double 로 캐스팅했으므로, 값이 정밀도에 맞게 출력
System.out.println((float) d2); // 비트가 잘리고 반올림 발생
/**
* f = 9.123456954956055000
* d = 9.123456700000000000
* d2 = 9.123456954956055000
* 9.123457
*/
}
"정수형을 실수형으로 변환"할 경우 정수리터럴에 소수점이 붙게 되며, "실수형을 정수형으로 변환"하면 소수점 아래자리 수가 잘려나간다.
int 타입이 표현할 수 있는 자리수는 10자리(20억)
이다. 하지만float 타입의 정밀도는 7자리
이므로, 7자리가 넘는 정수를 float 타입으로 형변환하면, 오차가 발생한다.
public static void main(String[] args) {
int i = 91234567; // 8자리 정수
float f = (float) i; // float 타입의 정밀도 7 & 9123456오차.0f
int i2 = (int) f;// 소수점아래는 잘려나간다. 9123456오차
double d = (double) i; // double 타입의 정밀도 15 & 91234567.0
int i3 = (int) d; // 91234567
float f2 = 1.666f;
int i4 = (int) f2; // 같은 4byte 지만 float 타입이 표현할 수 있는 범위가 더 넓으므로 캐스팅 & 1
// 정수형을 실수형으로 형변환하면, 실수형의 정밀도만큼 정확하게 표현하고 정수 뒤에 소수점이 붙는다.
System.out.printf("i= %d\n",i);
System.out.printf("f= %f i2= %d\n",f,i2);
System.out.printf("d= %f i3=%d\n",d,i3);
System.out.printf("(int) %f = %d\n", f2, i4);
}
/**
i= 91234567
f= 91234568.000000 i2= 91234568
d= 91234567.000000 i3=91234567
(int) 1.666000 = 1
*/
서로 다른 타입 간의 "대입" 이나 "연산"을 할 때에 형변환으로 타입을 일치시키는 것이 원칙이다.
경우에 따라 형변환 연산자를 생략할 수 있는 경우가 있는데, 이 경우는 "자동 형변환"의 경우이다.
자동 형변환은, "컴파일러가 프로그래머 대신 캐스팅 연산자를 작성해주는 것"으로, 사실 상 형변환 연산자가 기술되는 것이다.
대입의 경우, 더 작은 타입이 더 큰 타입으로 대입될 때 자동 형변환[프로모션]이 일어난다.
float f = 1234; // 4byte 로 동일하지만, float 타입이 표현할 수 있는 범위가 더 넓으므로 자동 형변환[프로모션]이 일어난다.
더 큰 타입이 더 작은 타입으로 대입될 때는 "실수가 아니라 의도적인 변환"임을 컴파일러에게 알리기 위해서 명시적 형변환[캐스팅]을 해야한다.
char ch = (char)1000;// 4byte의 int 타입리터럴이 2byte의 char 변수에 대입되므로, 명시적 형변환[캐스팅]을 해야한다.
연산을 하기위해서는 피연산자 간의 타입이 일치해야한다. 그러므로, 연산 전에 "자동 형변환[프로모션]"
이 일어난다.
이를 "산술 변환"
이라고 하며, 산술 변환은 "연산 결과를 온전히 보존" 할 수 있도록 더 넓은 범위의 타입으로 자동 형변환[프로모션]시킨 후 연산을 진행한다.
피연산자가
int
타입보다 작다면int
타입으로 타입을 일치시킨 후 연산을 진행한다.
- byte + short -> int + int = int
- char + short -> int + int = int
이는 JVM 의 피연산자 스택(Operand Stack)의 기본 공간이 4byte 이므로 4byte 보다 작은 타입은 int 타입으로 변환되어 연산되는 것과 관련이 있다.
피연산자가
int
타입보다 크다면,더 큰 피연산자의 타입
으로 타입을 일치시킨 후 연산을 진행한다.
- int + double -> double + double = double
- int + float -> float + float = float
- float + double -> double + double = double
char a = 'A';
byte b = 10;
System.out.println(a+b);
System.out.println( ((int) a) + ((int) b) );// 결과는 int 타입
System.out.println( a + 10.5 );
System.out.println( ((double) a) + 10.5 ); // 결과는 double 타입
byte result = a + b;// X -> 결과는 int 타입이므로 명시적 형변환 없이 대입 불가능
/**
* 75
* 75
* 75.5
* 75.5
*/
char 와 short 는
2byte
로 동일한 크기를 가지지만, 표현 범위가 다르다.
- char = -2^16-1 ~ (2^16-1) -1
- short = 0 ~ (2^16) -1
char 와 short 타입 간의 형변환에는 타입에 관계없이 값 손실이 발생할 수 있으므로
반드시 명시적 형변환을 진행해야한다.
char ch = 'A';
short s = 100;
ch = (char) s;
s = (byte) ch;