자바의 이상한 정수 복싱


114

방금 다음과 유사한 코드를 보았습니다.

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a == b);

        Integer c = 100, d = 100;
        System.out.println(c == d);
    }
}

실행되면 다음 코드 블록이 출력됩니다.

false
true

첫 번째 이유를 이해 false합니다. 두 개체가 별도의 개체이기 때문에 ==참조를 비교합니다. 그러나 두 번째 진술이 반환되는 이유를 알 수 없습니다 true. Integer의 값이 특정 범위에있을 때 시작되는 이상한 오토 박싱 규칙이 있습니까? 여기서 무슨 일이 일어나고 있습니까?


1
의 속는 것 같은데 stackoverflow.com/questions/1514910/...

3
@RC-속이는 것은 아니지만 비슷한 상황이 논의됩니다. 그래도 참고해 주셔서 감사합니다.
Joel

2
이것은 끔찍합니다. 이것이 내가 전체 원시의 요점을 결코 이해하지 못한 이유입니다. 그러나 객체이지만 둘 다 자동 상자이지만 종속적이지만 aaaaaaaaargh입니다.
njzk2

1
@Razib : "autoboxing"이라는 단어는 코드가 아니므로 형식을 지정하지 마십시오.
Tom

답변:


102

true라인은 실제로 언어 사양에 의해 보장된다. 에서 섹션 5.1.7 :

boxing되는 값 p가 true, false, a byte, \ u0000 ~ \ u007f 범위의 char 또는 -128에서 127 사이의 int 또는 짧은 숫자이면 r1과 r2를 두 개의 boxing 변환의 결과로 둡니다. 의 p. 항상 r1 == r2 인 경우입니다.

두 번째 출력 라인은 보장되지만 첫 번째 라인은 보장되지 않는다는 것을 암시합니다 (아래 인용 된 마지막 단락 참조).

이상적으로 주어진 기본 값 p를 박싱하면 항상 동일한 참조가 생성됩니다. 실제로 이것은 기존 구현 기술을 사용하여 실현 가능하지 않을 수 있습니다. 위의 규칙은 실용적인 타협입니다. 위의 마지막 절에서는 특정 공통 값을 항상 구별 할 수없는 개체로 묶어야합니다. 구현은 이들을 느리게 또는 열심히 캐시 할 수 있습니다.

다른 값의 경우,이 공식은 프로그래머 측에서 박스형 값의 식별에 대한 어떠한 가정도 허용하지 않습니다. 이렇게하면 이러한 참조의 일부 또는 전체를 공유 할 수 있습니다 (필수는 아님).

이렇게하면 특히 작은 장치에서 과도한 성능 저하없이 대부분의 일반적인 경우 동작이 원하는 동작이됩니다. 예를 들어 메모리 제한이 적은 구현은 -32K-+ 32K 범위의 정수 및 long뿐만 아니라 모든 문자와 단락을 캐시 할 수 있습니다.


17
autoboxing은 실제로 valueOfbox 클래스 의 메서드 를 호출하기위한 구문 설탕 (예 :)이라는 점도 주목할 가치가 있습니다 Integer.valueOf(int). JLS가 intValue()et al을 사용하여 정확한 unboxing desugaring을 정의 하지만 boxing desugaring 은 정의 하지 않습니다.
gustafc

@gustafc Integer공식 publicAPI 를 통하는 것 외에 다른 방법은 없습니다 intValue(). 그러나 값에 대한 Integer인스턴스 를 얻을 수있는 다른 방법이 있습니다. 예를 int들어 컴파일러가 코드를 유지하고 이전에 만든 Integer인스턴스를 재사용 할 수 있습니다 .
Holger

31
public class Scratch
{
   public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;  //1
        System.out.println(a == b);

        Integer c = 100, d = 100;  //2
        System.out.println(c == d);
   }
}

산출:

false
true

네 첫 번째 출력은 참조를 비교하기 위해 생성됩니다. 'a'와 'b'-이들은 서로 다른 두 참조입니다. 포인트 1에서는 실제로 다음과 유사한 두 개의 참조가 생성됩니다.

Integer a = new Integer(1000);
Integer b = new Integer(1000);

두 번째 출력 은가 범위 (-128에서 127)에 속할 JVM때 메모리를 절약하려고하기 때문에 생성 됩니다 Integer. 지점 2에서는 'd'에 대한 Integer 유형의 새로운 참조가 생성되지 않습니다. Integer 유형 참조 변수 'd'에 대한 새 개체를 만드는 대신 'c'에서 참조하는 이전에 만든 개체 만 할당합니다. 이 모든 작업은 JVM.

이러한 메모리 절약 규칙은 정수에만 적용되는 것이 아닙니다. 메모리 절약을 위해 다음 래퍼 개체의 두 인스턴스 (복싱을 통해 생성되는 동안)는 항상 기본 값이 동일한 ==입니다.

  • 부울
  • 바이트
  • 캐릭터로부터 \ u0000의\u007f(7F는 십진수 127)
  • -128 에서 127 까지의 Short 및 Integer

2
Long와 동일한 범위의 캐시도 있습니다 Integer.
Eric Wang

8

일부 범위 (-128에서 127까지)의 정수 개체는 캐시되고 다시 사용됩니다. 이 범위를 벗어난 정수는 매번 새로운 객체를 얻습니다.


