Type casting(형변환)

dev_Black_Snake·2024년 1월 14일

용어정리

목록 보기
17/25

사전적 정의

자료형을 다른 형태로 변경하는 것

용어 설명

Java에서 형변환을 하는 방법은 크게 두 가지가 있다.

  • Implicit Casting (묵시적 형변환)
  • Explicit Casting (명시적 형변환)

Implicit Casting (묵시적 형변환)

컴파일러가 자동으로 변환하는 형변환

byte a = 1;

혹시 여기서 숫자 1의 data type을 생각해본 적이 있는가? Java에서는 정수의 기본 타입을 int로 지정하고 있다. 고로 위 코드에서 1의 data type은 int 이다. 즉, 위 코드는 int 타입 값을 byte 타입에 넣으려고 시도하는 것이다. 이 과정에서 프로그램은 int 타입인 1을 byte 타입으로 묵시적 형변환을 하여 값을 집어넣게 되는 것이다.

Casting을 하는 경우 data type의 크기를 고려할 필요가 있는데, 그건 다음 형변환 방식을 설명하면서 함께 정리하겠다.

Explicit Casting (명시적 형변환)

프로그래머가 직접 명령하는 형변환

double a = 1.0;
int b = a; 

이 코드를 컴파일해보면, 아래와 같은 에러를 반환한다.

error: incompatible types: possible lossy conversion from double to int

double을 int형으로 변환하는 과정에서 문제가 있다는 내용이다. 앞서 int에서 double은 됐는데 왜 그 역은 안되는 걸까? int는 4bytes이고, double은 8bytes이다. 이는 double이 갖고 있는 정보의 크기가 더 크다는 것이다. 작은 물컵에서 큰 물컵으로 물을 옮기는 데에는 문제가 없지만, 큰 물컵에서 작은 물컵으로 물을 옮길 때에는 물이 흘러 넘칠 경우가 생길 수 있지 않겠는가? 여기서도 정확히 동일한 원리로 문제가 생기는 것이다. 현재 위 코드에서는 크기가 큰 자료형에서 작은 자료형으로 값을 넘겨주려다보니, 컴파일러가 문제가 있을 수 있다고 경고하는 것이다.

하지만 큰 물컵에 들어있는 물의 양이 충분히 적다면, 작은 물컵으로 물을 옮기는데 문제가 없는 경우도 있을 것이다. 개발자가 확실하게 형변환을 하는데 있어 문제가 없다고 판단되는 경우, 우리는 명시적 형변환을 통해 컴파일러를 무시하고 강제로 형변환을 시킬 수 있다.

double a = 1.0;
int b = (int) a;  // b = 1

'1.0'이라는 값은 1이라는 정수로 바꿔도 전혀 문제가 없다. 이런 경우 해당 값을 int 형으로 받아줄 수 있다. 명시적 형변환을 하기 위해서는 대입하려는 변수명 앞에 casting operator인 (data type)을 사용해줘야 한다.

하지만 앞서 말했듯, 데이터 손실이 우려될 가능성이 있는 로직을 다루고 있다면 함부로 명시적 형변환을 진행해서는 안될 것이다. Casting을 할 때는 항상 데이터 크기를 고려하면서 작업하도록 하자.

reference 타입에서의 형변환

위에서는 단순히 primitive data type로 예제를 들었다. 하지만 Reference data type에서도 형변환이 가능하다. 근데 이 경우에는 개념적 크기에 따라서 casting의 안전성을 따져야 한다. 개념적으로 더 큰 클래스를 부모 클래스, 내용이 더 구체적인 클래스를 자식 클래스라고 부른다.

'동물'과 '새' 중 동물이 개념적으로 크다는 것은 직관적으로 알 수 있다. 하지만 만약 두 클래스를 모두 구현해야 한다면, 좀 더 구체적인 새 class 쪽이 더 큰 데이터를 보유하고 있을 것이다.

class Animal {
	String name;
}

