1. 형변환이란?
Java는 데이터를 크게 2가지(Primitive, Refence) 타입으로 분류하고 있습니다. 또한 데이터를 다룰 때 데이터 타입을 매우 엄격하게 다루는 편입니다. 기본(Primitive) 타입끼리 연산 시에도 반드시 같은 데이터 형끼리 연산하도록 강제하고 있습니다. 따라서 종류가 다른 2개의 데이터를 연산 시에는 반드시 형변환이 필요합니다.
Java가 지원하는 기본 데이터 타입은 각자의 크기와 범위가 정해져 있습니다. 따라서 크기가 큰 타입을 작은 타입으로 변환하거나 범위가 벗어나는 변환을 시도하면 데이터의 손실이 발생합니다.
이번 포스팅에서는 Java에서 기본 Type끼리의 변환은 어떤 방식으로 하고, 어떠한 예외가 있는지 자세히 살펴보겠습니다.
형변환이란 두 개의 다른 타입을 처리하기 위해 하나의 타입을 다른 타입으로 변환하는 것이다.
1.1 자동 형변환, 강제 형변환
형 변환은 형 변환을 하는 주체와 데이터가 변하는 방향에 따라 2가지로 분류됩니다. 컴파일러가 수행하는 자동 형변환과 개발자가 직접 코드에 명시하는 강제 형변환이 있습니다.
2. 자동 형변환(Type Conversion)
첫 번째는 컴파일러에 의해 데이터 타입이 자동으로 변환되는 형변환이 있습니다. 자동 형변환은 의미와 특징에 따라 여러 가지 용어로 불립니다.
- 자동 형변환
- 암시적 형변환(Implicit Conversion)
- 확장 변환(Widening Conversion)
- 타입 승급(Type Promotion)
자동 형변환은 개발자가 관여하지 않기 때문에 데이터 손실이 발생하지 않는 방향으로만 변환됩니다. 만약 컴파일러가 스스로 데이터의 손실이 일어날 수 있는 형변환을 시도하면 개발자는 큰 혼란을 겪을 것입니다.
데이터가 손실되지 않기 위해선 크기가 작은 타입에서 큰 타입으로 변환되어야만 합니다. 따라서 자동 형변환은 Widening Conversion, Promotion과 같은 용어로 불리기도 합니다.
작은 그릇에 담긴 물을 큰 그릇으로 옮겨도 물의 손실은 발생하지 않는다!
2.1 자동 형변환의 조건
그럼 컴파일러는 언제 자동으로 형변환을 일으킬까요? 자동 형변환이 발생하는 조건은 다음과 같습니다.
1. 서로 다른 두 개의 타입을 연산할 때
위와 같이 서로 다른 타입을 연산할 때 자동적으로 형변환이 일어나는 것을 볼 수 있습니다.
2. 함수 호출 시 매개변수와 인수의 타입이 일치하지 않을 때
method의 매개변수는 double형으로 선언되어 있고 인수를 int형으로 전달했을 때 double형으로 형변환된 것을 볼 수 있습니다.
2.2 byte to char
char형은 유니코드 문자를 나타내기 때문에 사실상 정수형입니다. 따라서 1바이트 크기의 byte에서 2바이트 크기의 char형으로 변환할 수 있지 않나?라고 오해할 수 있습니다.
- byte : -128 ~ 127
- char : 0 ~ 65535
이는 char형의 범위를 보면 쉽게 이해할 수 있습니다. char형은 유니코드를 표현하기 위해 음수값을 표현하지 않습니다. 따라서 byte형과 char 간의 형변환은 불가능합니다.
2.3 long to float
long형은 8byte 크기이고 float는 4byte 크기이기 때문에 메모리 크기로 따지면 형 변환이 불가능하다고 생각할 수 있습니다.
- long : -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
- float : (3.4 X 10^-38) ~ (3.4 X 10^38)의 근삿값
float는 부동소수점 방식을 택하고 있기 때문에 long보다 훨씬 큰 범위를 가지고 있습니다. 위의 사진을 보면 long의 최댓값이 float에 포함되는 것을 볼 수 있습니다.
지수부는 2의 거듭제곱을 나타내고 지수부는 2의 거듭제곱에 곱해지는 수를 나타냅니다. 지수부의 최댓값이 127이기 때문에 long보다 훨씬 넓은 범위를 커버합니다.
하지만, float은 넓은 범위를 가지는 대신 정밀도가 매우 떨어집니다. 따라서 long의 비트를 float으로 옮기는 과정에서 데이터 값이 변질될 수 있습니다. 따라서 일반적으로 정수형을 float으로 형변환 하는 것은 권장하지 않습니다.
정수형을 실수형으로 형변환하려면 float 대신 double로 변환시키는 것을 권장!
3. 강제 형변환(Type Casting)
강제 형변환은 개발자가 코드에 직접 명시하는 형변환입니다. 강제 형변환도 마찬가지로 여러 가지 용어로 불립니다.
- 강제 형변환
- 명시적 형변환(Explicit Conversion)
- 축소 변환(Narrowing Conversion)
- 타입 강등(Type Demotion)
- 타입 주조(Type Casting)
강제 형변환은 개발자가 직접 형변환에 관여하기 때문에 데이터가 손실되는 변환도 가능하도록 설계되었습니다. 개발자가 데이터의 변형을 감수하고도 변형을 할 필요성이 있을 때 강제 형변환을 이용할 수 있습니다.
강제 형변환은 모든 방향으로 데이터를 변환할 수 있습니다. 하지만 형변환 시 오버플로우, 언더플로우가 발생하면 정확한 예측을 할 수 없기 때문에 주의해야 합니다.
강제 형변환은 casting 연산자 ()를 사용하여 코드에 명시합니다.
위의 결과를 보면 데이터가 작은 쪽으로 형변환을 거듭할수록 데이터가 손실되는 것을 볼 수 있습니다. 따라서 개발자는 강제형변환을 할 때 두 타입의 범위를 잘 고려하면서 형변환을 시도해야 합니다.
3.1 데이터 손실 방지
형변환 시 데이터 손실을 유발하는 원인은 크게 3가지가 있습니다.
- 범위 초과 : target 타입의 범위보다 source 타입의 범위가 작은 경우
- 정밀도 손실 : 부동소수점의 정밀도에 의한 손실
- 반올림 오차 : 부동소수점 연산 과정에서 근사적으로 처리 시 발생
정밀도 손실은 보다 높은 정밀도를 가진 double형을 사용하면 어느 정도 방지할 수 있습니다. 그럼 범위초과는 어떻게 방지할 수 있을까요?
바로 Wrapper클래스에 정의된 자료형의 최댓값, 최솟값 상수를 통해 범위를 검사할 수 있습니다.
참고자료
- https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html
- https://www.javatpoint.com/type-casting-vs-type-conversion
- https://inpa.tistory.com/entry/JAVA-%E2%98%95-%ED%83%80%EC%9E%85-%ED%98%95%EB%B3%80%ED%99%98-%EC%A2%85%EB%A5%98-%EB%B0%A9%EB%B2%95-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC#%EA%B0%95%EC%A0%9C_%ED%98%95%EB%B3%80%ED%99%98_casting