1
이 범위는 java.lang.Integer.IntegerCache.high속성을 사용하여 확장 할 수 있습니다 . 흥미롭게도 Long에는 해당 옵션이 없습니다.
Aleksandr Kravets 2015 년

5

예, 값이 특정 범위에있을 때 시작되는 이상한 오토 박싱 규칙이 있습니다. Object 변수에 상수를 할당 할 때 언어 정의에 새 개체를 만들어야한다는 내용이 없습니다. 캐시에서 기존 개체를 재사용 할 수 있습니다.

실제로 JVM은 일반적으로 이러한 목적을 위해 작은 정수 캐시와 Boolean.TRUE 및 Boolean.FALSE와 같은 값을 저장합니다.


4

내 생각에 Java는 매우 일반적이기 때문에 이미 '박스형'인 작은 정수의 캐시를 유지하고 새 개체를 만드는 것보다 기존 개체를 재사용하는 데 많은 시간을 절약합니다.


4

흥미로운 점입니다. 책에서 Effective Java 는 항상 자신의 클래스에 대해 같음을 재정의 할 것을 제안합니다. 또한 Java 클래스의 두 개체 인스턴스에 대한 동등성을 확인하려면 항상 equals 메소드를 사용하십시오.

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a.equals(b));

        Integer c = 100, d = 100;
        System.out.println(c.equals(d));
    }
}

보고:

true
true

@Joel은 Integer equality가 아니라 객체 런타임 동작이라는 매우 다른 주제를 요청했습니다.
Iliya Kuznetsov

3

Java에서 boxing은 Integer에 대해 -128에서 127 사이의 범위에서 작동합니다. 이 범위의 숫자를 사용하는 경우 == 연산자와 비교할 수 있습니다. 범위를 벗어난 Integer 개체의 경우 같음을 사용해야합니다.


3

Integer 참조에 int 리터럴을 직접 할당하는 것은 자동 박싱의 예입니다. 여기서 객체 변환 코드에 대한 리터럴 값은 컴파일러가 처리합니다.

컴파일 단계 컴파일러 변환하는 동안 그래서 Integer a = 1000, b = 1000;Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

따라서 Integer.valueOf()실제로 정수 객체를 제공하는 메소드이며, Integer.valueOf()메소드 의 소스 코드를 보면 메소드가 -128에서 127 (포함) 범위의 정수 객체를 캐시하는 것을 명확하게 알 수 있습니다.

/**
 *
 * 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);
 }

따라서 전달 된 int 리터럴이 -128보다 크고 127보다 작 으면 새 정수 개체를 만들고 반환하는 대신 Integer.valueOf()메서드는 내부에서 Integer 개체를 반환합니다 IntegerCache.

Java는 이러한 정수 개체를 캐시합니다.이 정수 범위는 일상적인 프로그래밍에서 많이 사용되어 일부 메모리를 간접적으로 절약하기 때문입니다.

캐시는 정적 블록으로 인해 클래스가 메모리에로드 될 때 처음 사용할 때 초기화됩니다. 캐시의 최대 범위는 -XX:AutoBoxCacheMaxJVM 옵션 으로 제어 할 수 있습니다 .

정수 우리는 또한이 Integer.IntegerCache과 유사한 개체 만이 캐싱 동작은 적용되지 않습니다 ByteCache, ShortCache, LongCache, CharacterCache에 대한 Byte, Short, Long, Character각각.

내 기사 Java Integer Cache-Why Integer.valueOf (127) == Integer.valueOf (127) Is True에서 더 많은 것을 읽을 수 있습니다 .


0

Java 5에서는 메모리를 절약하고 Integer 유형 객체 처리 성능을 향상시키는 새로운 기능이 도입되었습니다. 정수 개체는 내부적으로 캐시되고 동일한 참조 개체를 통해 재사용됩니다.

  1. 이것은 –127에서 +127 (최대 정수 값) 범위의 정수 값에 적용됩니다.

  2. 이 정수 캐싱은 오토 박싱에서만 작동합니다. Integer 객체는 생성자를 사용하여 빌드 될 때 캐시되지 않습니다.

자세한 내용은 pls는 아래 링크를 통해 이동합니다.

세부적인 정수 캐시


0

Integerobeject 의 소스 코드를 확인하면 valueOf다음과 같은 메소드 의 소스를 찾을 수 있습니다 .

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

이는 Integer-128 ( Integer.low)에서 127 ( Integer.high) 까지의 범위에있는 객체가 오토 박싱 중에 동일한 참조 객체 인 이유를 설명 할 수 있습니다 . 그리고 우리는 클래스 의 개인 정적 내부 클래스 인 캐시 배열을 IntegerCache처리 하는 클래스 가 있음을 알 수 있습니다 .IntegerInteger

이 이상한 상황을 이해하는 데 도움이되는 또 다른 흥미로운 예가 있습니다.

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

      Class cache = Integer.class.getDeclaredClasses()[0]; 
      Field myCache = cache.getDeclaredField("cache"); 
      myCache.setAccessible(true);

      Integer[] newCache = (Integer[]) myCache.get(cache); 
      newCache[132] = newCache[133]; 

      Integer a = 2;
      Integer b = a + a;
      System.out.printf("%d + %d = %d", a, a, b); //The output is: 2 + 2 = 5    

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.