원시타입 (Primitive Type)

JAVA의 자료형에는 흔히 기본형 또는 원시타입 이라고 부르는
Primitive type 과 참조형 또는 래퍼클래스 라고 부르는 Reference Type 두 종류가 있습니다. 이 포스트에서는 Primitive Type에 대해서 알아보겠습니다.

Primitive Type의 테이터는 변수에 할당 될 때 아래표에 나와있는것과 같이 고정된 크기로 저장되고, 해당 변수가 데이터 값을 보관합니다.

# primitive type

타입 크기 범위 최소값 최대값
byte 8 bits -2^7 ~ 2^7-1 -128 127
boolean 1 bit true, false - -
short 16 bits -2^15 ~ 2^15-1 -32,768 +32,767
int 32 bits -2^31 ~ 2^31-1 -2,147,483,648 +2,147,483,647
long 64 bits -2^63 ~ 2^63-1 -9,223,372,036,854,775,808 +9,223,372,036,854,775,807 to
float 32 bits 2^-149 ~ (2-2^-23)*2^127 1.4 E-45 3.402,823,5 E+38
double 64 bits 2^-1074 ~ (2-2^-52)*2^1023 4.9 E-324 1.797,693,134,862,315,7 E+308
char 16 bits 0 ~ 2^16-1 (모든 Unicode 캐릭터) 0 65,535

자바에서는 위에 나와있는 8가지 + void 를 포함하여 총 9가지를 primitive type으로 정의 합니다.

char 타입을 제외하고는 컴파일러에서 해당 값을 상위타입으로 자동으로 변환 (Auto casting) 할 수 있습니다. 예를 들어 위의 표에 나와있는 int 형 변수를 하나 만들고 가질수 있는 최대값인 2,147,483,647을 할당해 봅시다.

1
int intValue = 2147483647; //Integer.MAX_VALUE 와 동일

이후에 intValue 에다가 +1을 해버리면 어떻게 될까요? int형이 가질수 있는 최대범위를 넘어가기 때문에 에러가 날것 같은데 한번 테스트 해 보겠습니다.

1
2
3
4
5
6
@Test
public void testAutoCast() {
int intValue = 2147483647; //Integer.MAX_VALUE;
intValue++;
assertEquals(2147483648, intValue);
}

intValue++; 하는 순간에 오류가 날 것으로 예상했지만 오류는 나지 않았습니다.

에러가 나지 않았으니
2147483647에 1을 더한 값인 2147483648과 intValue++ 한 값이 같을것이라고 assertion을 줘 봤는데요 다음과 같은 컴파일 에러가 납니다.

“The literal 2147483648 of type int is out of range”

컴파일러는 2147483648이 int의 범위 밖이라는것을 알고 있는데 어째서 intValue++는 허용을 해 줬을까요? 어떤값이 나오는지 출력해서 확인 해 봤습니다.

1
2
//assertEquals(2147483648, intValue);
System.out.println(intValue++);
1
-2147483648

음수값이 나온것으로 확인 됐습니다. 1씩 더 더하면 어떻게 되는지 확인해 보겠습니다.

1
2
3
4
intValue++;
System.out.println(intValue++);
intValue++;
System.out.println(intValue++);
1
2
3
-2147483648
-2147483646
-2147483644

이런 결과를 얻게 된 이유는 short, int, long 타입의 2진수 값 저장소는 메모리에서 2의 보수 값을 사용하는데서 비롯 됩니다.

학교에서 다 배워서 알고있는 10진수의 2진수 변환을 해보면

  • 0은 00000000
  • 1은 00000001
  • 2는 00000010

… 이런식으로 변환되고

  • -1은 11111111
  • -2는 11111110
  • -128은 10000000

이런식으로 변환이 됩니다.
그럼 우리가 궁금해 하는 Integer.MAX_VALUE 의 2진수 값은 무엇인지 확인해 보겠습니다.

1
2
3
4
5
@Test
public void testAutoCast(){
int intValue = 2147483647; //Integer.MAX_VALUE;
System.out.println(Integer.toBinaryString(intValue));
}
1
1111111111111111111111111111111

※ 참고로 Integer.toBinaryString() 함수는 int형의 10진수 숫자를 2진수로 변환한 값을 스트링으로 리턴해 주는 함수 입니다.

그 결과가 양수인 경우 부호비트를 제외한 2^31 만큼만 보여주고 음수인경우 2^32 를 다 보여줍니다.

