날 야근하게 만든 Java Integer의 비밀

이동기·2022년 10월 22일
1
post-thumbnail

오늘은 코딩하다 겪어본 상황 중 가장 어이없었던 상황을 소개하고자한다. 코딩을 하다보면 비교문을 정말 많이 쓰게되는데 예상치도 못한일이 발생했으니.. 아래와 같은 결과가 나오는 것이다.

Integer a = 127;
Integer b = 127;

1. a == b //true
2. a.equals(b) //true
Integer a = 126;
Integer b = 127;

1. a == b //true
2. a.equals(b) //false

기본형 비교에 너무 익숙해져 있던터라 무의식중에 써버린 == 비교(참조하는 주소를 비교)를 한것부터 본인 잘못이기는 하나 위 코드박스를 보면 각각 따로 선언했음에도 불구하고 주소 값 비교 시 true가 반환된다.(아니 왜??) 이것 때문에 중요한 로직에서 오류를 찾는데 엄청 오래걸렸다.

다행히도 같이 근무하시는 선배님들이 뭔가를 발견했는데 그건 바로 특정 범위의 숫자의 경우 Integer는 캐싱된 값을 끌어와서 이용한다는 거였다. 어이가 없어서 for문을 돌려가며 1000까지 비교해봤는데. 딱 128부터 false가 떨어진다.

Integer a = 128;
Integer b = 128;

1. a == b // false
2. a.equals(b) //true

그래서 Integer 객체를 까보니 답이 나왔는데, -128 에서 127 범위의 숫자를 따로 캐싱하고 있어서 Integer에 기본형 데이터를 넣으면 오토박싱되어 Integer.valueOf 함수가 자동적으로 호출되고 Integer.valueOf는 -128 에서 127 범위의 값의 경우 캐싱된 값을 내어주는 것이다. 그래서 주소값을 비교할때 true가 반환되는것이다.

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low&& i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
private static class IntegerCache {
    static final int low= -128;
    static final int high;
    static final Integercache[];

    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 {
								//integerCacheHighPropValue는 127로 추정
                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; //h == 127

cache= new Integer[(high-low) + 1]; //127 + 128 + 1 == 256
        int j = low; //-128
        for(int k = 0; k <cache.length; k++)
					cache[k] = new Integer(j++); (-128 ~ +127 까지의 정수를 캐시화)

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high>= 127;
    }

    private IntegerCache() {}
}

핵심요약

Integer에 기본형 타입 값을 넣으면 오토박싱되어 Integer.valueOf()로 데이터를 세팅한다. Integer.valueOf() 메소드는 -128 에서 127 사이의 int literal을 넘겨줄 때, 새로운 Integer 객체를 생성하고 리턴하는 대신, 내부의 IntegerCache 객체에서 Integer 객체를 반환함. Integer와 Integer를 비교할때 -128 부터 127에 해당하는 정수값까지는 내부 캐시(IntegerCache)에 저장되어 있기 때문에 == 연산자로 비교하면 무조건 true를 반환한다. 해당 범위를 벗어나는 값들에 대해서는 false 값을 반환한다.

ByteCacheShortCacheLongCacheCharacterCache도 각각 존재함.

- 참고사항

  • 오토박싱과 언박싱
  • 기본형과 참조형
  • wrapper object
profile
개발자가 되고 싶은 '개'발자입니다.

0개의 댓글