Wrapper Class는 뭘까 ?

기본형 데이터를 객체로 다룰 수 있도록 해주는 클래스다.
Java에는 8개의 기본 데이터 타입이 있으며, 각각에 대응되는 Wrapper 클래스는 아래와 같다.
- byte → Byte
- short → Short
- int → Integer
- long → Long
- float → Float
- double → Double
- char → Character
- boolean → Boolean
import java.util.ArrayList;
public class WrapperExample {
public static void main(String[] args) {
// 1. 기본 타입 int를 Wrapper 클래스 Integer로 변환 (Autoboxing)
int num = 10;
Integer wrappedNum = num; // 자동으로 변환해준다. int → Integer
System.out.println("wrappedNum: " + wrappedNum); // 출력: wrappedNum: 10
// 2. Wrapper 클래스 Integer를 기본 타입 int로 변환 (Unboxing)
Integer anotherWrappedNum = 20;
int anotherNum = anotherWrappedNum; // Integer → int 자동 변환
System.out.println("anotherNum: " + anotherNum); // 출력: anotherNum: 20
// 3. 컬렉션에 기본 타입 값을 저장하기 위해 Wrapper 클래스 사용
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(num); // int 값을 컬렉션에 추가 (Autoboxing)
numbers.add(anotherNum);
System.out.println("numbers: " + numbers); // 출력: numbers: [10, 20]
// 4. 문자열을 숫자로 변환하는 유틸리티 메서드 사용
String strNum = "100";
int parsedNum = Integer.parseInt(strNum); // 문자열을 int로 변환
System.out.println("parsedNum: " + parsedNum); // 출력: parsedNum: 100
// 5. 기본 타입과 Wrapper 클래스의 비교
if (num == wrappedNum) { // Unboxing으로 값 비교가 가능
System.out.println("num과 wrappedNum은 같습니다."); // 출력: num과 wrappedNum은 같습니다.
}
}
}
이처럼 Wrapper 클래스는 Java에서 기본 타입을 객체처럼 다루기 위해 여러 메서드들(valueOf, parseInt 등)이 존재하는데, 매우 유용하다는걸 볼 수 있다.
위에 예시 뿐만아니라 Wrapper 클래스를 사용하면 null 값을 가질 수 있다는 점도 큰 장점이다. 기본 타입은 항상 값이 존재(-1, 0, 1 과 같은 값)해야 하지만, Integer와 같은 Wrapper 클래스는 null을 가질 수 있어, 값이 없음을 명시적으로 표현할 수 있다.
그럼 이렇게 좋은 Wrapper 클래스만 사용하면 되지 굳이 기본형 타입이 있는 이유가 따로 있는걸까 ?
정답은 성능적인 부분에서 차이가 존재한다.
public class PerformanceTest {
public static void main(String[] args) {
int iterations = 100_000_000;
// 1. 기본형 int 성능 테스트
long startTime = System.nanoTime();
int sum1 = 0;
for (int i = 0; i < iterations; i++) {
sum1 += i;
}
long endTime = System.nanoTime();
long durationPrimitive = endTime - startTime;
System.out.println("기본형 int 성능: " + durationPrimitive + " ns");
// 2. Wrapper 클래스 Integer 성능 테스트
startTime = System.nanoTime();
Integer sum2 = 0;
for (int i = 0; i < iterations; i++) {
sum2 += i; // 자동 언박싱 및 박싱이 발생
}
endTime = System.nanoTime();
long durationWrapper = endTime - startTime;
System.out.println("Wrapper 클래스 Integer 성능: " + durationWrapper + " ns");
// 성능 비교 결과 출력
if (durationPrimitive < durationWrapper) {
System.out.println("기본형이 Wrapper 클래스보다 더 빠릅니다.");
} else {
System.out.println("Wrapper 클래스가 기본형보다 더 빠릅니다.");
}
}
}
startTime과 endTime은 각자 다를수 있겠지만 아마 대부분의 환경에선 "기본형이 Wrapper 클래스보다 더 빠릅니다." 라고 출력이 될 것이다.
래퍼 클래스가 3~5배정도 메모리를 추가로 사용한다. 그래서 CPU 연산을 아주 많이 수행하는 경우나 많은 연산을 연속해서 수행하는 경우엔 그냥 기본형을 사용하는 것이 좋다.
System 클래스 arraycopy
Class를 공부하다가 자바 내부에 있는 System 클래스 메서드중 arraycopy라는 메서드를 알게 되었는데 정말 좋은 메서드라 생각해서 따로 정리했다.
char[] originalArray = new char[]{'h', 'e', 'l', 'l', 'o'};
char[] copiedArray = new char[5];
System.arraycopy(originalArray, 0, copiedArray, 0, originalArray.length);
System.out.println("copiedArray = " + copiedArray); // [C@77459877
System.out.println("Arrays.toString = " + Arrays.toString(copiedArray)); // [h, e, l, l, o]
System클래스의 arraycopy는 첫 번째인자인 originalArray 배열을 고속으로 복사할 수 가 있는데, 이 메서드의 장점은 반복문으로 복사할때마다 속도가 2배 이상 훨씬 더 빠르기 때문이다.(운영체제 레벨에서 하드웨어로 바로 던져서 복사를 한다고 한다.)
다만, System.arraycopy는 얕은 복사(Shallow Copy)만 수행하므로 2차원 배열을 복사하려고 하면 각 행(1차원 배열)을 개별적으로 복사해줘야 한다.
char[][] originalArray = {
{'h', 'e', 'l', 'l', 'o'},
{'w', 'o', 'r', 'l', 'd'}
};
// 복사할 배열 선언 및 생성 (원본 배열과 같은 크기)
char[][] copiedArray = new char[originalArray.length][];
// 각 행(1차원 배열)을 개별적으로 복사
for (int i = 0; i < originalArray.length; i++) {
copiedArray[i] = new char[originalArray[i].length];
System.arraycopy(originalArray[i], 0, copiedArray[i], 0, originalArray[i].length);
}