0
点赞
收藏
分享

微信扫一扫

Java自动装箱与自动拆箱(包装类)以及Integer和Double的valueOf()源码分析

alonwang 2022-01-20 阅读 68

一、基本介绍

包装类的作用: Java 语言中,一切都是对象,但是有例外: 8 个基本数据类型不是对象,因此在很多时候非常不方便。为此,Java提供为 8 个基本类型提供了对应的包装类:

基本数据类型包装类
int(4字节)Integer
byte(1字节)Byte
short(2字节)Short
long(8字节)Long
float(4字节)Float
double(8字节)Double
char(2字节)Character
boolean(未定)Boolean
  • 自动装箱 : 当我们把一个基本类型的值( 20),赋值给引用变量时候,系统可以 自动将它“包装”为相应的包装类的实例程序需要对象时,如果给的只是一个基本类型的值,系统会将它自动装箱为包装类的实例达到的效果:有了自动装箱之后,基本类型的值可以当成对象用—— 其实是【假相】 。
  • 自动拆箱: 当我们需要一个基本类型的值时,但实际上传入的包装类的对象。系 统会自动把对象“剥”开,得到它的值。达到的效果:有了自动拆箱之后,包装类的对象可当成基本类型的值 用——其实是【假相】 。

事实上,包装类比基本类型更好用——基本类型能做的事情,包装类也能做。但包装类能做的,基本类型不一定能做,比如要赋一个 null 值。

二、装箱和拆箱是如何实现的

public class Main {
    public static void main(String[] args) {         
        Integer i = 10;//装箱
        int n = i;//拆箱
    }
}

反编译为:

package com.mao.a_box;
 
public class Test01
{
 
	public Test01()
	{
	}
 
	public static void main(String args[])
	{
		Integer i = Integer.valueOf(10);
		int n = i.intValue();
	}
}

从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法

1.Integer

Integer i1=Integer.valueOf(100);
Integer i2=Integer.valueOf(100);
Integer i3=Integer.valueOf(200);
Integer i4=Integer.valueOf(200);

System.out.println(i1==i2);     //true
System.out.println(i3==i4);		//false

输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:

@HotSpotIntrinsicCandidate
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    /**
     * 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
            VM.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() {}
    }

可以发现valueOf(i)函数进入后 i首先和IntegerCache.low以及IntegerCache.high比较,而IntegerCache内部类定义low为-128,high为127.同时声明Cache的大小可以被{@code -XX:AutoBoxCacheMax=}设定,而且cache在第一次使用的时候被初始化。

那么换句话说在使用Integer.valueOf()时,首先就产生了一个值从-128–127的缓存区域,如果要autoBoxing的数字在这个缓存里,就不需要新创建了,直接饮用这里的地址,所以例子中的100可以直接引用,而200需要分别new Integer()。

2.Double

Double i1=Double.valueOf(100);
Double i2=Double.valueOf(100);
Double i3=Double.valueOf(200);
Double i4=Double.valueOf(200);

System.out.println(i1==i2);     //false
System.out.println(i3==i4);		//false

为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。

很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是

注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。

     Double、Float的valueOf方法的实现是类似的。

可以看一下Double中valueOf()的实现代码:

@HotSpotIntrinsicCandidate
    public static Double valueOf(double d) {
        return new Double(d);
    }

发现没有提前初始化缓存区域,而是直接new 新的对象,所以每次产生的都是不一样的对象

3.Boolean

public class Main {
    public static void main(String[] args) {
         
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}
//true
//true

至于为什么是这个结果,同样地,看了Boolean类的源码也会一目了然。下面是Boolean的valueOf方法的具体实现:

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

而其中的 TRUE 和FALSE又是什么呢?在Boolean中定义了2个静态成员属性:

public static final Boolean TRUE = new Boolean(true);
 
    /** 
     * The <code>Boolean</code> object corresponding to the primitive 
     * value <code>false</code>. 
     */
    public static final Boolean FALSE = new Boolean(false);

谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

  当然,这个题目属于比较宽泛类型的。但是要点一定要答上,我总结一下主要有以下这两点区别:

  1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;

  2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。

下面程序的输出结果是什么?

public class Main {
    public static void main(String[] args) {
         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}
/*true
false
true
true
true
false
true*/

其反编译为:

package com.mao.a_box;
 
import java.io.PrintStream;
 
public class Main
{
 
	public Main()
	{
	}
 
	public static void main(String args[])
	{
		Integer a = Integer.valueOf(1);
		Integer b = Integer.valueOf(2);
		Integer c = Integer.valueOf(3);
		Integer d = Integer.valueOf(3);
		Integer e = Integer.valueOf(321);
		Integer f = Integer.valueOf(321);
		Long g = Long.valueOf(3L);
		Long h = Long.valueOf(2L);
		System.out.println(c == d);
		System.out.println(e == f);
		System.out.println(c.intValue() == a.intValue() + b.intValue());
		System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
		System.out.println(g.longValue() == (long)(a.intValue() + b.intValue()));
		System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
		System.out.println(g.equals(Long.valueOf((long)a.intValue() + h.longValue())));
	}
}

第一个和第二个输出结果没有什么疑问。第三句由于  a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。

举报

相关推荐

0 条评论