본문 바로가기

언어/Java

Java Integer Cache - Integer a = 127 과 Integer b = 127 일때 a == b 일까?

개요

https://www.programmingmitra.com/2018/11/java-integer-cache.html

 

Java Integer Cache - Why Integer.valueOf(127) == Integer.valueOf(127) Is True

Java Integer Cache - Why Integer.valueOf(127) == Integer.valueOf(127) Is True, Weird Integer boxing in Java

www.programmingmitra.com

위 내용 + 제 생각을 정리 해볼까 합니다.

원문

어떤 면접 인터뷰에서 내 친구는 이런 질문을 받은 적이 있습니다. Integer a = 127; Integer b = 127; 일때 a 와 b 다른 두 오브젝트는 아래 식 a == b이 만족할까요? 라는 질문이였습니다.  그래서 나는 이 글에서 질문을 답하고 해석까지 해보려고 합니다.

Short Answer

간단히 답 해보자면, 오토박싱(auto-boxing)에 의하여 Integer a = 127;로 정의할 경우 자동으로 Integer a = Integer.valueOf(127)로 할당됩니다. 그런데 Integer 클래스는 IntegerCache가 포함되어 있는데, 미리 -128부터 127까지 값을 미리 할당하고 그 오브젝트를 반환하게 됩니다. 그래서 Integer a = 127; 및 Integer b = 127;로 정의 할 경우 모두 같은 오브젝트를 참조하기 때문에 a == b는 참이됩니다.

Long Answer

우선 위에 Short Answer 을 이해하기 위해서 자바의 타입을 알아야합니다, 자바의 타입은 기본적으로 2가지로 나뉩니다.

  1. Primitive Types: 자바에는 총 8가지 원시 자료형(primitive type)이 있습니다. byte, short, int, long, float, double, char 그리고 boolean. 직접 값을 가지고 있기 때문에 int a = 5; int b = 5; 라고 하면 직접 값을 비교 하기 때문에 a == b 가 성립합니다.
  2. Reference Types: primative type을 제외한 모두는 참조 타입(reference type)에 속합니다. Class, Interface, Enum, Array 등등. 참조 타입은 오브젝트 값을 가지고 있는게 아니라 오브젝트의 주소를 가지고 있습니다. 예를 들어서 어떤 코드가 있는데 Integer a = new Integer(5); Integer b = new Integer(5); 라고 하면 이때 a 와 b는 다른 오브젝트로 생성이 되기 때문에 a 값은 5라는 값을 가진 오브젝트의 주소값이 되고, b 값은 5라는 값을 가진 a 와는 다른 오브젝트의 주소 값이 됩니다. 그래서 a == b 연산을 했을때 자바에서는 a 값과 b 값을 비교 하는게 아니라 a 의 주소와 b 의 주소가 같은지를 체크하게 됩니다. 그래서 이때 우리는 비교 하기 위해서 a.equals(b)라는 연산을 하게 됩니다. 그리고 참조 타입은 Strong, Soft, Weak, Phantom으로 나뉠수 있는데요, 이건 나중에 기회가 되면 글을 올릴게요.

예제에 대해서 조금 더 설명을 드릴까 합니다. 1번에 있는 예제 같은 경우 int a = 5;처럼 정의 했을때, 우리는 메모리(stack)에서 0x7aae62270(예) 이라는 주소에 4 바이트(자바는 int 값을 4바이트에 저장합니다)를 할당해서 5 라는 값을 넣습니다. int b = 5; 인 경우 시스템이 할당해주는 어떤 주소(예를 들면 0x9bca85179 라고 가정할게요)에서 똑같이 4 바이트를 할당해서 5를 저장합니다. 2개의 주소가 다르지만 primitive type 이기 때문에 a 와 b 라는 변수는 주소 값이 아닌 실질적인 값 5를 둘다 들고 있습니다. 그래서 a == b 연산 시 참이라는 결과를 얻을수 있습니다.

그런데 2번 예제의 경우, Integer a = new Integer(5); Integer b = new Integer(5); 라고 정의했을때 a 는 0x7aae62270 라는 주소에 4바이트를 할당해서 5라는 값을 넣고, b 는 0x9bca85179에 값을 할당했다면, 이때 a와 b는 참조 타입이기 때문에 a 값은 0x7aae62270이고 b 값은 0x9bca85179라서 a == b 연산시 주소값이 같은지 체크 하기 때문에 다른 값이라는 걸 알 수 있습니다.

다만 Integer는 Object를 상속했기 때문에 equals라는 함수가 있는데요 이를 통해서 비교를 합니다. 다만 Object.equals() 메소드는 오브젝트 주소 값을 비교하는데, Integer에서 Override 해서 Intger 클래스에 있는 primitive type(5) 값을 비교하기 때문에 올바른 결과를 얻을수 있습니다.

그리고 자바는 primitive type에 래퍼 클래스(wrapper class)를 제공하고, 오토박싱 오토언박싱을 지원합니다.

// 오토박싱 예제, 여기서 c는 참조 타입입니다.
Integer c = 128; // 컴파일시 Integer c = Integer.valueOf(128); 로 변경

// 오토언박싱 예제, 여기서 e는 원시 타입입니다.
int e = c; // 컴파일시 int e = c.intValue(); 로 변경

그러면 2개의 integer 오브젝트 a 와 b를 == 연산자로 비교하겠습니다. 그리고 우리는 == 연산이 거짓이라는 걸 알 수 있습니다, 왜냐면 a 와 b는 다른 오브젝트이기 때문에 다른 주소 값을 가지고 있습니다.

Integer a = 128; // 컴파일시 Integer a = Integer.valueOf(128);
Integer b = 128; // 컴파일시 Integer b = Integer.valueOf(128);

System.out.println(a == b); // 결과 -- false

하지만 2개의 integer 오브젝트 a 와 b에 127로 주었을때는 == 연산이 참으로 나왔습니다. 이유가 무엇일까요?

Integer a = 127; // 컴파일 시 Integer a = Integer.valueOf(127);
Integer b = 127; // 컴파일 시 Integer b = Integer.valueOf(127);

System.out.println(a == b); // 결과 -- true

a == b 연산이 참이려면 a 와 b 의 오브젝트가 같아야 합니다. 그래야 같은 주소값을 가르켜서 == 연산이 참이 될 수 있기 때문입니다. 하지만 128일때는 거짓이고 127일때는 참인 이유를 알기 위해서 Integer 클래스를 확인해보겠습니다. 

/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value.  If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param  i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since  1.5
*/
public static Integer valueOf(int i) {
	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 {@code -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) {
        	
            try {
            	int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
            	// If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
        	cache[k] = new Integer(j++);
        
        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }
    private IntegerCache() {}
}

요청하는 값이 [-128, 127]일 경우는 new Integer()를 호출하는게 아니라 미리 캐쉬된 Integer 오브젝트를 반환하는걸 확인 할 수 있습니다. 그래서 2개 오브젝트 a 와 b 가 같은 Integer 오브젝트를 참조하게 되고 == 연산이 참으로 될 수 있습니다.

그리고 우리는 -XX:AutoBoxCacheMax 라는 JVM 옵션을 통해서 캐쉬 하는 최대 값을 변경할 수 있습니다.

그리고 캐싱은 Integer 뿐만 아니라 Byte, Short, Long, Character 에도 포함되어 있으니 각각 확인해보면 좋을것 같습니다.

읽어주셔서 감사합니다.