Java

[Java] 박싱(Boxing), 언박싱(UnBoxing), 오토박싱(AutoBoxing), 오토언박싱(AutoUnBoxing), 래퍼 클래스(Wrapper Class)

cloud-grace 2024. 5. 25. 15:05

Java 데이터 타입

Java의 데이터 타입은 크게 2가지로 나뉜다. 원시 타입(Primitive Type)과 참조 타입(Reference Type)으로 존재하며, 종종 원시 타입으로 사용하는 데이터를 객체로 표현해야 하는 경우가 있다. 원시 타입은 int, char, boolean, float, double 등의 기본 데이터 타입이다. 이 타입들은 래퍼 클래스(Wrapper Class)를 활용해서 객체로 사용할 수 있다.

래퍼 클래스(Wrapper Class)

  • 래퍼 클래스(Wrapper Class)는 원시 타입을 객체로 사용하기 위해 사용하는 클래스이다.
  • Java는 모든 원시 타입을 객체로 만들 수 있다.
  • 래퍼 클래스는 값을 포장해서 객체로 만드는 것이다.
  • 래퍼 클래스는 java.lang package에서 제공된다.
기본 타입 래퍼 클래스
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

원시 타입을 객체로 표현해야 하는 경우

1) 컬렉션 프레임워크(Collection Framework)를 사용할 때

  • Java의 컬렉션 프레임워크(List, Set, Map 등)는 객체만 다룬다.
List<Integer> intList = new ArrayList<>();
intList.add(10);  // Autoboxing: int 10이 Integer 객체로 변환됨

2) 메서드 인자로 객체가 필요할 때

  • 메서드 인자로 객체를 요구할 수 있다.
public void printObject(Object obj) {
    System.out.println(obj);
}

printObject(10);  // Autoboxing: int 10이 Integer 객체로 변환됨

3) 제네릭(Generic)을 사용할 때

  • 제네릭은 원시 타입을 사용할 수 없고, 객체 타입만 사용할 수 있다.
public class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

Box<Integer> intBox = new Box<>();
intBox.setValue(10);  // Autoboxing: int 10이 Integer 객체로 변환됨

4) 객체 메서드를 사용해야 할 때

  • 원시 타입에는 메서드가 없지만, 래퍼 클래스에는 다양한 메서드가 있다.
String number = "123";
int result = Integer.parseInt(number); // Integer 클래스의 parseInt 메서드가 있다.

5) 동기화(synchronization)가 필요할 때

  • 원시 타입은 동기화 블록에서 직접 사용할 수 없다.
Integer count = 0;

synchronized(count) {
    count++;
}

6) 함수형 프로그래밍과 람다 표현식을 사용할 때

  • Java 8 이후 도입된 함수형 인터페이스와 람다 표현식에서도 원시 타입을 바로 사용할 수 없고, 객체 타입을 사용해야 한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().mapToInt(Integer::intValue).sum();

박싱 Boxing & 언박싱 UnBoxing

  • 박싱 Boxing : 원시 타입 → 래퍼 클래스
  • 언박싱 UnBoxing : 래퍼 클래스 → 원시 타입

오토박싱 AutoBoxing & 오토언박싱 AutoUnBoxing

  • 오토박싱 AutoBoxing과 오토언박싱 AutoUnBoxing은 Java 컴파일러가 박싱 & 언박싱이 필요한 상황에 자동으로 수행해준다.
public class AutoboxingUnboxingExample {
    public static void main(String[] args) {
        // 오토 박싱: 원시 타입 int가 Integer 객체로 변환됨
        Integer boxedInt = 100;  // Autoboxing

        // 오토 언박싱: Integer 객체가 원시 타입 int로 변환됨
        int primitiveInt = boxedInt;  // Unboxing

        // 오토 박싱을 이용한 리스트 생성
        List<Integer> list = new ArrayList<>();
        list.add(10);  // Autoboxing
        list.add(20);  // Autoboxing

        // 오토 언박싱을 이용한 합계 계산
        int sum = 0;
        for (int num : list) {  // Unboxing
            sum += num;
        }

        // 결과
        System.out.println("Boxed Integer: " + boxedInt);
        System.out.println("Primitive int: " + primitiveInt);
        System.out.println("Sum of list elements: " + sum);
    }
}

래퍼 클래스 값 비교

  • 참조 타입인 래퍼 클래스 값 비교는 equals, 인스턴스의 주소 비교는 == 를 사용한다.
public class WrapperClassComparison {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        Integer c = 200;
        Integer d = 200;

        // 객체 참조 비교 (자동 언박싱)
        System.out.println(a == b);  // true, -128부터 127까지는 캐싱되어 동일한 참조
        System.out.println(c == d);  // false, 128 이상의 값은 새로 객체가 생성됨

        // 값 비교
        System.out.println(a.equals(b));  // true
        System.out.println(c.equals(d));  // true
    }
}

NullPointException 에러

  • 래퍼 클래스 객체를 사용하면 NullPointException 에러를 많이 보게 된다.
  • 원시 타입과 Null을 가지는 래퍼 클래스 객체와 연산을 하면 이 에러가 발생한다.
  • 아래 예시는 + 연산을 하면서 n이 오토언박싱이 되는 도중 에러가 발생한다.
public class NullPointExceptionError {
    public static void main(String[] args) {
        Integer n = null;
        int a = 10;
        
        System.out.println(n + a);
    }
}

자료형 변환 예제

  • 래퍼 클래스의 parseType() 메서드로 데이터 타입 변환 시 자주 사용한다.
public class WrapperClassConversionExample {
    public static void main(String[] args) {
        // Integer 객체를 다양한 타입으로 변환
        Integer integerObject = 42;
        int primitiveInt = integerObject.intValue();
        long primitiveLong = integerObject.longValue();
        float primitiveFloat = integerObject.floatValue();
        double primitiveDoubleFromInt = integerObject.doubleValue();

        // Double 객체를 다양한 타입으로 변환
        Double doubleObject = 42.0;
        double primitiveDouble = doubleObject.doubleValue();
        int primitiveIntFromDouble = doubleObject.intValue();

        // Boolean 객체를 boolean 타입으로 변환
        Boolean booleanObject = true;
        boolean primitiveBoolean = booleanObject.booleanValue();

        // String 객체를 원시 타입으로 변환
        String numberStr = "42";
        int parsedInt = Integer.parseInt(numberStr);

        String doubleStr = "42.0";
        double parsedDouble = Double.parseDouble(doubleStr);

        String booleanStr = "true";
        boolean parsedBoolean = Boolean.parseBoolean(booleanStr);

        // 결과
        System.out.println("Integer to int: " + primitiveInt);
        System.out.println("Integer to long: " + primitiveLong);
        System.out.println("Integer to float: " + primitiveFloat);
        System.out.println("Integer to double: " + primitiveDoubleFromInt);

        System.out.println("Double to double: " + primitiveDouble);
        System.out.println("Double to int: " + primitiveIntFromDouble);

        System.out.println("Boolean to boolean: " + primitiveBoolean);

        System.out.println("String to int: " + parsedInt);
        System.out.println("String to double: " + parsedDouble);
        System.out.println("String to boolean: " + parsedBoolean);
    }
}