`
喻红叶
  • 浏览: 40816 次
  • 性别: Icon_minigender_1
  • 来自: 哈尔滨
社区版块
存档分类
最新评论

Java的装箱与拆箱机制

 
阅读更多

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

Java 5增加了自动装箱与自动拆箱机制,方便基本类型与包装类型的相互转换操作。在Java 5之前,如果要将一个int型的值转换成对应的包装器类型Integer,必须显式的使用new创建一个新的Integer对象,或者调用静态方法Integer.valueOf()。

//在Java 5之前,只能这样做
Integer value = new Integer(10);
//或者这样做
Integer value = Integer.valueOf(10);
//直接赋值是错误的
//Integer value = 10;

在Java 5中,可以直接将整型赋给Integer对象,由编译器来完成从int型到Integer类型的转换,这就叫自动装箱。

//在Java 5中,直接赋值是合法的,由编译器来完成转换
Integer value = 10;
与此对应的,自动拆箱就是可以将包装类型转换为基本类型,具体的转换工作由编译器来完成。
//在Java 5 中可以直接这么做
Integer value = new Integer(10);
int i = value;
自动装箱与自动拆箱为程序员提供了很大的方便,而在实际的应用中,自动装箱与拆箱也是使用最广泛的特性之一。自动装箱和自动拆箱其实是Java编译器提供的一颗语法糖(语法糖是指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通过可提高开发效率,增加代码可读性,增加代码的安全性)。

1 实现

在八种包装类型中,每一种包装类型都提供了两个方法:

静态方法valueOf(基本类型):将给定的基本类型转换成对应的包装类型;

实例方法xxxValue():将具体的包装类型对象转换成基本类型;
下面我们以int和Integer为例,说明Java中自动装箱与自动拆箱的实现机制。看如下代码:

class Auto //code1
{
	public static void main(String[] args) 
	{
		//自动装箱
		Integer inte = 10;
		//自动拆箱
		int i = inte;

		//再double和Double来验证一下
		Double doub = 12.40;
		double d = doub;
		
	}
}
上面的代码先将int型转为Integer对象,再讲Integer对象转换为int型,毫无疑问,这是可以正确运行的。可是,这种转换是怎么进行的呢?使用反编译工具,将生成的Class文件在反编译为Java文件,让我们看看发生了什么:
class Auto//code2
{
  public static void main(String[] paramArrayOfString)
  {
    Integer localInteger = Integer.valueOf(10);
    
    int i = localInteger.intValue();
    

    Double localDouble = Double.valueOf(12.4D);
    double d = localDouble.doubleValue();
  }
}
我们可以看到经过javac编译之后,code1的代码被转换成了code2,实际运行时,虚拟机运行的就是code2的代码。也就是说,虚拟机根本不知道有自动拆箱和自动装箱这回事;在将Java源文件编译为class文件的过程中,javac编译器在自动装箱的时候,调用了Integer.valueOf()方法,在自动拆箱时,又调用了intValue()方法。我们可以看到,double和Double也是如此。

实现总结:其实自动装箱和自动封箱是编译器为我们提供的一颗语法糖。在自动装箱时,编译器调用包装类型的valueOf()方法;在自动拆箱时,编译器调用了相应的xxxValue()方法。

2 自动装箱与拆箱中的“坑”

在使用自动装箱与自动拆箱时,要注意一些陷阱,为了避免这些陷阱,我们有必要去看一下各种包装类型的源码。

Integer源码

public final class Integer extends Number implements Comparable<Integer> {
	private final int value;
	
	/*Integer的构造方法,接受一个整型参数,Integer对象表示的int值,保存在value中*/
	 public Integer(int value) {
	        this.value = value;
	 }
	 
	/*equals()方法判断的是:所代表的int型的值是否相等*/
	 public boolean equals(Object obj) {
	        if (obj instanceof Integer) {
	            return value == ((Integer)obj).intValue();
	        }
	        return false;
    }
	 
	/*返回这个Integer对象代表的int值,也就是保存在value中的值*/
	 public int intValue() {
	        return value;
     }
	 
	 /**
	  * 首先会判断i是否在[IntegerCache.low,Integer.high]之间
	  * 如果是,直接返回Integer.cache中相应的元素
	  * 否则,调用构造方法,创建一个新的Integer对象
	  */
	 public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
	 }
	
	/**
	  * 静态内部类,缓存了从[low,high]对应的Integer对象
	  * low -128这个值不会被改变
	  * high 默认是127,可以改变,最大不超过:Integer.MAX_VALUE - (-low) -1
	  * cache 保存从[low,high]对象的Integer对象
	 */
	 private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            }
            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() {}
    }
}

以上是Oracle(Sun)公司JDK 1.7中Integer源码的一部分,通过分析上面的代码,得到:
1)Integer有一个实例域value,它保存了这个Integer所代表的int型的值,且它是final的,也就是说这个Integer对象一经构造完成,它所代表的值就不能再被改变。
2)Integer重写了equals()方法,它通过比较两个Integer对象的value,来判断是否相等。
3)重点是静态内部类IntegerCache,通过类名就可以发现:它是用来缓存数据的。它有一个数组,里面保存的是连续的Integer对象。
(a) low:代表缓存数据中最小的值,固定是-128。
(b) high:代表缓存数据中最大的值,它可以被该改变,默认是127。high最小是127,最大是Integer.MAX_VALUE-(-low)-1,如果high超过了这个值,那么cache[ ]的长度就超过Integer.MAX_VALUE了,也就溢出了。
(c) cache[]:里面保存着从[low,high]所对应的Integer对象,长度是high-low+1(因为有元素0,所以要加1)。
4)调用valueOf(int i)方法时,首先判断i是否在[low,high]之间,如果是,则复用Integer.cache[i-low]。比如,如果Integer.valueOf(3),直接返回Integer.cache[131];如果i不在这个范围,则调用构造方法,构造出一个新的Integer对象。
5)调用intValue(),直接返回value的值。
通过3)和4)可以发现,默认情况下,在使用自动装箱时,VM会复用[-128,127]之间的Integer对象。

Integer  a1 = 1;
Integer  a2 = 1;
Integer  a3 = new Integer(1);
//会打印true,因为a1和a2是同一个对象,都是Integer.cache[129]
System.out.println(a1 == a2);
//false,a3构造了一个新的对象,不同于a1,a2
System.out.println(a1 == a3);

Byte源码

public final class Byte extends Number implements Comparable<Byte> {
		//Byte表示的范围是[-128,127]
	    public static final byte   MIN_VALUE = -128;
	    public static final byte   MAX_VALUE = 127;
	    
	    private final byte value;
	    
	    public Byte(byte value) {
	        this.value = value;
	    }
	    
	    /**
	     * 缓存Byte对象
	     * 将Byte可能的256个对象全部保存到cache[]中
	     * @author cxy
	     *
	     */
	    private static class ByteCache {
	        private ByteCache(){}

	        static final Byte cache[] = new Byte[-(-128) + 127 + 1];

	        static {
	            for(int i = 0; i < cache.length; i++)
	                cache[i] = new Byte((byte)(i - 128));
	        }
	    }
	    
	    /*直接返回ByteCache.cache[]中相应的对象*/
	    public static Byte valueOf(byte b) {
	        final int offset = 128;
	        return ByteCache.cache[(int)b + offset];
	    }
	    
	    /*返回此对象的byte值*/
	    public byte byteValue() {
	        return value;
	    }
	}
byte的表示范围是[-128,127],在Byte内部同样有一个ByteCache类,它也同样有一个cache[ ],它里面保存了所有可能的256个Byte对象。所以在自动装箱时,所有的Byte对象都是复用ByteCache.cache[ ]中的元素。

同样的Character中的CharacterCache类也有一个cache[ ],缓存了[0,127]中的元素。Short和Integer一样,缓存了[-128,127]之间的数,不同的是,Integer可以修改high的值,ShortCache中则是写死的,不能改变。Long的实现方法和Short一样。

Double和Float

/*Double.valueOf(double d)*/
  public static Double valueOf(Double d) {
        return new Double(d);
    }

/*Float.valueOf(float f)*/
  public static Float valueOf(float f) {
        return new Float(f);
    }
从源码中可以看出,Double和Float都没有缓存了,调用valueOf()方法,直接构造出一个新的对象。Double和Float之所以不用缓存,是因为没有办法缓存,(0,1)这么小的一个区间里面,就有无数个double或float数,根本无从缓存。所以在使用Double和Float自动装箱时,全都是构造新的对象,没有缓存。

Boolean源码

public final class Boolean implements java.io.Serializable,Comparable<Boolean>
{
    /*boolean只有两种取值:true,false,所以不需要内部类来缓存了*/
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);

    private final boolean value;

    public Boolean(boolean value) {
        this.value = value;
    }

    public boolean booleanValue() {
        return value;
    }
    
    /**
     * 根据b的值,返回对应的对象
     */
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
}
查看Boolean的源码,发现Boolean没有无参的valueOf(),我们可以推断Boolean没有自动装箱与封箱,可以通过代码验证一下:
 boolean b = true;
 Boolean b1 = b;
 boolean b2 = b1;
这些代码是无法通过编译的。

Boolean还是用到了缓存,由于boolean只有两种取值,所以没有必要使用内部类或者数组来保存缓存的对象,直接定义两个静态属性即可,也就是Boolean.TRUE和Boolean.FALSE。在调用Boolean.valueOf(boolean b)是,返回的是缓存的TRUE或者FALSE,代码验证:

	Boolean b1 = Boolean.valueOf(true);
	Boolean b2 = Boolean.valueOf(true);
	Boolean b3 = new Boolean(true);
	//true,因为返回的都是TRUE对象
	System.out.println(b1 == b2);
	//false,因为b1是TRUE,b3则是一个新的Boolean对象
	System.out.println(b1 == b3);

发生时机

来欣赏一个比较典型的例子:

public class AutoWrapperTrap {
	public static void main(String[] args) {
		//[-128,127]之间,自动装箱会复用对象
		Integer a = 1;
		Integer b = 2;
		Integer c = 3;
		Integer d = 3;
		//不会复用
		Integer e = 321;
		Integer f = 321;
		
		int base = 3;
		
		Long g = 3L;
		
		System.out.println(c == base);//true c自动拆箱
		System.out.println(c == d);//true
		System.out.println(e == f);//false
		System.out.println(c == (a + b));//true 遇到算术运算,自动拆箱
		System.out.println(c.equals(a + b));//true 需要对象,自动装箱
		System.out.println(g == (a + b));//true
		System.out.println(g.equals(a + b));//false 只会自动装箱为对应的包装类型
	}
}

通过反编译后,得到如下代码:
public class AutoWrapperTrap
{
 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);


  int base = 3;


  Long g = Long.valueOf(3L);


  System.out.println(c.intValue() == base);
  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() == a.intValue() + b.intValue());
  System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
 }
}
通过上面的代码,我们分析一下自动装箱与拆箱发生的时机:

(1)当需要一个对象的时候会自动装箱,比如Integer a = 10;equals(Object o)方法的参数是Object对象,所以需要装箱。

(2)当需要一个基本类型时会自动拆箱,比如int a = new Integer(10);算术运算是在基本类型间进行的,所以当遇到算术运算时会自动拆箱,比如代码中的 c == (a + b);

(3) 包装类型 == 基本类型时,包装类型自动拆箱;

需要注意的是:“==”在没遇到算术运算时,不会自动拆箱;基本类型只会自动装箱为对应的包装类型,代码中最后一条说明的内容。

总结

在JDK 1.5中提供了自动装箱与自动拆箱,这其实是Java 编译器的语法糖,编译器通过调用包装类型的valueOf()方法实现自动装箱,调用xxxValue()方法自动拆箱。自动装箱和拆箱会有一些陷阱,那就是包装类型复用了某些对象。

(1)Integer默认复用了[-128,127]这些对象,其中高位置可以修改;

(2)Byte复用了全部256个对象[-128,127];

(3)Short服用了[-128,127]这些对象;

(4)Long服用了[-128,127];

(5)Character复用了[0,127],Charater不能表示负数;

Double和Float是连续不可数的,所以没法复用对象,也就不存在自动装箱复用陷阱。

Boolean没有自动装箱与拆箱,它也复用了Boolean.TRUE和Boolean.FALSE,通过Boolean.valueOf(boolean b)返回的Blooean对象要么是TRUE,要么是FALSE,这点也要注意。

本文介绍了“真实的”自动装箱与拆箱,为了避免写出错误的代码,又从包装类型的源码入手,指出了各种包装类型在自动装箱和拆箱时存在的陷阱,同时指出了自动装箱与拆箱发生的时机。

转载请注明出处:喻红叶《Java的装箱与拆箱机制》


分享到:
评论
1 楼 大江轻舟 2017-09-26  
请问楼主使用的是什么反编译工具呢

相关推荐

    JAVA装箱拆箱(很详细、易于理解)

    Java中的装箱和拆箱是面向对象编程中的一个重要概念,主要涉及到Java的自动类型转换机制。装箱是指将基本数据类型(如int、char、boolean等)转换为对应的包装器类对象,而拆箱则是相反的过程,即把包装器类的对象...

    Java自动装箱与拆箱深度解析:原理、应用与性能考量

    自动装箱和拆箱是Java语言中的重要特性,它们简化了基本数据类型与包装类之间的转换过程。然而,这两个机制也带来了一定的性能影响。为了编写更高效、更可维护的Java代码,开发者需要了解自动装箱和拆箱的工作原理、...

    Java 装箱与拆箱详解及实例代码

    本文将深入讲解Java装箱与拆箱的基本概念,并通过实例代码进行演示。 装箱(Boxing)是Java自动将基本数据类型转换为对应的包装类的过程。例如,int类型转换为Integer,char类型转换为Character等。这是Java 5引入...

    Java中的自动装箱与拆箱:深入理解基本数据类型与对象的转换

    自动装箱和拆箱的实现依赖于Java中的特殊机制。每个包装类都包含一个名为`value`的私有成员变量,用于存储基本数据类型的值。例如,在`Integer`类中,有一个名为`value`的`int`类型成员变量。 - **自动装箱实现**:...

    Java中自动装箱、拆箱引起的耗时详解

    我们也可以使用缓存机制来减少自动装箱和拆箱的次数,从而提高程序的性能。 结论 自动装箱和拆箱是Java语言中一个非常重要的特性,它可以使得程序员的代码更加简洁易懂。但是,这个过程也会引起一些耗时问题。本文...

    Java中的自动装箱与拆箱

    自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。  如果你在Java1.5下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合(Collections)中放...

    Java软件开发实战 Java基础与案例开发详解 8-3 装箱和拆箱 共4页.pdf

    装箱和拆箱就是Java提供的一种自动转换机制,用于在基本数据类型和其对应的包装类之间进行转换。 #### 二、包装类的概念 **包装类**是为了实现面向对象编程中的“一切皆对象”思想而存在的。在Java中,每种基本...

    浅谈Java自动装箱与拆箱及其陷阱

    Java中的自动装箱和拆箱是自JDK 1.5版本引入的一种语言特性,它极大地简化了基础数据类型(如int)与它们对应的包装类(如Integer)之间的转换过程。这一特性使得开发者无需手动创建包装类实例或使用显式类型转换,...

    《深入理解java虚拟机》自动装箱拆箱实例.doc

    ### 自动装箱与拆箱概念 在Java中,自动装箱是指将基本数据类型自动转换为对应的包装类对象的过程,而自动拆箱则是指将包装类对象自动转换为基本数据类型的逆过程。这一特性自Java 5起引入,极大地简化了开发者的...

    java装拆箱.ppt

    Java装箱和拆箱是Java语言中的一种特性,主要用于基本数据类型(如int、char、boolean等)与它们对应的引用类型(如Integer、Character、Boolean等包装器类)之间的转换。这种转换在处理集合框架、泛型以及某些高级...

    详解Java包装类及自动装箱拆箱

    "详解Java包装类及自动装箱拆箱" Java 包装类是 Java 语言中的一种基本类型...Java 包装类和自动装箱拆箱机制是 Java 语言中的一种重要机制,它可以使代码更加简洁和易读,但是需要注意性能问题和 equals 方法的使用。

    java编程中自动拆箱与自动装箱详解

    Java编程中自动拆箱与自动装箱详解 Java编程中自动拆箱与自动装箱是从J2SE 5.0开始提供的功能,可以简化Java编程中的数据类型转换。下面是自动拆箱与自动装箱的详细解释: 1. 基本数据类型的自动装箱(Autoboxing...

    java面试题大全整理版.docx

    本文档整理了Java面试题大全,涵盖了Java的基本概念、面向对象特征、访问修饰符、数据类型、自动装箱/拆箱机制等多个方面。 面向对象特征 Java是一种面向对象的编程语言,具有四个基本特征:抽象、继承、封装和多...

    Java拆箱与装箱实例详解

    Java中的拆箱(Unboxing)和装箱(Boxing)是自动类型转换的过程,涉及到基本类型(Primitive Types)与它们对应的引用类型(Wrapper Classes)之间的转换。这些转换在编程时可以提高代码的简洁性,但在处理大量数据...

    java陷阱之自动装箱共2页.pdf.zip

    Java语言在设计时引入了自动装箱和拆箱机制,以方便开发者在基本类型与它们对应的包装类之间进行转换。这个特性在提高编程效率的同时,也可能带来一些隐藏的陷阱,尤其是在性能敏感的代码中。本篇文章将深入探讨Java...

    Java包装类、拆箱和装箱详解.docx

    Java 包装类、拆箱和装箱详解 Java 语言作为一门典型的面向对象编程语言,然而其八种基本数据类型并不支持面向对象编程。这些基本类型的数据不具备“对象”的特性——不携带属性、没有方法可调用。为了解决这个问题...

    10万字总结java面试题和答案.pdf

    它针对Java语言特点、面向对象与面向过程的区别、Java基本数据类型及其封装类、标识符命名规则、instanceof关键字的作用以及Java自动装箱与拆箱的知识点进行了阐述。下面将详细展开这些知识点。 1. Java语言特点: ...

    Java面试汇总.pdf

    在Java面试汇总.pdf这份资料中,涵盖了Java语言的基础知识,包括Java的特性、面向对象与面向过程的区别、基本数据类型的大小和封装类、标识符命名规则、instanceof关键字的使用、Java的自动装箱与拆箱机制等关键知识...

    java包装类练习.doc

    Java 包装类型的自动装箱拆箱机制是指在(assign)操作时,Java 会自动地将基本类型转换为包装类型,或者将包装类型转换为基本类型。例如,Integer i = 100;这里的 i 就是自动装箱的结果。 5. Java 语句 Integer i=1;...

    Java基础教程2023年最新整理

    自动装箱和拆箱是Java中的一个重要机制,用于将基本类型和其对应的包装类型之间的赋值。自动装箱是将基本类型的值赋值给包装类型的对象,而拆箱是将包装类型的对象赋值给基本类型。 * 自动装箱:Integer x = 2; // ...

Global site tag (gtag.js) - Google Analytics