점점 강해지고 있습니다.
[Java] 오토 박싱과 오토 언박싱
들어가기 전에
우리가 프로그램 개발할 때 여러 자료형을 많이 사용하게 된다. 보통은 원시 타입인 int, double 등의 자료형을 사용하는데, Generic을 사용하는 컬렉션들(List, Map 등)을 사용할 때는 원시 타입을 사용할 수 없고 Wrapper Class라는 Reference Type를 사용해야 한다.
오토 박싱과 오토 언박싱도 자료형의 타입에 따라 생기는 변환 과정이며 Java가 자동으로 해주는 기능 중 하나로 이에 대해 알아볼 것이다.
자료형의 Primitive Type & Wrapper Class
Primitive Type | Wrapper Class(Reference Type) |
---|---|
boolean | Boolean |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
래퍼 클래스(Wrapper Class)는 원시 타입(Primitive Type)의 각 단어의 첫 글자를 대문자로 바꾼 수준으로, 무엇인가 감싸주는 클래스라는 의미로 래퍼(Wrapper) 클래스라고 부른다.
이는 원시 타입을 객체로서 다루기 위해 사용하며, 이를 통해 객체(Object)가 된 래퍼 클래스에는 원시 타입과는 달리 null을 저장할 수 있다.
public class Test {
public static void main(String[] args) {
Integer integer1 = null; // 정상
int int1 = null; // 오류
}
}
원시 타입과 래퍼 클래스 서로 간 변환 과정을 박싱, 언박싱이라고 하는데, 기본 타입을 박스에 담아 감싸주는 과정과 그 반대를 표현하는 것으로
-
원시 타입 -> 래퍼 클래스 — 박싱
-
래퍼 클래스 -> 원시 타입 — 언박싱
이라고 한다.
이를 Java에서 자동으로 해주는 걸 오토 박싱, 오토 언박싱 이라고 부른다.
박싱과 언박싱
그럼 박싱과 언박싱이 왜 필요한가?
자바에서는 Generic
이 래퍼 클래스를 사용하기 때문이다.
Generic
은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법인데,
ArrayList<String>이라면 런타임 환경에서 값을 검색할 때 ArrayList<Object>에서 String
으로 캐스팅되어 검색되는 것이기 때문이다.
int
등의 원시 타입은 최고 조상 클래스인 Object 클래스를 상속받지 않고 있어서 사용할 수 없다.
우리가 사용하는 List<Integer>
등이 예시가 있다. < >
안에 int, double 등의 원시 타입을 쓴다면 **레퍼런스 타입을 사용해야 합니다 **라는 오류가 나게 된다.
이 때문에 래퍼 클래스를 사용하는데, 앞서 말한 List<Integer>
의 경우를 생각해보자.
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
List<Character> characterList = new ArrayList<>();
integerList.add(1);
characterList.add('c');
System.out.println(integerList.get(0).getClass().getName());
System.out.println(characterList.get(0).getClass().getName());
}
}
위의 코드에서 결과는
java.lang.Integer
java.lang.Character
처럼 나오게 된다. 리스트에는 1과 c라는 원시 타입으로 넣었는데 우리가 모르는 사이에 자동으로 오토 박싱이 되었던 것이다.
명시적으로도 가능한가?
public class Test {
public static void main(String[] args) {
Integer integer1 = 1;
int integer_to_int1 = integer1.intValue(); // 명시적 언박싱
Integer integer2 = 2;
int integer_to_int2 = integer2; // 오토 언박싱
System.out.println(integer_to_int1);
System.out.println(integer_to_int2);
System.out.println("=======================");
int int1 = 1;
Integer int_to_integer1 = new Integer(int1); // 명시적 박싱
int int2 = 2;
Integer int_to_integer2 = int2; // 오토 박싱
System.out.println(int_to_integer1);
System.out.println(int_to_integer2);
}
}
명시적으로 오토 박싱, 오토 언박싱이 가능한데 Java에서 자동으로 해주니 굳이 할 필요는 없을 것 같다.
그냥 래퍼 클래스만 사용하는 건?
기본 타입은 null을 다루지도 못하고, Generic에 담기지도 않는다. 그치만 두 가지의 이점이 있어서 사용하게 된다.
- ‘성능’ 에 대한 이점
원시 타입은 스택 메모리에 값이 존재하지만, 참조 타입인 래퍼 클래스는 힙 메모리에 값이 존재하게 되므로 성능이 낮아진다. 또한 값을 필요로 할 때마다 언박싱 과정을 거쳐야 한다.
- ‘점유하는 메모리’ 에 대한 이점
64비트 컴퓨터 기준 점유 메모리이다.
원시타입이 사용하는 메모리 | 참조타입이 사용하는 메모리 |
---|---|
boolean - 1bit | Boolean – 128 bits |
byte - 8bits | Byte - 128bits |
short, char - 16bits | Short, Charater - 128bits |
int, float - 32bits | Integer, Float - 128bits |
long, double - 64bits | Long, Double - 196bits |
Primitive Type 과 Wrapper Class의 값 비교
public class Test {
public static void main(String[] args) {
Integer integer1 = new Integer(2);
int int1 = 1;
System.out.println(integer1 == int1);
}
}
비교 대상 중 primitive type(int) 의 변수가 하나라도 있다면, == 연산자는 값으로 비교한다.
Boxed primitive 또는 Wrapper class(Integer) 끼리 비교 하는 경우, == 연산자는 각 객체의 주소 값을 비교 하게 된다. 값끼리의 비교는 equal 메소드를 사용해야한다.
정리
-
Primitive Type -> Reference Type를 박싱이라고 하며,
-
Reference Type -> Primitive Type를 언박싱이라고 한다.
- 이를 Java에서 자동으로 해주는 것을 오토 박싱, 오토 언박싱이라고 일컫는다.
- 성능과 메모리에 장점이 있는 Primitive Type을 먼저 고려하고, 만약 null 사용 또는 Generic 타입에서 사용되어야 한다면 Wrapper Class를 사용한다.
참고자료
http://www.tcpschool.com/java/java_api_wrapper
https://siyoon210.tistory.com/139
https://velog.io/@kimmjieun/Java-Integer-int-%EC%88%AB%EC%9E%90-%EB%B9%84%EA%B5%90-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD
https://www.geeksforgeeks.org/why-java-collections-cannot-directly-store-primitives-types/
https://stackoverflow.com/questions/1040384/why-do-some-languages-need-boxing-and-unboxing