자바 Primitive Type과 Boxed Primitives 둘 중 무엇을 사용할까?
자바에는 기본 자료형인 Primitive Type과 객체로 제공되는 Boxed Primitives Type이 있다. (보통 Primitive를 감싸고 있다고 해서 Wrapper 클래스라고도 불린다.)
자바 1.5 이상부터 Primitive와 Boxed Primitives간의 변환(Cast)을 자동으로 해주는 Autoboxing/AutoUnboxing을 지원하면서 편리하게 사용할 수 있다.
그럼 Primitive와 Boxed Primitives 둘 중 무엇을 사용해야 할까? 스택 영역에 저장되는 Primitive(기본자료형)과 힙영역에 저장되는 Boxed Primitives(객체형)간에는 메모리 효율과 접근속도면에서 Primitive Type이 뛰어나다.
그럼 언제 Boxed Primitives를 사용하는게 좋을까? 첫째, 명시적으로 null을 반환해야 할 경우. Primitive Type의 경우 기본 값을 가지고 있으므로 원하지 않는 값이 제공 될 수 있다. (int는 0, boolean은 false 등) 둘째, Collection의 키/값으로 사용되어 질 때이다. Collection에는 Primitive를 넣을 수 없기 때문에 객체형인 Boxed Primitives를 사용해야한다. 셋째, Parameterized Type의 경우도 (예로 List<String>에서 <String>을 말한다) Boxed Primitives를 사용해야 한다.
이외의 경우에는 Primitive Type을 선호한다.
Boxed Primitives의 경우 성능뿐 아니라 다음과 같은 상황에서 문제가 발생 할 소지가 있기 때문이다. 첫째, == 등 비교 연산에 주의하자. 객체에서의 ==은 값의 비교가 아니라 레퍼런스의 비교이다. Autoboxing/AutoUnboxing으로 인해 혼돈 할 수 있으니 주의하자. (레퍼런스 비교임에도 불구하고 Caching을 하고 있어서 일부 범위에서는 예상 외의 결과가 나온다. 자세한 건 뒤에서 설명하겠다.) 둘째, Autoboxing/AutoUnboxing으로 인해 불필요한 성능저하를 초래할수 있다. 아래 코드 반복문 안의 sum = sum + i; 수행시 Boxed Primitives가 AutoUnboxing되어 매번 Primitives로 변환되어 계산된다. 에러는 없는 코드이지만 반복적인 AutoUnboxing으로 성능저하가 발생하게 된다.
1 2 3 4 5 6 7 | public static void main(String[] args) { Long sum = 0L; for ( long i= 0 ; i<Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); } |
Boxed Primitives에서 또 하나 특이한 점은 Caching을 하고 있다는 사실이다.
1 2 3 4 5 6 7 | Integer i1 = 127 ; Integer i2 = 127 ; Integer j1 = 128 ; Integer j2 = 128 ; System.out.println(i1 == i2); System.out.println(j1 == j2); |
결과는 무엇일까? 답은 true와 false다.
Integer i1 = 127 은 컴파일시에 Integer i1 = Integer.valueOf(127)로 변환된다. valueOf 메소드에서는 해당 int값에 대한 Integer 객체를 생성해서 반환한다. 응? 그럼 어쨋든 ==는 객체 레퍼런스 비교이기 때문에 둘 다 false여야 되는게 아닌가? 여기서 우리가 알아 두어야 할 점이 바로 Caching이다.
Boxed Primitives에서는 자주 사용되는 범위의 값에 대해 미리 생성 해 놓은(Caching 된) 객체를 가지고 있다. 실제 Integer.java 소스를 확인하면 다음과 같이 내부적으로 Caching 된 Integer 객체를 반환한다. (주석을 보면 Caching 범위는 JVM Option으로 조절 할 수 있다.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public static Integer valueOf( int i) { assert IntegerCache.high >= 127 ; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } /** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the -XX:AutoBoxCacheMax=<size> option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = - 128 ; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127 ; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty( "java.lang.Integer.IntegerCache.high" ); if (integerCacheHighPropValue != null ) { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127 ); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low)); } high = h; cache = new Integer[(high - low) + 1 ]; int j = low; for ( int k = 0 ; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} } |
-128 ~ 127 범위에 대해 객체 Caching을 해 놓고 있다. 결국 저 범위의 숫자에 대해서 반환 된 객체는 Caching된 동일 객체이므로 == 비교에서 true인 것이다. Autoboxing/AutoUnboxing에 대해 혼돈하고 Caching된 범위의 값들로만 == 테스트를 해보았다면 사소하지만 큰일이 발생 할 수도 있으니 주의하자.
[참고]
Effective Java 2nd Edition : Item 49. Prefer primitive types to boxed primitives
Java Language Specification : 5.1.7. Boxing Conversion (http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html)
[추가 참고]
http://psvm.tistory.com/81
http://psvm.tistory.com/82