자바의 모든 type 은 아래 두 가지 범주에 속한다.
값을 binary bits 형태로 직접 저장하는 8개의 primitive Types 이 있다. (byte, short, int, long, float, double, char and boolean)
예를 들어 int a = 5; int b = 5;
를 비교할 때 a 와 b 는 5 라는 이진 값을 직접 보유하고 있기 때문에 a == b 를 사용하면 실제로 5 == 5 를 비교하여 true 를 반환한다.
Primitive Types 이외의 모든 type은 Reference Types (예: 클래스, 인터페이스, 열거형, 배열 등)의 범주에 속한다. Reference Types 의 변수는 컴퓨터의 메모리에 객체의 위치를 저장한다. 이러한 변수는 객체를 참조한다.
예를 들어 Integer a = new Integer(5); Integer b = new Integer(5)
에서 a와 b는 5라는 이진 값을 보유하는 것이 아니라 두 객체 모두 5라는 값을 포함하는 두 개의 개별 객체의 메모리 주소를 보유한다.
따라서 a == b를 사용하여 a와 b를 비교하려고 하면 실제로는 두 개의 개별 메모리 주소를 비교하는 것이므로 false 이고, a와 b에 대해 실제 값이 같음을 비교하려면 a.equals(b)를 수행해야 한다.
Integer c = 128;
Java 컴파일러는 위 코드를 Integer c = Integer.valueOf(128)
로 autoBoxing 을 적용한다.
primitive Types 의 값을 wrapper 클래스의 객체로 바꾸는 과정을 의미한다.
Java 컴파일러는 아래의 경우에 autoBoxing 을 적용한다.
- Passed as a parameter to a method that expects an object of the corresponding wrapper class.
- Assigned to a variable of the corresponding wrapper class.
primitive Types 이 Wrapper 클래스의 타입의 파라미터를 받는 메서드를 통과할 때
primitive Types 이 Wrapper 클래스의 변수로 할당될 때
Integer c = 128;
int e = c;
Java 컴파일러는 위 코드를 int e = c.intValue();
로 autoUnboxing 을 적용한다.
Wrapper 클래스 타입을 primitive Types 으로 변환하는 과정을 의미한다.
Java 컴파일러는 아래의 경우에 autoUnboxing 을 적용한다.
- Passed as a parameter to a method that expects a value of the corresponding primitive type.
- Assigned to a variable of the corresponding primitive type.
Wrapper 클래스 타입이 primitive Types 의 파라미터를 받는 메서드를 통과할 때
Wrapper 클래스 타입이 primitive Types 의 변수로 할당될 때
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // Output -- false
이제 두 개의 Integer 객체 a와 b 를 생성하고 == 을 사용하여 비교하려고 하면 두 참조가 서로 다른 객체를 보유하고 있기 때문에 false가 반환된다.
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // Output -- true
그런데 값 127을 a와 b에 모두 할당하고 == 을 사용하여 비교하면 true 가 반환된다.
위 코드에서 분명 a와 b에 서로 다른 객체를 할당하고 있고, true 가 반환되는 때는 a와 b가 모두 동일한 객체를 가리키는 경우이다.
일단 Integer a = 127;
는 Integer a = Integer.valueOf(127);
로 autoBoxing 된다.
따라서 Integer 객체를 반환하는 것은 Integer.valueOf()
메서드이며, 이는 이 메서드가 내부적으로 무언가를 수행하고 있다는 것을 의미한다.
/**
* 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);
}
Integer.valueOf()
메서드의 소스 코드를 살펴보면,
전달된 int i
가 IntegerCache.low
보다 크고 IntegerCache.high
보다 작으면 IntegerCache
에서 Integer 객체를 반환한다는 것을 알 수 있다.
IntegerCache.low
와 IntegerCache.high
의 기본값은 각각 -128과 127 이다.
즉, 새로운 Integer 객체를 생성하여 반환하는 대신 Integer.valueOf()
는 전달된 int i
가 -128 보다 크고 127 보다 작은 경우 내부 IntegerCache
에서 Integer 객체를 반환한다.
Java는 -128 에서 127 범위에 속하는 Integer 객체를 캐시하는데, 이는 일상적인 프로그래밍에서 이 Integer 범위가 많이 사용되기 때문에 간접적으로 메모리를 절약할 수 있기 때문이다.
아래 코드에서 Integer 클래스는 -128 에서 127 까지의 Integer 객체를 보관하는 캐시 역할을 하는 내부 static IntegerCache 클래스를 유지하므로 127 의 Integer 객체를 가져오려고 할 때 항상 동일한 객체를 얻을 수 있다.
/**
* 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
* jdk.internal.misc.VM class.
*
* WARNING: The cache is archived with CDS and reloaded from the shared
* archive at runtime. The archived cache (Integer[]) and Integer objects
* reside in the closed archive heap regions. Care should be taken when
* changing the implementation and the cache array should not be assigned
* with new Integer object(s) after initialization.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
// Load IntegerCache.archivedCache from archive, if possible
CDS.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int i = 0; i < c.length; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
캐시는 static block
으로 인해 클래스가 메모리에 로드될 때 초기화된다.
캐시의 최대 범위는 -XX:AutoBoxCacheMax
JVM 옵션으로 제어할 수 있다.
이 캐싱은 Integer 객체에만 적용되는 것이 아니고 Integer.IntegerCache
와 마찬가지로 각각 Byte, Short, Long, Character에 대한 ByteCache
, ShortCache
, LongCache
, CharacterCache
도 있다.
Byte, Short, Long 의 캐싱 범위는 -127에서 127 까지로 고정되어 있지만, Character 의 경우 0에서 127 까지로 범위가 정해져 있다.
범위는 Integer 만 수정할 수 있다.
Section 3.3 Primitive Types vs. Reference Types
Autoboxing and Unboxing
Java Integer Cache - Why Integer.valueOf(127) == Integer.valueOf(127) Is True