[Java] 자바의 자료형 - 기본형(Primitive Type) vs 참조형(Reference Type)

sewonK·2022년 8월 23일
1

백기선님 자바 기초 스터디 2주차 과제 질문을 참조하여 포스트를 작성하였습니다.

이번 포스트에서는 자바의 자료형에 대해 알아보겠습니다. 자바의 자료형은 크게 기본형과 참조형 두 가지로 나눌 수 있습니다.


💡 기본형(Primitive Type)

기본형 변수는 실제 값을 저장합니다. 논리형(boolean), 문자형(char), 정수형(byte, short, int, long), 실수형(float, double) 총 8가지 타입이 있으며, 이외 타입은 모두 참조형입니다.

✨ 논리형

boolean

  • 저장 가능 값 : true / false
  • 크기 : 자바에서 데이터를 다루는 최소 단위인 1 byte
  • 기본 값 : false

✨ 문자형

char

  • 저장 가능 값 : 문자의 유니코드 값. 0 ~ 65535(2^16 - 1). \u0000 ~ \uFFFF.
    ex) char ch = 'A'; // 문자 'A'를 char 타입 변수 ch에 저장, 실제 변수 ch에는 유니코드인 65가 저장됨.
    ex) char ch = 65; // 문자의 유니코드를 직접 변수 hc에 저장.

  • 크기 : 2 byte(16 bit).
    정수형과 달리 음수를 나타낼 필요가 없으므로 최상위비트(부호비트)를 제거해 저장 가능한 양수 범위가 두 배가 됩니다.

  • 기본 값 : \u0000

✨ 정수형

byte

  • 저장 가능 값 : -128 ~ 127(-2^7 ~ 2^7-1)
    0000 0000 ~ 0111 1111 : 0, 양수 (0 ~ 127)
    1000 0000 ~ 1111 1111 : 음수 (-128 ~ -1)

  • 크기 : 1 byte(8 bit)
    8비트로 표현할 수 있는 정수의 개수 : 2^8개(2^7 + 2^7 개)

  • 기본 값 : 0

short

  • 저장 가능 값 : -32768 ~ 32767(-2^15 ~ 2^15-1)
  • 크기 : 2 byte(16 bit)
  • 기본 값 : 0

int

  • 저장 가능 값 : -2147483648 ~ 2147483647(-2^31 ~ 2^31-1, +- 20억)
  • 크기 : 4 byte(32 bit)
  • 기본 값 : 0

long

  • 저장 가능 값 : -9223372036854775808 ~ 9223372036854775807(-2^63 ~ 2^63-1)
  • 크기 : 8 byte(64 bit)
  • 기본 값 : 0L

정수값의 범위에 따라 4개의 정수형 중에 하나를 선택하면 되지만, byte나 short보다는 int를 사용하는 것이 권장됩니다. byte와 short가 int보다 크기가 작아서 메모리를 조금 더 절약할 수는 있지만, 저장할 수 있는 값의 범위가 작아 연산 시 범위를 넘어 잘못된 결과를 얻을 수 있기 때문입니다.

오히려 JVM의 피연산자 스택(operand stack)이 피연산자를 4 byte 단위로 저장하기 때문에, 크기가 작은 자료형 값을 계산하면 4 byte로 변환하는 연산이 수행되어 비효율적입니다.

결론적으로 정수형 변수를 선언할 때는 int 타입으로 선언하되, int의 범위를 넘는 경우 long 타입을 사용하는 것이 좋습니다.

✨ 실수형

float

  • 저장 가능 값 : -3.4 x 10^38 ~ -1.4 x 10^-45, 1.4 x 10^-45 ~ 3.4 x 10^38
  • 크기 : 4 byte(32 bit)
  • 기본 값 : 0.0f

double

  • 저장 가능 값 : -1.8 x 10^308 ~ -4.9 x 10^-324, 4.9 x 10^-324 ~ 1.8 x 10^308
  • 크기 : 8 byte(64 bit)
  • 기본 값 : 0.0d

