본문 바로가기

Java/Java SE, EE

박싱(Boxing)과 언박싱(Unboxing), 래퍼 클래스(Wrapper class)

이 문서의 내용

    박싱(Boxing)과 언박싱(Unboxing)

    박싱(Boxing)은 기본 타입(Primitive)을 참조 타입(Reference)으로 변환하는 것을 의미합니다.

    반대로 참조 타입을 기본 타입으로 변환하는 것을 언박싱(Unboxing)이라고 부릅니다.

    구분 기본 타입 박싱 언박싱 참조 타입 범위
    논리 타입 boolean > < Boolean true or false
    정수 타입 byte > < Byte -128 ~ 127
    정수 타입 short > < Short -32,768 ~ 32,767
    정수 타입 int > < Integer -2,147,483,648 ~ 2,147,483,647
    정수 타입 long > < Long -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
    실수 타입 float > < Float (3.4 X 10-38) ~ (3.4 X 1038) 의 근사값
    실수 타입 double > < Double (1.7 X 10-308) ~ (1.7 X 10308) 의 근사값
    문자 타입 char > < Character 0 ~ 65,535(유니코드, \u0000 ~ \uFFFF)

    박싱의 결과로 얻을 수 있는 참조 타입을 래퍼 클래스(Wrapper class)입니다.

    기본 타입을 박싱하는 주된 이유는 다음과 같습니다.

    데이터 없음(Null) 표현 컬렉션(Collection) 사용 멤버 함수, 멤버 변수 사용
    기본 타입은 리터럴을 저장하기 때문에 Null을 표현 할 수 없습니다. 배열을 제외한 자바의 컨테이너는 참조 타입을 사용합니다. 래퍼 클래스를 구성하는 멤버 함수와 멤버 변수를 정의해서 사용 할 수 있습니다.

    데이터 없음(Null) 표현

    기본 타입은 리터럴을 저장하기 때문에 Null을 표현 할 수 없습니다.

    기본 타입에서 데이터가 없음을 표현하기 위해서는 임의의 규칙(예를 들면, 저장된 값이 0인 경우 Null로 간주)을 정의해야 합니다.

    이렇게 만들어진 규칙은 유지 보수에서 치명적인 버그로 이어질 가능성이 높습니다.

    또한 데이터베이스가 연결되는 경우에 있어서 스키마의 Null을 처리하기 위해서 참조 타입을 이용해야 할 수 있습니다.

    컬랙션(Collection) 사용

    배열을 제외한 자바의 컨테이너 List Map Set 등은 참조 타입을 사용합니다.

    기본 타입으로 정의된 int[]와 같은 데이터 타입은 List<Integer>와 같은 컨테이너로 박싱해야 할 수 있습니다.

    서로 다른 컨테인너 간의 박싱과 언박싱을 위해서 자바의 스트림(Stream)을 사용 할 수 있습니다.

    멤버 함수, 멤버 변수 사용

    래퍼 클래스를 구성하는 멤버 함수와 멤버 변수를 정의해서 사용 할 수 있습니다.

    예를 들어 int 타입에서 저장 할 수 있는 최대 리터럴은 Integer.MAX_VALUE처럼 래퍼 클래스에서 정의되어 있습니다.

    자동 박싱(Auto boxing)과 자동 언박싱(Auto unboxing)

    래퍼 클래스 또한 하나의 클래스이므로 데이터 사용을 위해서는 먼저 인스턴스 생성이 필요합니다.

    Integer a = new Integer(100);
    Integer b = Integer.valueOf(100);

    자동 박싱(Auto boxing)은 기본 타입이 묵시적으로 참조 타입으로 변환되는 것을 의미합니다.

    Integer v = 100
    더보기

    자동 박싱은 JDK 1.5부터 지원합니다. 이전 버전에서는 명시적으로 형 변환을 필요로 합니다.

    자동 언박싱(Auto unboxing)은 참조 타입이 묵시적으로 기본 타입으로 변환되는 것을 의미합니다.

    Integer a = 100;
    int b = a;

    래퍼 클래스(Wrapper class) 생성자와 valueOf()의 차이

    래퍼 클래스는 기본 타입을 박싱한 결과로써 그 자체로 참조 타입입니다.

    다음은 래퍼 클래스를 생성하는 3가지 방법입니다.

    Integer a = 100;
    Integer b = new Integer(100);
    Integer c = Integer.valueOf(100);
    코드 비고
    Line 1 Integer a = 100 리터럴을 묵시적 형 변환하여 래퍼 클래스를 초기화합니다.
    Line 2 Integer b = new Integer(100) 래퍼 클래스의 생성자를 사용합니다.
    Line 3 Integer c = Integer.valueOf(100) 래퍼 클래스의 정적 함수를 사용합니다.

    위 예시에서 new Integer(100)처럼 생성자를 사용한 래퍼 클래스 초기화에 주의해야 합니다.

    다른 방법은 상수 풀을 사용해 리터럴을 재사용하지만, 생성자를 사용한 초기화는 항상 새로운 메모리를 할당합니다.

    더보기

    상수 풀은 프로그램에서 한 번이라도 사용된 리터럴을 저장 및 유지하여 다음에 동일한 리터럴을 참조할 때 새로 메모리를 할당하는 것이 아니라 기존 메모리의 값을 재사용합니다.

    예를 들어 다음과 같이 생성자를 사용해 초기화한 두 개의 래퍼 클래스는 서로 다른 메모리를 가리킵니다.

    Integer a = new Integer(100);
    Integer b = new Integer(100);
    
    System.out.println(a == b);		// false
    System.out.println(a.equals(b));	// true

    두 변수 a b에 저장된 리터럴이 같은 값을 의미하지만 실제 변수가 참조하는 메모리는 다르기 때문입니다.

    리터럴의 값이 표현하는 의미를 비교하려면 equals()를 사용합니다.

    반면 생성자를 사용하지 않고 초기화 된 두 개의 래퍼 클래스는 동일한 메모리를 참조합니다.

    Integer a = 100;
    Integer b = Integer.valueOf(100);
    
    System.out.println(a == b);		// true
    System.out.println(a.equals(b));	// true

    따라서 자바는 래퍼 클래스 사용 시 new 생성자보다는 valueOf()를 권장합니다.

    valueOf()를 사용하면 상수 풀의 기존 메모리 주소를 참조하므로 공간적, 시간적으로 더 효율적입니다.

    MAX_VALUE, MIN_VALUE

    래퍼 클래스에서 가장 많이 사용되는 정적 변수입니다.

    각 래퍼 클래스 타입에서 지정하는 MAX_VALUEMIN_VALUE는 기본 타입의 데이터 범위를 상수로 정의합니다.

    System.out.println(Integer.MIN_VALUE);		// 0x80000000
    System.out.println(Integer.MAX_VALUE);		// 0x7fffffff
    
    System.out.println(Character.MIN_VALUE);	// \u0000
    System.out.println(Character.MAX_VALUE);	// \uFFFF
    
    System.out.println(Boolean.TRUE);		// true
    System.out.println(Boolean.FALSE);		// false

    일반적으로 데이터의 오버플로우와 언더플로우 관리 및 검증의 용도로 사용합니다.

    valueOf()를 사용한 형 변환

    래퍼 클래스는 valueOf()를 오버로딩해서 여러 기본 타입의 인자로부터 초기화 될 수 있도록 지원합니다.

    System.out.println(Integer.valueOf("123"));
    System.out.println(Float.valueOf("123.456f"));
    System.out.println(String.valueOf(123));

    valueOf() 정적 함수를 사용하면 서로 다른 데이터 타입의 형 변환에서 유용합니다.

    예를 들면 웹에서 문자열로 입력된 요청에 대한 실제 타입으로의 형 변환과 검증에서 사용 할 수 있습니다.

    아래 예시는 valueOf()의 인자로 잘못된 값이 전달되었을 때의 RuntimeException을 보여줍니다.

    Exception in thread "main" java.lang.NumberFormatException: For input string: "1.0f"
    	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    	at java.base/java.lang.Integer.parseInt(Integer.java:652)
    	at java.base/java.lang.Integer.valueOf(Integer.java:983)
    	at Log4JExploit.test(Log4JExploit.java:11)
    	at Main.main(Main.java:4)

    정리 및 복습

    • 박싱(Boxing)은 기본 타입(Primitive type)을 참조 타입(Reference type)으로 변환하는 것을 의미합니다.
    • 참조 타입을 기본 타입으로 변환하는 것은 언박싱(Unboxing)이라고 부릅니다.
    • 자동 박싱(Auto boxing)은 기본 타입이 묵시적으로 참조 타입으로 변환되는 것을 의미합니다.
    Integer v = 100
    • 자동 언박싱(Auto unboxing)은 참조 타입이 묵시적으로 기본 타입으로 변환되는 것을 의미합니다.
    • 박싱의 결과로 얻을 수 있는 참조 타입을 래퍼 클래스(Wrapper class)라고 부릅니다.
    • 래퍼 클래스를 초기화 하는 방법은 다음과 같이 크게 3가지가 있습니다.
    Integer a = 100;
    Integer b = new Integer(100);
    Integer c = Integer.valueOf(100);
    • new 연산자를 사용하는 경우 리터럴을 상수 풀이 아닌 새로운 메모리에 할당하게 됩니다.
    • 따라서 다음과 같이 두 변수는 서로 다른 메모리를 참조하므로 비교 연산자(==)의 결과는 false입니다.
    Integer a = new Integer(100);
    Integer b = new Integer(100);
    
    System.out.println(a == b);		// false