Effective Java : 아이템 61. 박싱된 기본 타입보다는 기본 타입을 사용하라.

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

    • 기본 타입(int), 박싱 기본 타입(Integer)은 오토 박싱 / 오토 언박싱을 통해서 구분되지 않고 사용될 수 있음.
    • 기본 타입, 박싱 기본 타입은 다음 차이점이 있음. 
      • 기본 타입은 값만 가짐. 박싱 기본 타입은 식별성(identity)도 가짐. → '=='으로 비교하면 같은 객체인지 확인함. 
      • 기본 타입은 언제나 유효함. 박싱 기본 타입은 유효하지 않을 수 있음. → 박싱 기본 타입은 Null 값을 전달할 수 있음. 
      • 기본 타입이 메모리 / 시간적으로 더 효율적임. 
        • 박싱 기본 타입은 연산 시, 오토 박싱 / 오토 언박싱 시에 오버로딩이 발생하기 때문임.

     


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

    자바는 오토 박싱 / 오토 언박싱 기능을 제공해주기 때문에 기본 타입(int)과 박싱된 기본 타입(Integer)를 크게 구분하지 않고 사용할 수 있다. 그렇지만 이 기능이 지원된다고 해서 두 타입이 완전히 같은 것은 아니다. 기본 타입과 박싱된 기본 타입은 다음 차이점을 가지고 있다. 

    • 기본 타입은 값만 가짐.  박싱 기본 타입은 식별성(Identity)도 가짐. 
    • 기본 타입은 언제나 유효함. 박싱 기본 타입은 null이 될 수 있음. 
    • 기본 타입이 박싱 기본 타입보다 시간 / 메모리 사용면에서 효율적임. 

    기본 타입과 박싱된 기본 타입은 이런 차이점을 가지고 있으며, 이 차이점을 고려하지 않고 코드를 작성했을 때 발생할 수 있는 문제점을 알아보려고 한다. 

     


    박싱 기본 타입은 식별성을 가짐. 

    기본 타입은 값만 가지지만 박싱 기본 타입은 식별성도 가진다. 박싱 기본 타입은 하나의 객체이며, 따라서 '==' 연산을 한다면 '같은 객체'인지를 확인한다. 아래 코드를 확인해보자.

    public class UnsafeEqualExample {
    
        public static Comparator<Integer> naturalOrder =
                (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
    
        public static void main(String[] args) {
            Integer i1 = new Integer(42);
            Integer i2 = new Integer(42);
            int result = naturalOrder.compare(i1, i2);
            System.out.println("result = " + result);
        }
    }

    위 코드에서 문제가 되는 부분은 (i == j)인 부분이다. Integer 타입은 객체고, 객체에서 == 연산은 같은 객체인지를 본다. 객체가 가리키는 주소가 같은 값인지를 본다. 이런 이유 때문에 main() 메서드에서처럼 42라는 값을 가지는 객체 i1, i2를 전달해주면 compareTo() 연산의 결과로 '1'이 반환된다. 

    i1, i2는 42라는 같은 값을 가진다. 하지만 주소값은 다르다. 즉, 같은 값을 가지는 객체지만 서로 다른 객체다. 따라서 i < j 연산에는 오토 언박싱을 통해 객체의 비교가 이루어진 후, 'i == j '가 평가될 때 '같은 객체'인지를 평가하게 된다. 이런 이유 때문에 기대하지 않은 결과인 '1'이 나오게 된다. 

    이처럼 박싱 기본 타입은 '=='을 평가할 때는 '같은 주소를 가지는 객체'인지를 확인하게 된다. 이 부분을 명확하게 이해해야한다.


    박싱 기본 타입은 안전하지 않을 수 있음.

    기본 타입은 선언되었을 때 기본값을 가지도록 선언된다. 예를 들면 int는 0이라는 값을 가질 것이다. 반면 박싱 기본 타입은 아무런 값도 지정하지 않으면 Null이 된다. 따라서 박싱 기본 타입을 사용했을 때는 Null 값인지를 확인하는 연산이 필요할 수도 있다. 

    public class UnsafeBoxingtype {
    
        public static Integer integerValue;
        public static int intValue;
    
        public static void main(String[] args) {
            System.out.println("Integervalue = " + integerValue);
            System.out.println("intValue = " + intValue);
        }
    }

    코드를 실행해보면 IntegerValue는 Null, intvalue는 기본값인 0을 가지는 것을 확인할 수 있다.

    Integervalue = null
    intValue = 0

     


    기본 타입이 박싱 기본 타입보다 효율적임.

    아래 코드에서는 기본 타입과 박싱 기본 타입에 대해서 동일한 연산을 했을 때 걸리는 시간을 살펴보고자 한다. Long 타입(박싱 타입) / long 타입(기본 타입)에 적산하는 연산을 많이 반복했을 때 소요 시간의 차이점을 확인해본다. 

    public static void main(String[] args) {
        Long sumLong = 0L;
        int limit = 100000000;
    
        long s1 = System.currentTimeMillis();
        for (int i = 0; i < limit; i++) {
            sumLong += i;
        }
        long t1 = System.currentTimeMillis() - s1;
    
    
        long sum = 0L;
        long s2 = System.currentTimeMillis();
        for (int i = 0; i < limit; i++) {
            sum += i;
        }
        long t2 = System.currentTimeMillis() - s2;
    
        System.out.println(t1);
        System.out.println(t2);
    }

    결과는 다음과 같다. 동일한 연산을 하더라도 Long 타입(박싱 기본 타입)에 연산을 반복했을 때 더 오랜 시간이 걸렸다. 이것은 박싱 기본 타입에 더하는 연산을 할 때, 오토 박싱 / 오토 언박싱이 반복되었기 때문이다.

    t1 = 447
    t2 = 28
    • sumLong += i 라는 연산을 할 때, sumLong은 오토 언박싱 되어서 더하기 연산이 된다.
    • 더하기 연산이 완료되면 다시 한번 오토 박싱되어서 값이 저장된다. 

    반면 기본 타입인 long을 사용했을 때는 오토 박싱 / 오토 언박싱 과정 없이 연산이 계속 진행되었기 때문에 메모리 / 시간 효율적으로 동작할 수 있었다. 

    댓글

    Designed by JB FACTORY