float 타입은 7자리의 정밀도를 가지며, 이 이상의 정밀도를 가져야할 경우에는 15자리의 정밀도를 가지는 double 타입을 사용해야 합니다. 실수형 값을 저장할 때, 값의 범위보다는 보다 높은 정밀도를 보장하기 위해서 double 타입의 변수를 사용합니다.


💡 참조형(Reference Type)

참조형 변수는 어떤 값이 저장되어 있는 주소(memory address)를 값으로 갖습니다.

int a = 77;
Person person = new Person();

위와 같이 변수를 선언했다고 생각해봅시다.

그림을 보면, 기본형 타입 변수인 a는 stack memory에 77이라는 값으로 할당된 것을 볼 수 있습니다.

그러나 person 변수의 경우, stack memory에 B10이라는 주소 값이 저장되어 있습니다. 해당 주소 값을 Heap memory에서 찾으면 person Object가 저장되어 있는 것을 확인할 수 있습니다.

결과적으로 기본형 변수는 stack memeory에 실제 값으로 할당되며, 참조형 변수는 stack memeory에 참조 값이 할당되는 것을 확인할 수 있었습니다. 실제로 참조형 변수는 Heap 영역에 생성됩니다.

잠시 자바의 Call by value, Call by reference에 대해 알아봅시다.

🙋‍♀️ 자바는 Call by value!

프로그래밍 언어들의 메소드 매개변수 호출 방식에는 여러 가지가 있으며, 호출 방식은 언어마다 다르게 되어 있습니다. 대표적으로 C++은 Call by Reference를 사용합니다. Call by Value는 함수의 인자를 전달할 때 '값을 전달하는 방식'이고 Call by Reference는 '주소를 전달하는 방식'입니다.

자바는 Call by value 방식을 사용합니다. 예시를 들어보겠습니다.

public class PrimitiveTypeExample {
    @Test
    public void primitiveTypeExample() {
        int a = 8;
        System.out.println("Before Modify: " + a);
        modify(a);
        System.out.println("After Modify: " + a);
    }
    private static void modify(int a) {
       --a; //Here, the value of a will not change because a comes here not as reference.
        System.out.println("Inside the method the value of a: " + a);
    }
}

결과를 보면, modify 메소드에서 값을 변경한 것이 실제 main 메소드의 값에 영향을 주지 않는 것을 확인할 수 있습니다.

그 이유는 메소드 매개변수 호출 방식이 Call by Value라서, modify 메소드의 변수 a에 8이라는 값만 전달해준 것이기 때문입니다. modify 메소드의 변수 a는, 실제 main 메소드의 변수 a가 아닌 값만 복사된 또 다른 변수인 것이지요.

그렇다면 다음 예제를 보겠습니다.

public class ReferenceTypeExample {
    @Test
    public void referenceTypeExample() {
        int[] myArr = new int[] { 4, 5 };
        System.out.println("Before modify method: " + (myArr[0] + myArr[1])); 
        
        modify(myArr);
        
        System.out.println("After modify method: " + (myArr[0] + myArr[1])); //modify method changed myArr because of reference type!
    }
    private static void modify(int[] arr) {
        System.out.println("Inside modify method: " + (arr[0] + arr[1])); 
        arr[0]--; 
    }
}

Test Result

Before modify method: 9
Inside modify method: 9
After modify method: 8

결과를 보면, modify 메소드 이후 값이 변한 것을 확인할 수 있습니다. 분명 Call by Value인데, 어떻게 값이 변할 수 있는 것일까요? 이번에 공부한 참조형 변수를 떠올려봅시다.

참조형 변수는 값이 저장되어 있는 주소 값을 가지고 있습니다. modify(myArr) 메소드를 통해 myArr의 주소 값(value)이 modify 메소드의 arr에 전달되는 것입니다.