그래서 int형의 최대값은 1이 31개 있는것을 확인 할 수 있습니다.
여기에 1씩 더한값을 또 이진수로 변환해보면 아래와같이 되겠죠

1
2
3
4
1111111111111111111111111111111
10000000000000000000000000000000
10000000000000000000000000000001
10000000000000000000000000000010

2의 보수로 저장하고 있기 때문에 1씩 더해서 실제로 2^0 자리가 1로 바뀌었지만 0은 1로 1은 0으로 바꾼 이후 +1을 해서 원래값을 만들어야 하므로 +2씩 한것같은 효과를 봤던 것 입니다.

잠깐 딴길로 샜는데 다시 본론으로 돌아와서 AutoCasting에 대해 살펴보겠습니다.

1
2
3
4
5
6
@Test
public void testAutoCast(){
int intValue = Integer.MAX_VALUE; //Integer.MAX_VALUE;
long longValue = intValue+1;
assertEquals(Integer.MAX_VALUE+1, longValue);
}

int형인 intValue를 Integer 범위의 최대값으로 잡고, long형인 longValue에 intValue+1을 한 값을 저장 했습니다.
int형을 그냥 +1 했을때는 음수값이 나오면서 위의 테스트가 실패했었는데 지금은 성공합니다.
이와 같이 상위 개념의 타입으로는 값의 정확도를 보장하면서 자동으로 변환을 시켜 주는데 이를 AutoCasting 이라고 합니다.

# 퀴즈

int value의 절대값을 구하는 함수를 만드시오.

1
2
3
4
5
6
7
8
9
10
11
12
public class PrimitiveType {
public int absIntegerValue(int intValue){
return Math.abs(intValue);
}

@Test
public void testAbsIntegerValue() {
int a = 100000;
int b = absIntegerValue(-a);
assertEquals(a, b);
}
}

Math.abs() 라는 훌륭한 함수가 있기 때문에 그냥 return 해주면 간단히 해결 됩니다.
But!
맨처음 표를 보실 때 int의 범위가 -2^31 ~ 2^31-1 였다는걸 기억하신다면 음수가 양수보다 표현할수 있는 숫자가 한개 더 많다는 사실을 눈치 채셨는지 모르겠습니다.

1
2
3
4
5
6
7
@Test
public void testAbsIntegerValue() {

int b = absIntegerValue(Integer.MIN_VALUE);
System.out.println(b);

}
1
-2147483648

int형으로 표현할 수 있는 최소값은 절대값을 씌웠는데도 그대로 음수 부호를 달고 있습니다?!!

2의 보수표현을 할때 0은 양수로 취급하기 때문에 음수를 표현할 수 있는 값이 양수 보다 한개 더 많은 것 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//1. 아예 long으로 변환해서 값을 리턴해 주는 경우
public class PrimitiveType {
public long absIntegerValue(int intValue){
return Math.abs((long)intValue);
}

@Test
public void testAbsIntegerValue() {
long b = absIntegerValue(Integer.MIN_VALUE);
System.out.println(b);

}
}

//2. MIN_VALUE가 들어오면 Exception을 던져주는 경우
public class PrimitiveType {
public int absIntegerValue(int intValue){
if (intValue==Integer.MIN_VALUE){
throw new NumberFormatException();
}
return Math.abs(intValue);
}
@Test
public void testAbsIntegerValue() {

int b = absIntegerValue(Integer.MIN_VALUE);
System.out.println(b);

}

}

위와같이 두가지 경우로 생각해 볼 수 있습니다.

  1. 아예 그냥 long으로 변환해서 던져주거나 (물론 이경우에는 호출하는쪽에서 long으로 받아야 하므로 사실상 int의 최소값 이하를 사용하는 경우가 종종 있는 경우에 해당될 것 입니다.)

  2. 혹시라도 int의 최소값이 들어오는 경우에는 아예 익셉션을 리턴해 버려서 문제가 있음을 인지시켜 줄 수도 있겠습니다.

익셉션이 안나오고 그냥 절대값을 마이너스로 던져버린 경우 실제 저 코드가 프로그램의 일부로 돌아간다고 했을 때 에러로그도 안찍히고 문제없이 돌아가는것 처럼 보이지만 틀린값이 나오는 끔찍한 프로그램이 될 수 있습니다.

Share