>Java >java지도 시간 >Java 자동 박싱 및 언박싱과 그 함정에 대한 간략한 논의

Java 자동 박싱 및 언박싱과 그 함정에 대한 간략한 논의

高洛峰
高洛峰원래의
2017-01-16 15:51:271214검색

이 기사에서 저자는 Java의 매우 중요하고 흥미로운 기능인 자동 박싱 및 언박싱을 소개하고 동시에 이 기능도 소스 코드에서 자동 박싱 및 언박싱의 원리를 설명합니다. 함정이 남아있습니다. 개발자는 주의하지 않으면 쉽게 이 함정에 빠질 수 있습니다.

오토박싱

정의

모든 사람이 Java 프로그램을 작성할 때 다음과 같은 방식으로 Integer 객체를 정의하는 경우가 많습니다.

Integer i=100;

위 코드를 보면 i는 Integer 타입의 참조이고, 100은 Java의 기본 데이터 타입임을 알 수 있습니다. 기본 데이터 유형을 해당 래퍼 클래스에 직접 전달하는 이 방법을 Autoboxing이라고 합니다.

jdk 1.5에서 처음으로 오토박싱이 도입되었습니다. jdk 1.5 이전에는 값이 100인 Integer 객체를 정의하려면 다음을 수행해야 합니다.

Integer i=new Integer (100);

Principle

위의 코드 "Integer i=100;"에 중단점을 두고 추적해 보겠습니다.

Java 자동 박싱 및 언박싱과 그 함정에 대한 간략한 논의

다음으로 프로그램이 Integer 클래스

/**
   * Returns a <tt>Integer</tt> instance representing the specified
   * <tt>int</tt> value.
   * If a new <tt>Integer</tt> 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.
   *
   * @param i an <code>int</code> value.
   * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
   * @since 1.5
   */
  public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
      return IntegerCache.cache[i + 128];
    else
      return new Integer(i);
  }

의 valueOf(int i) 메소드로 점프하는 것을 볼 수 있습니다. 즉, 박싱은 jdk 자체가 Integer.valueOf(100) 호출을 완료한다는 의미입니다.

Unboxing

Definition

Integer integer100=100;
int int100=integer100;

위의 코드를 보면 정수100은 Integer 타입의 레퍼런스, int100은 원시 데이터라는 것을 알 수 있다 int 유형의 유형입니다. 그러나 Integer 유형의 객체를 해당 기본 데이터 유형의 변수에 할당할 수 있습니다. 언박싱 입니다.

언박싱과 포장은 반대 작업입니다. Boxing은 해당 캡슐화 클래스의 변수에 기본 데이터 유형을 할당하는 것입니다. 언박싱은 패키지 클래스의 변수를 해당 기본 데이터 유형의 변수에 할당하는 것입니다. 포장 및 개봉기 이름도 매우 적절합니다.

원칙

언박싱 과정에서 jdk가 우리를 위해 무엇을 했는지 다들 짐작하셨을 거라 믿습니다. 실험을 통해 우리의 추측을 증명해보자.

위 코드의 두 번째 줄에 중단점을 설정합니다. 즉, "int int100=integer100;"에 중단점을 설정하고 추적합니다.

프로그램이 Integer의 intValue() 메서드로 점프하는 것을 볼 수 있습니다.

/**
   * Returns the value of this <code>Integer</code> as an
   * <code>int</code>.
   */
  public int intValue() {
    return value;
  }

즉, jdk는 intValue() 메서드 호출을 완료하는 데 도움이 됩니다. 위 실험에서는 정수100의 intValue() 메서드가 호출되고 해당 반환 값이 int100에 할당됩니다.

Extension

실험 1

Integer integer400=400;
int int400=400;
System.out.println(integer400==int400);

위 코드의 세 번째 줄에서는 정수400과 int400이 == 연산을 수행합니다. 이 두 가지 변수는 서로 다른 유형의 변수입니다. Integer400은 unboxed인가요, 아니면 int400인가요? 수술의 결과는 무엇입니까?

== 연산은 두 개체의 주소가 같은지, 두 기본 데이터 유형의 값이 같은지 여부를 확인하는 것입니다. 따라서, 만약 Integer400이 unboxing된다면 이는 두 기본 유형의 값이 비교된다는 것을 의미하고 이때 두 값은 같아야 하며 int400이 boxing되면 실행 결과는 true일 것이라는 것을 누구나 쉽게 추측할 수 있습니다. 이는 두 값을 비교한다는 의미이며, 객체의 주소가 동일한지 여부는 이때 주소가 동일하지 않아야 하며 연산 결과는 거짓입니다. (저자가 값을 400으로 지정한 이유는 나중에 설명할 트랩과 관련이 있습니다.)

실제 실행 결과는 사실입니다. 그래서 정수400 언박싱입니다. 코드 추적 결과도 이를 입증합니다.

실험 2

Integer integer100=100;
int int100=100;
System.out.println(integer100.equals(int100));

위 코드의 세 번째 줄에서 메소드 equals of 정수100의 매개변수는 int100입니다. 우리는 equals 메소드의 매개변수가 기본 데이터 유형이 아닌 Object라는 것을 알고 있으므로 여기서는 int100 boxing이어야 합니다. 코드 추적 결과도 이를 입증합니다.