myArr가 가지고 있는 주소 값인 1234를 arr에게 전달하면, arr는 1234라는 주소 값을 복사한 새로운 지역 변수가 됩니다.

이 때 arr[0]--;Heap 영역에 존재하는 실제 myArr object를 참조하게 되기 때문에, myArr object 값에 변경이 발생하게 되는 것입니다.

자바의 메소드 매개변수 호출 방식은 모두 동일한 Call by Value이지만, 매개변수의 자료형이 기본형인지 참조형인지에 따라 결과가 달라질 수 있으니 유의하여 사용해야겠습니다!

💡 래퍼 클래스(Wrapper Class)

기본 자료형의 데이터를 객체로 만들기 위해 사용(포장) 하는 클래스를 래퍼 클래스(Wrapper Class)라고 합니다.

제네릭, 자료구조, 매개변수 등 기본 자료형이 아닌 레퍼런스 타입을 필요로 하는 경우가 많고, 레퍼런스 타입의 경우 메서드를 갖고 있어 다양하게 활용이 가능하기에 기본형 변수와 참조형 변수를 변환하는 래퍼 클래스가 도입되었습니다.

java.lang 패키지에 래퍼 클래스가 제공되며, 종류는 다음과 같습니다.

기본형의 데이터를 래퍼 클래스의 참조형 변수로 변환하는 것을 박싱(boxing)이라 하며, 래퍼 클래스의 참조형 데이터를 기본형으로 변환하는 과정을 언박싱(unboxing)이라고 부릅니다.

박싱(boxing), 언박싱(unboxing) 예제

public class WrapperEx {
	public static void main(String[] args) {
		int i = 10;
		Integer wi = new Integer(i); // 박싱(int → Integer)
        wi = Integer.valueOf(i);     // 박싱(int → Integer)
        wi = i; 					 // 오토 박싱
		
        int i2 = wi.intValue();      // 언박싱(Integer → int)
        i2 = wi;					 // 오토 언박싱
				
		double d = 3.14;
		Double wd = new Double(d);   // 박싱(double → Double)
        wd = Double.valueOf(d);      // 박싱(double → Double)
        wd = d;						 // 오토 박싱
        
        double d2 = wd.doubleValue(); // 언박싱(Double → double)        		        
        d2 = wd;					  // 오토 언박싱
	}
}

💡 기본형을 사용하는 이유?

참조형의 경우 제네릭에서 사용할 수도 있고, 오토 언박싱을 통해 기본형처럼 연산도 가능합니다. 그렇다면 왜 기본형 자료형을 사용해야할까요? 그 이유는 성능에 있습니다.

✨ 메모리

원시타입이 사용하는 메모리참조타입이 사용하는 메모리
boolean - 1 bitBoolean – 128 bits
byte - 8 bitsByte - 128 bits
short, char - 16 bitsShort, Charater - 128 bits
int, float - 32 bitsInteger, Float - 128 bits
long, double - 64 bitsLong, Double - 196 bits

기본형보다 참조형 타입이 사용하는 메모리 양이 압도적으로 높은 것을 확인할 수 있습니다. 따라서 메모리 사용 측면에서 원시 타입이 참조 타입보다 효율적으로 사용할 수 있습니다.

✨ 실행 속도

마지막 인덱스에 위치한 값을 제외하고 모두 값이 동일한 500만 크기의 배열로, 다음 코드를 실행한 속도를 벤치마킹 툴로 측정한 그래프입니다.

while (!pivot.equals(elements[index])) {
    index++;
}

이런 간단한 실행 조차도, 래퍼 클래스가 기본형보다 훨씬 긴 실행 속도를 보이는 것을 확인할 수 있습니다.

📃 출처

  1. 기본형 vs 참조형
  2. 자바의 정석 3rd Edition
  3. Call by value
  4. 기본형, 참조형 예제
  5. 기본형과 참조형의 차이

0개의 댓글