Integer 객체를 비교함에 있어 정석은 equals 함수를 사용해 비교해야 한다.
그런데 ‘==’을 사용해 풀어도 어떤 경우에는 원하는 대로 동작하는 경우가 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
void compareInteger() {
Integer num1 = 128;
System.out.println(num1 == Integer.valueOf(128));
Integer num2 = 127;
System.out.println(num2 == Integer.valueOf(127));
System.out.println(Integer.valueOf(127) == Integer.valueOf(127));
}
//결과
false
true
true
우선 Integer num1 = 128은 Integer num1 = Integer.valueOf(128);과 같다. 오토박싱이 된다.
그런데 127을 ‘==’ 으로 비교했을 때는 true, 128을 비교했을 때는 false가 나타나는 것을 볼 수 있다.
Integer.valueOf(127) == Integer.valueOf(127)
우선 원시 타입에는 char, int, float, double, boolean 등이 있다. 이러한 원시 타입은 값을 직접 들고 있는 타입이다.
따라서 아래와 같이 비교하는 것은 실제 값으 비교하는 것이므로 항상 true가 나온다.
1
2
3
4
5
6
System.out.println('c' == 'c');
System.out.println(128 == 128);
//결과
true
true
반면 참조 타입에는 클래스와 인터페이스 등이 있다. 프로그래밍을 하다 보면 원시 타입을 객체로 다루어야 하는 경우가 있다. 이 때 원시 타입을 담도록 만들어진 클래스를 래퍼 클래스 (Wrapper Class) 라고 한다. 대표적으로 Character, Integer, Float, Double, Boolean 등이 있다.
자바에서는 컴파일러가 원시 타입과 참조 타입을 서로 호환가능하도록 도와준다.
위의 Integer num1 = 128;이 가능한 이유이다.
참조 타입은 실제 객체가 아닌 객체의 주소를 저장하고 있다. 래퍼 클래스 역시 참조 타입인 클래스의 일종이므로, 객체가 별도의 주소에 할당된다.
즉 Integer.valueOf(128)에 의해 매번 다른 객체가 만들어지므로 ‘==’으로 비교했을 경우 false가 나오는 것이다.
그런데 왜 127은 정상적으로 비교가 되는걸까?
Integer 캐시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 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
*/
@IntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Integer.valueOf() 메서드를 들여다보면 위와 같다.
IntegerCache.low보다 크고, IntegerCache.high보다 작으면 IntegerCache.cache를 리턴한다.
IntegerCache.low는 -128이고, high는 127이다. 즉, Integer는 내부에서 -128부터 127까지의 Integer 객체를 미리 캐싱해두고, 해당 범위의 값을 요청하면 캐싱된 값을 반환하는 것이다.
그래서 해당 범위의 값을 비교하면 같은 참조를 갖게 되므로 true가 나오게 되는 것이다.
왜 캐싱해둘까?
해당 범위의 값은 매우 자주 사용되기 때문에 메모리를 절약하고자 캐싱을 해두고 있는 것이다. 실제로 자바 Integer 클래스의 내부 스태틱 클래스로 IntegerCache라는 클래스가 선언되어 있음을 확인할 수 있다.
IntegerCache의 static block에서 값을 메모리에 초기화하고 있으므로, 캐시는 클래스가 메모리로 로딩될 때 초기화된다. 캐싱은 Integer 뿐만 아니라 Byte, Short, Long Character 클래스에도 적용된다.
- Byte, Short, Long: -127 ~ 127
- Integer: -128 ~ 127
- Character: 0 ~ 127
참고로 캐시될 범위는 JVM 옵션 중 하나인 -XX:AutoBoxCacheMax로 조절할 수 있다. 하지만 이는 Integer에만 적용 가능하며 다른 클래스의 경우에는 범위를 조절할 수 없다.