class Bird extends animal{
	String wings;
}

public class Test {
	public static void main(String[] args) {
    	Animal a = new Animal();
        Bird b = new Bird();
        Animal c = b;  // Animal로 묵시적 형변환됨
        // Bird d = a;  // error 발생
        
        Bird d = (Bird) c;  // 되돌리는 형태의 casting의 경우, 다시 자식 클래스로 명시적 형변환 가능
    }
}

위 코드를 보면 bird 클래스는 name 필드까지 포함하고 있기 때문에, 분명히 bird 클래스로 구현된 인스턴스는 animal 클래스로 구현된 것보다 클 것이다. 하지만 primitive data type의 경우 작은 쪽에서 큰 쪽으로 casting하는데 data적인 측면에서 전혀 문제가 발생하지 않지만, reference data type의 경우 작은 쪽에서 큰 쪽으로 casting하면 기존에 전혀 없던 필드랑 메소드를 마주해야 한다. 이렇듯 reference data type은 확장을 해버리면 예측 불가능한 환경을 마주해야 하기 때문에 상식적으로 불가능하다는 것을 느낄 수 있다.

반면 자식에서 부모 클래스로 명시적 casting은 가능하다. 그런데 primitive type과 다르게, 자식에서 부모 자료형으로 변환한다 해도 메모리상에서 실질적으로 데이터가 사라지지는 않는다. 그저 개념적으로 자식 클래스에만 있는 필드나 메소드를 호출할 수 없게될 뿐이다. 그래서, 이 상태에서 다시 자식 클래스로 명시적 casting을 하게 되면 다시 이전 상태로 되돌릴 수 있게 된다!

C언어의 구조체를 떠올리면 충분히 납득할 수 있을 것이다. 만약 직관적으로 이해가 안 간다면, 제 블로그의 OOP 포스트를 쭉 읽어보시면서 데이터 크기를 비교해보기 바란다. 그냥 내용 자체도 매우 좋으니 읽어보는 것을 강추.

primitive type VS reference type

  • primitive type은 데이터 크기가 작은 쪽에서 큰 쪽으로 가는 것을 허용.
  • reference type은 개념적 크기가 작은 쪽에서 큰 쪽으로 가는 것을 허용.

casting 연산자의 우선순위

casting 연산자는 매우 높은 우선순위를 갖고 있다. 웬만한 것들보다 우선적으로 실행된다.

int a = 201/2;  // 100
double b = 201.0/2;  // 100.5
double c = (double) 201/2;  // 100.5
double d = (double) (201/2);  // 100.0

몫 연산자는 정수 연산에 대해서는 소수점을 버리지만, 실수 연산에서는 정상적인 나머지 연산을 수행한다는 것을 알아두자. 그리고 데이터 크기가 큰 쪽으로 연산 결과가 나온다는 것도 알아두자. 변수 a와 b를 통해 그 특징을 확인해볼 수 있다.

이제 연산의 우선순위도 확인해보자. 변수 c와 d를 비교해보면, d는 연산식이 소괄호로 묶여있는 것을 확인할 수 있다. casting 연산자의 우선순위는 소괄호보다 느리다. 그러므로 casting 연산자를 활용하면 먼저 정수 연산을 한 후에 그 값을 실수값으로 만들던지, 그냥 실수 연산을 먼저 진행할지 개발자가 선택할 수 있다.

profile
"개발 관련 용어 간단 정리"가 이 블로그의 메인 컨텐츠입니다. 목표는 "개발자들의 위키백과"를 만드는 것입니다. 포스트를 읽는데 요구되는 시간이 대부분 1분 내외이므로, 개발 용어를 리마인드하고자 하면 제 포스트들을 여러 개 읽어보는 것을 추천합니다.                                ※ 주의 : 현재 velog 검색엔진의 문제로, 제 블로그에서 검색하면 제 글이 검색이 안됩니다. 해결법은 제 블로그의 소개글을 확인해주세요.

0개의 댓글