[자바] 박싱, 언박싱은 낯설어서

skyepodium·2021년 12월 25일
3

혹시 이렇게 생각해본적이 있나요?
Integer 좋으면 전부 다 Integer 사용하지 왜 int를 사용해야하나

요즘은 Unboxing 찾으면 유튜버 언박싱만 나와

1. 자바 데이터 타입

박싱, 언박싱을 알기 위해서는 먼저 자바 데이터 타입을 알아봅시다.

1) 데이터 타입 2가지

자바의 데이터 타입은 크게 2가지로 나눌 수 있습니다. int, double과 같은 원시타입String, List와 같은 참조타입입니다.

2) 원시 타입

int, char, byte, short, float, double, long, boolean
총 8개가 있습니다.

3) 참조 타입

원시타입에 대응되는 각각이 모두 있습니다. wrapper class 라고 부르기도 합니다.
Integer, Character, Byte, Short, Float, Double, Long, Boolean

그냥, 왜 앞글자가 대문자냐고 물어본다면, 자바에서 관례적으로 클래스 이름은 대문자로 쓰자나요

4) 값 저장 위치

  • 원시타입 - stack 에 값을 저장합니다.
  • 참조타입 - stack 에 주소를 저장하고 heap 에서 값을 참조해옵니다.

2. 참조타입과 원시타입의 차이점

1) 식별성

값이 같아도 다른 인스턴스라고 구별될 수 있습니다.

참조타입의 값 비교에는 equals를 사용합니다. ==는 인스턴스의 주소비교에 사용합니다.

class Main {

    public static void main(String[] args) {

        Integer num1 = new Integer(42);
        Integer num2 = new Integer(42);

        System.out.println("== 비교 결과");
        if(num1 == num2) {
            System.out.println("같아요!!!");
        }
        else{
            System.out.println("다릅니다 달라요!!!!!!!!");
        }

        System.out.println("\nequals 비교 결과");
        if(num1.equals(num2)) {
            System.out.println("같아요!!!");
        }
        else{
            System.out.println("다릅니다 달라요!!!!!!!!");
        }
    }
}

2) null값 가질 수 있음

참조타입의 경우 null값을 가질 수 있고, 원시타입은 가질 수 없습니다. 원시타입에 null이 들어가면 에러가 발생합니다.

3) 속도, 메모리 효율성

타입별 평균적인 접근시간은 참조타입이 원시타입보다 더 큽니다. 메모리 또한 참조타입이 더 사용합니다.

2. 박싱과 언박싱

1) 정의

  • Boxing(박싱)은 원시 타입을 참조 타입으로 변환하는 것을 의미합니다.
  • Unboxing(언박싱)은 참조 타입을 원시 타입으로 변환하는 것을 의미합니다.

2) auto Boxing, auto Unboxing

JDK 1.5에 추가된 내용으로 박싱과 언박싱이 자동으로 이루어지는 것을 의미합니다.

class Main {
    public static void main(String[] args) {
        // 1. auto Boxing
        Integer num1 = 20;

        // 2. auto Unboxing
        int num2 = num1;
    }
}

여기 까지 보면, 자동으로 언박싱, 박싱까지 제공해주는데 속도 조금 느리기는 해도 null값도 넣을 수 있는 참조타입 쓰면 짱짱맨 아닌가 할 수 있는데...
아니다

3. 필요한 부분에 타입을 적절하게 사용하지 않은 경우

1) 같은데 같지 않아

다음 코드는 2개의 인스턴스를 ==으로 비교해서 주소값이 비교되었고 같지 않다고 나옵니다.

import java.util.Comparator;

class Main {

    public static void main(String[] args) {
        /*
            -1: - i < j
            0: i == j
            1: i > j
         */
        Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);

        // 2개의 인스턴스를 == 으로 비교해서, 주소값이 비교되었고 같지 않다고 나옵니다.
        System.out.println(naturalOrder.compare(new Integer(42), new Integer(42)));
    }
}

다음과 같이 언박싱 해주고 값 비교를 해줍니다.

import java.util.Comparator;

class Main {

    public static void main(String[] args) {
        /*
            -1: - i < j
            0: i == j
            1: i > j
         */
        Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
            // 오토 언박싱 해줍니다.
            int i = iBoxed, j = jBoxed;
            return i < j ? -1 : (i == j ? 0 : 1);
        };

        // 값 비교를 통해 같다고 나옵니다.
        System.out.println(naturalOrder.compare(new Integer(42), new Integer(42)));
    }
}

2) NullPointerException 발생

참조타입은 원시타입과 비교연산을 수행할 때 자동으로 언박싱됩니다.

그리고, null 참조를 언박싱하면 NullPointerException이 발생합니다.

class Main {

    public static Integer num1;

    public static void main(String[] args) {
        int num2 = 22;

        // 참조타입은 원시타입과 비교연산을 수행할때 자동으로 Unboxing 됩니다.
        // NullPointerException 발생
        if(num1 == num2) {
            System.out.println("same");
        }
        else{
            System.out.println("different");
        }
    }
}

3) 속도 효율성이 안좋음

다음은 수행속도 차이를 비교하는 코드입니다.

첫번째의 경우 합산하는 변수와 더해지는 변수 모두 원시타입입니다.

두번째의 경우 합산하는 변수를 참조타입으로 선언했고 계산할때 오토 언박싱, 대입할때 오토 박싱이 발생합니다.

이것을 1억번 반복하기 때문에 원시타입을 수행할때보다 약 10배 더 느립니다.

class Main {

    public static void main(String[] args) {
        long primitiveStartTime = System.currentTimeMillis();
        long primitiveSumValue = 0;
        for(int i=0; i<100000000; i++) {
            primitiveSumValue += i;
        }
        System.out.println("원시 타입 수행 결과: " + (System.currentTimeMillis() - primitiveStartTime) + "ms");

        long referenceStartTime = System.currentTimeMillis();
        Long referenceSumValue = 0L;
        for(Integer i=0; i<100000000; i++) {
            referenceSumValue += i;
        }
        System.out.println("참조 타입 수행 결과: " + (System.currentTimeMillis() - referenceStartTime) + "ms");
    }
}

4) 메모리 효율성이 않좋음 - 쓸데없는 객체 생성

자바에서 필요없는 메모리를 점유하고 있는 현상을 메모리 누수 라고 부릅니다.

고대에는 개발자가 직접 해제해주었지만 자바에서는 가비지 컬렉터가 알아서 회수를 해줍니다.

근데 문제는...

가비지 컬렉터가 동작할때 자바 가상 머신이 멈춘다는 것입니다. 이것을 Stop the world 라고 부릅니다.

Auto boxing을 하게되면 필요없는 객체가 생성됩니다.

stop the world는 어떠한 서비스에서도 발생하지만, 굳이 쓸데 없는 객체를 많이 만들어서 자주 발생시킬 이유는 없습니다.

4. 그래서 참조 타입 언제 사용하라구?

이펙티브 자바에 따른면 다음과 같이 명시되어 있습니다.

  1. 가능하면 원시타입 사용

  2. collections를 사용하는 경우 어쩔 수 없이 참조타입 사용

  3. 리플렉션을 통해 메서드 호출하는 경우 참조타입 사용

profile
callmeskye

0개의 댓글