실제로 메소드의 매개변수 유형이 원시 데이터 유형이고 전달된 매개변수 유형이 캡슐화 클래스인 경우 그에 따라 자동으로 박싱이 해제됩니다. 클래스 유형의 경우 전달된 매개변수 유형은 원래 데이터 유형이며 자동으로 박싱 처리됩니다.

실험 3

Integer integer100 = 100;
int int100 = 100;
Long long200 = 200l;
System.out.println(integer100 + int100);
System.out.println(long200 == (integer100 + int100));
System.out.println(long200.equals(integer100 + int100));

첫 번째 실험에서는 기본 데이터 유형이 캡슐화된 클래스로 == 연산을 수행할 때 언박싱을 위한 캡슐화된 클래스라는 것을 배웠습니다. +, -, *, /라면 어떨까요? 우리는 이 실험을 통해 알 수 있습니다.

+ 연산이 기본 데이터 유형을 상자화하는 경우 다음은 다음과 같습니다.

•라인 4에서 정수100+int100은 Integer 유형과 값 200의 객체 o를 가져오고 toString( ) 이 개체의 메서드 및 출력 "200";

• 라인 5에서 정수100+int100은 Integer 유형과 값 200의 개체 o를 가져오고 == 연산은 이 개체를 long200 개체와 비교합니다. . 당연히 false가 출력됩니다.

• 6행에서 정수100+int100은 Integer 유형과 값 200의 객체 o를 가져오고 Long의 equals 메소드는 long200을 o와 비교합니다. 둘 다 서로 다른 유형이기 때문입니다. 캡슐화된 클래스는 false를 출력합니다.

+ 작업으로 캡슐화된 클래스가 해제되면 다음과 같습니다.

•第4行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b,再将b进行装箱得到o,执行这个对象的toString()方法,并输出”200”;

•第5行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b1,==运算将long200进行拆箱得到b2,显然b1==b2,输出true;

•第6行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b,Long的equals方法将b进行装箱,但装箱所得到的是类型为Integer的对象o,因为o与long200为不同的类型的对象,所以输出false;

程序运行的结果为:     

200
true
false

因而,第二种推测是正确,即在+运算时,会将封装类进行拆箱。

陷阱

陷阱1

Integer integer100=null;
int int100=integer100;

这两行代码是完全合法的,完全能够通过编译的,但是在运行时,就会抛出空指针异常。其中,integer100为Integer类型的对象,它当然可以指向null。但在第二行时,就会对integer100进行拆箱,也就是对一个null对象执行intValue()方法,当然会抛出空指针异常。所以,有拆箱操作时一定要特别注意封装类对象是否为null。

陷阱2

Integer i1=100;
Integer i2=100;
Integer i3=300;
Integer i4=300;
System.out.println(i1==i2);
System.out.println(i3==i4);

因为i1、i2、i3、i4都是Integer类型的,所以我们想,运行结果应该都是false。但是,真实的运行结果为“System.out.println(i1==i2);”为 true,但是“System.out.println(i3==i4);”为false。也就意味着,i1与i2这两个Integer类型的引用指向了同一个对象,而i3与i4指向了不同的对象。为什么呢?不都是调用Integer.valueOf(int i)方法吗?

让我们再看看Integer.valueOf(int i)方法。

/**
   * Returns a <tt>Integer</tt> instance representing the specified
   * <tt>int</tt> value.
   * If a new <tt>Integer</tt> 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.
   *
   * @param i an <code>int</code> value.
   * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
   * @since 1.5
   */
  public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
      return IntegerCache.cache[i + 128];
    else
      return new Integer(i);
  }

   

我们可以看到当i>=-128且i

private static class IntegerCache {
    static final int high;
    static final Integer cache[];
 
    static {
      final int low = -128;
 
      // high value may be configured by property
      int h = 127;
      if (integerCacheHighPropValue != null) {
        // Use Long.decode here to avoid invoking methods that
        // require Integer&#39;s autoboxing cache to be initialized
        int i = Long.decode(integerCacheHighPropValue).intValue();
        i = Math.max(i, 127);
        // Maximum array size is Integer.MAX_VALUE
        h = Math.min(i, Integer.MAX_VALUE - -low);
      }
      high = h;
 
      cache = new Integer[(high - low) + 1];
      int j = low;
      for(int k = 0; k < cache.length; k++)
        cache[k] = new Integer(j++);
    }
 
    private IntegerCache() {}
  }

我们可以清楚地看到,IntegerCache有静态成员变量cache,为一个拥有256个元素的数组。在IntegerCache中也对cache进行了初始化,即第i个元素是值为i-128的Integer对象。而-128至127是最常用的Integer对象,这样的做法也在很大程度上提高了性能。也正因为如此,“Integeri1=100;Integer i2=100;”,i1与i2得到是相同的对象。

对比扩展中的第二个实验,我们得知,当封装类与基础类型进行==运行时,封装类会进行拆箱,拆箱结果与基础类型对比值;而两个封装类进行==运行时,与其它的对象进行==运行一样,对比两个对象的地址,也即判断是否两个引用是否指向同一个对象。

以上这篇Java 자동 박싱 및 언박싱과 그 함정에 대한 간략한 논의就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持PHP中文网。

更多Java 자동 박싱 및 언박싱과 그 함정에 대한 간략한 논의相关文章请关注PHP中文网!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.