Question?
DTO의 필드를 선언할 때 null 이 들어오면 안되는 필드이면 int 를 사용하고는 했습니다.
물론 이런 필드는 앞에서 validation을 하기 때문에 운영에서 큰 이슈는 없었던 것 같습니다.
Entity에서는 Integer을 주로 사용했습니다.
DB 가 깨지거나 요구사항 추가로 필드가 추가될 때면 간혹 null 이 들어오는 상황이 발생해서 보통 Integer로 받고 null check를 하도록 구현합니다.
그냥 Integer은 null 이 들어올 수 있다! 정도만 알고 무심코 쓰고있었는데, 사실상 객체 생성이라 메모리나 성능 측면에서 int 가 유리하지 않을까? 왜 래퍼타입을 쓰지? 싶어졌습니다 🤔
이번 기회에 primitive 와 wrapper class 에 대해 공부해보겠습니다!
래퍼 클래스(Wrapper class)
자바의 자료형은 primitive type(기본 타입)
과 reference type(참조 타입)
으로 나누어집니다.
8개의 기본 타입에 해당하는 데이터를 객체로 표현하기 위해 포장해 주는 클래스가 바로 wrapper class(래퍼 클래스)
입니다.
래퍼 클래스는 각각의 타입에 해당하는 값들을 전달받아, 해당 값을 가지는 객체(참조형)으로 만들어 줍니다.
primitive type(기본 타입) | wrapper class(래퍼 클래스) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
wrapper class들은 모두
java.lang
패키지에 포함되어 제공됩니다.
박싱(Boxing)과 언박싱(UnBoxing)
래퍼 클래스는 산술 연산을 위해 정의된 클래스가 아니므로, 인스턴스에 저장된 값을 변경할 수 없습니다.
단지, 값을 참조하기 위해 새로운 인스턴스를 생성하고, 생성된 인스턴스의 값만을 참조할 수 있습니다.
허나 JDK 1.5
부터 boxing
,unboxing
이 필요한 상황에서 자바 컴파일러가 이를 자동으로 처리해주는 AutoBoxing, AutoUnboxing
을 지원합니다.
public static void main(String[] args) {
Integer a = new Integer(3);
int b = 3;
Integer c = (Integer) b; // boxing
int d = (int) a; // Unboxing
int e = a; // AutoBoxing
Integer f = b; // AutoUnBoxing
}
Wrapper Class 비교연산 (==, equals)
public class Temp {
public static void main (String args[]) {
Long l1 = 128L;
Long l2 = 128L;
Integer i1 = 128;
Integer i2 = 128;
System.out.println(l1==l2); // false
System.out.println(i1==i2); // false
System.out.println(l1.equals(l2)); // true
System.out.println(i1.equals(i2)); // true
}
}
래퍼 객체는 내부의 값을 비교하기 위해 ==
연산자를 사용할 수 없습니다.
==
연산자는 내부의 값을 비교하는 것이 아니라 객체의 레퍼런스 주소를 비교하기 때문입니다.
비교 대상인 래퍼는 객체이므로 서로의 참조 주소가 다르기 때문에, equals
를 사용해 내부의 값만 얻어 비교해야합니다.
그러나 래퍼 클래스와 기본자료형과의 비교는 ==
연산과 equals
연산 모두 가능합니다. 그 이유는 컴파일러가 자동으로 오토박싱과 언박싱을 해주기 때문입니다.
public class Temp {
public static void main (String args[]) {
Long l1 = 127L;
Long l2 = 127L;
Integer i1 = 127;
Integer i2 = 127;
System.out.println(l1==l2); // true
System.out.println(i1==i2); // true
}
}
또한 Java는 -128 ~ 127
사이의 값은 상수풀에서 관리하기 때문에, 이 범위 내의 숫자들은 ==
로 비교해도 true 가 나오게 됩니다. (정정. 상수풀이 아니라 IntegerCahce 입니다.)
Wrapper class를 사용하는 이유
제네릭(generic)
제네릭(<>) 안에는 Wrapper class만 들어갑니다.
ArrayList 등과 같은 Collection 프레임 워크의 데이터 구조는 기본 타입이 아닌 객체만 저장하게 되고, Wrapper Class를 사용하여 자동 방식과 언방식이 일어납니다.
ArrayList<Integer> list = new ArrayList<>();
ArrayList<int> list = new ArrayList<>(); // compile error
java.util
java.util
패키지의 클래스는 객체만 처리하므로 Wrapper class는 이 경우에도 도움이 됩니다.
Null
Wrapper class는 Primitive Type과 다르게 null이 들어갈 수 있습니다.
Integer a = null;
int b = null; // compile error
Multi Thread
멀티스레딩에서 동기화를 지원하려면 객체가 필요합니다.
Conclude.
primitive type
은 stack memory
에 저장이 되고, reference type
은 heap memory
에 저장됩니다.
때문에 primitive type
은 값을 stack
에서 바로 읽고, reference type
은 stack
에서 heap memory
의 위치를 구해온 다음에 다시 heap memory
에 가서 값을 읽어와야 하기 때문에 primitive type
이 성능상 유리한 것이 맞기는 합니다.
늘 그렇듯 상황에 따라 적절히 선택해서 사용해야겠습니다 😊
Java Primitives Versus Objects
Ref.
Why are 2 Long variables not equal with == operator in Java?
Comparing Long Values in Java
자바에서 래퍼 클래스의 필요성
Why do people still use primitive types in Java?