[교재 EffectiveJava] 아이템 61. 박싱된 기본 타입보다는 기본 타입을 사용하라

반응형
728x90
반응형

기본타입과 박싱된 기본타입

자바이 데이터 타입은 크게 두가지로 나눌 수 있다. 바로 int, double, boolean과 같은 기본 타입과 String, List 같은 참조 타입이다. 각각의 기본 타입에 대응하는 참조 타입이 하나씩 존재하고, 이를 박싱된 기본타입이라고 한다. 

 

기본타입

int, double, boolean

 

박싱된 기본 타입

Integer, Duble, Boolean

 

오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분하지 않고 사용할 수는 있지만, 둘의 차이는 분명하다. 따라서 상황에 따라 주의해서 선택해야한다. 

 

 

기본 타입과 박싱 기본타입의 차이

1) 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성이란 속성을 갖는다.

박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다.

 

2) 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않은 값, 즉 null을 가질 수 있다.
3) 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다. 

 

 

박싱된 기본타입 사용의 주의점

Integer 값을 오름차순으로 정렬하는 비교자다. 비교자의 메서드 compare는 첫번째 원소가 두번째 원소보다 작으면 음수, 같으면 0, 크면 양수를 반환한다.  

package com.java.effective.item61;

import java.util.Comparator;

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

하지만 여기에 결함이 존재한다.

package com.java.effective.item61;

import java.util.Comparator;

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

        System.out.println(naturalOrder.compare(new Integer(42), new Integer(42)));
    }
}
* 결과
1

같은 값으로 생각하여 0이 출력되리라 예상했겠지만, 결과는 1이 나온다. 즉, 첫번째 Integer가 두번째 Integer 보다 큰 값이라고 주장한다.

 

위 코드의 동작 과정을 보자.

1) naturalOrder의 첫번째 검사 ( i < j )가 올바르게 작동된다.
여기서, i 와 j 가 참조하는 오토박싱된 Integer 인스턴스는 기본 타입 값으로 변환된다.

2) 두번째 검사에서는 두 '객체 참조'의 식별성을 검사하게 된다. i 와 j가 서로 다른 인스턴스라면 이 비교의 결과는 false가 되어 1이 반환된다. 즉, 첫번째 Integer 값이 두번째보다 크다는 것이다. 

이처럼, 박싱된 기본 타입에 == 연산자를 사용하면 오류가 발생한다. 

package com.java.effective.item61;

import java.util.Comparator;

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

        System.out.println(naturalOrder.compare(new Integer(42), new Integer(42)));
    }
}
* 결과
0

 

위 내용을 아래 코드로 정리해보자.

/* true */
new Integer(1).equals(new Integer(1));
/* false */
new Integer(1) == new Integer(1);

 

참고로 new Integer()은 JAVA9 버전 이후로 deprecated 되었다. 이 생성자를 사용하는 것이 거의 모든 면에서 적절하지 않다고 판단된 이유다. 

 

 

기본 타입과 박싱된 기본타입 혼용한 연산 

package com.java.effective.item61;

import java.util.Comparator;

public class Main3 {
    static Integer i;

    public static void main(String[] args) {
        if (i == 42) {
            System.out.println("믿을 수 없군");
        }
    }
}
* 결과
Exception in thread "main" java.lang.NullPointerException at com.java.effective.item61.Main3.main(Main3.java:9)

NullPointerException이 발생한다. 원인은 i가 int가 아닌 Integer 이며, 다른 참조 타입 필드와 마찬가지로 i의 초깃값도 null이라는데 있다. 거의 예외 없이 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다. 그리고 null 참조를 언박싱하면 위 결과처럼 nullPointerException이 발생한다. 

 

해결 (Integer -> int 변경)
package com.java.effective.item61;

import java.util.Comparator;

public class Main3 {
    static int i;

    public static void main(String[] args) {
        if (i == 42) {
            System.out.println("믿을 수 없군");
        }
    }
}

 

 

박싱된 기본타입을 사용 상황 

package com.java.effective.item61;

public class Main4 {
    public static void main(String[] args) {
        Long sum = 0L;

        for (long i = 0 ; i <= Integer.MAX_VALUE ; i++) {
            sum += i;
        }

        System.out.println(sum);
    }
}
* 결과
2305843008139952128

위 코드는 느리다. 이 프로그램은 실수로 지역변수 sum을 박싱된 기본 타입으로 선언하여 느려졌다. 오류나 경고 없이 컴파일되지만, 박싱과 언박싱이 반복해서 일어나 체감될 정도로 성능이 느려진다.

 

기본 타입과 박싱된 기본 타입의 차이를 무시했기 때문에 성능에 문제가 발생하였다. 우리는 기본 타입과 박싱된 기본 타입의 차이를 충분히 숙지하고 주의하여 사용해야한다.

 

박싱된 기본 타입은 언제 써야할까?

1) 컬렉션의 원소, 키, 값으로 쓴다.

컬렉션은 기본 타입을 담을 수 없다.

 

2) 매개변수화 타입 또는 매개변수화 메서드의 타입 매개변수로 쓴다.

자바 언어는 타입 매개변수로기본 타입을 지원하지 않는다.

 

3) 리플렉션을 통해 메서드를 호출할때 쓴다.

 

 

 

 

 

반응형

Designed by JB FACTORY