Java的自动拆箱和装箱是Java语言的一颗语法糖
在本文中,笔者向大家介绍下Java中一个非常重要也非常有趣的特性,就是自动装箱与拆箱,并从源码中解读自动装箱与拆箱的原理,同时这种特性也留有一个陷阱。开发者如果不注意,就会很容易跌入这个陷阱。
自动装箱(Autoboxing)
定义
大家在平时编写Java程序时,都常常以以下方式来定义一个Integer对象:
- Integer i=100;
从上面的代码中,大家可以得知,i为一个Integer类型的引用,100为Java中的基础数据类型(primitive data type)。而这种直接将一个基础数据类型传给其相应的封装类(wrapper class)的做法,便是自动装箱(Autoboxing)。
在jdk 1.5中,自动装箱首次被引入。而在jdk 1.5之前,如果你想要定义一个value为100的Integer对象,则需要这样做:
- Integer i=new Integer (100);
原理
我们在以上代码“Integer i=100;”处打一个断点,跟踪一下。
接下来,我们可以看到,程序跳转到了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);
- }
换句话说,装箱就是jdk自己帮你完成了调用Integer.valueOf(100)。
拆箱(Unboxing)
定义
- Integer integer100=100;
- int int100=integer100;
从上面的代码中,大家可看出integer100为一个Integer类型的引用,int100为一个int类型的原始数据类型。但是,我们可以将一个Integer类型的对象赋值给其相应原始数据类型的变量。这便是拆箱。
拆箱与装箱是相反的操作。装箱是将一个原始数据类型赋值给相应封装类的变量。而拆箱则是将一个封装类的变量赋值给相应原始数据类型的变量。装箱、拆箱的名字也取得相当贴切。
原理
笔者相信大家也都猜到了,拆箱过程中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()方法的调用。对于以上的实验而言,便是调用integer100的intValue()方法,将其返回值赋给了int100。
扩展
实验1
- Integer integer400=400;
- int int400=400;
- System.out.println(integer400==int400);
在以上代码的第三行中,integer400与int400执行了==运行。而这两个是不同类型的变量,到底是integer400拆箱了,还是int400装箱了呢?运行结果是什么呢?
==运算是判断两个对象的地址是否相等或者判断两个基础数据类型的值是否相等。所以,大家很容易推测到,如果integer400拆箱了,则说明对比的是两个基础类型的值,那此时必然相等,运行结果为true;如果int400装箱了,则说明对比的是两个对象的地址是否相等,那此时地址必然不相等,运行结果为false。(至于为什么笔者对它们赋值为400,就是后面将要讲到的陷阱有关)。
我们实际的运行结果为true。所以是integer400拆箱了。对代码跟踪的结果也证明这一点。
实验2
- Integer integer100=100;
- int int100=100;
- System.out.println(integer100.equals(int100));
在以上代码的第三行中,integer100的方法equals的参数为int100。我们知道equals方法的参数为Object,而不是基础数据类型,因而在这里必然是int100装箱了。对代码跟踪的结果也证明了这一点。
其实,如果一个方法中参数类型为原始数据类型,所传入的参数类型为其封装类,则会自动对其进行拆箱;相应地,如果一个方法中参数类型为封装类型,所传入的参数类型为其原始数据类型,则会自动对其进行装箱。
实验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行中,integer100+int100就会得到一个类型为Integer且value为200的对象o,并执行这个对象的toString()方法,并输出”200”;
- 第5行中,integer100+int100就会得到一个类型为Integer且value为200的对象o,==运算将这个对象与long200对象进行对比,显然,将会输出false;
- 第6行中,integer100+int100就会得到一个类型为Integer且value为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<=IntegerCache.high时,直接返回IntegerCache.cache[i + 128]。其中,IntegerCache为Integer的内部静态类,其原码如下:
- 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'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的自动拆箱和装箱是Java语言的一颗语法糖。在之前的学习中有很多误解,在别人的帮助下作出一些修正。先看下面的代码:
1 public static void main(String args[]) { 2 Integer a = 1; 3 Integer b = 2; 4 Integer c = 3; 5 Integer d = 3; 6 Integer e = 321; 7 Integer f = 321; 8 Long g = 3L; 9 int x = 3; 10 long y = 3L; 11 12 //x,y虽然类型不同但是可以直接进行数值比较 13 System.out.println(x == y); 14 //System.out.println(c == g); 提示出错,不可比较的类型。说明此时没有自动拆箱 15 System.out.println(c == d); 16 System.out.println(e == f); 17 System.out.println(c == (a+b)); 18 System.out.println(c.equals(a+b)); 19 //此时进行了自动的拆箱 20 System.out.println(g == (a+b)); 21 System.out.println(g.equals(a+b)); 22 }
答案是:
T
T
F
T
T
T
F
这样的答案是不是出乎很多人的意料呢?我们一一来分析。
1. 首先我们明确一下"=="和equals方法的作用。
"==":如果是基本数据类型,则直接对值进行比较,如果是引用数据类型,则是对他们的地址进行比较(但是只能比较相同类型的对象,或者比较父类对象和子类对象。类型不同的两个对象不能使用==)
equals方法继承自Object类,在具体实现时可以覆盖父类中的实现。看一下Object中qeuals的源码发现,它的实现也是对对象的地址进行比较,此时它和"=="的作用相同。而JDK类中有一些类覆盖了Object类的equals()方法,比较规则为:如果两个对象的类型一致,并且内容一致,则返回true,这些类有:
java.io.file,java.util.Date,java.lang.string,包装类(Integer,Double等)。
2. Java的包装类实现细节。观察源码会发现Integer包装类中定义了一个私有的静态内部类如下:
1 private static class IntegerCache { 2 static final int low = -128; 3 static final int high; 4 static final Integer cache[]; 5 6 static { 7 // high value may be configured by property 8 int h = 127; 9 String integerCacheHighPropValue = 10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); 11 if (integerCacheHighPropValue != null) { 12 try { 13 int i = parseInt(integerCacheHighPropValue); 14 i = Math.max(i, 127); 15 // Maximum array size is Integer.MAX_VALUE 16 h = Math.min(i, Integer.MAX_VALUE - (-low) -1); 17 } catch( NumberFormatException nfe) { 18 // If the property cannot be parsed into an int, ignore it. 19 } 20 } 21 high = h; 22 23 cache = new Integer[(high - low) + 1]; 24 int j = low; 25 for(int k = 0; k < cache.length; k++) 26 cache[k] = new Integer(j++); 27 28 // range [-128, 127] must be interned (JLS7 5.1.7) 29 assert IntegerCache.high >= 127; 30 } 31 32 private IntegerCache() {} 33 }
而Integer的自动装箱代码:
1 public static Integer valueOf(int i) { 2 if (i >= IntegerCache.low && i <= IntegerCache.high) 3 return IntegerCache.cache[i + (-IntegerCache.low)]; 4 return new Integer(i); 5 }
通过观察上面的代码我们可以发现,Integer使用一个内部静态类中的一个静态数组保存了-128-127范围内的数据,静态数组在类加载以后是存在方法区的,并不是什么常量池。在自动装箱的时候,首先判断要装箱的数字的范围,如果在-128-127的范围则直接返回缓存中已有的对象,否则new一个新的对象。其他的包装类也有类似的实现方式,可以通过源码观察一下。
3. "=="在遇到非算术运算符的情况下不会自动拆箱,以及他们的equals方法不处理数据类型转换的关系。
因此,对于 System.out.println(c == d); 他们指向同一个对象,返回True。
对于 System.out.println(e == f); 他们的值大于127,即使值相同,但是对应不同的内存地址,返回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。
4. 总结
对于不懂的地方,最好是通过阅读源码的方式来解决。这样才能真正明白内部的一些实现方式。
Java的自动拆箱和装箱是Java语言的一颗语法糖相关推荐
- java中的自动拆箱和装箱(以及NEP问题)
java中的自动拆箱和装箱 1.回顾知识点 java中的8种基本数据类型,可以分为三类 字符类型 char 布尔类型 boolean 整数类型 byte , short , int , long 浮点 ...
- Java包装类、拆箱和装箱详解
虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备"对象"的特性--不携带属性.没有方法可调用. 沿用它们只是为了迎合 ...
- java装箱和拆箱的意义_java的自动拆箱和装箱是每个程序员都要知道的
自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西. 自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动 ...
- java中的拆箱和装箱是指什么_一文带你理解Java中自动装箱和拆箱
Java中自动装箱和拆箱 装箱(Boxing),也称为包装(Wrapper),是在对象中放置原语类型(primitive type)的过程,以便原语(primitive)可以作为引用对象使用. 这里的 ...
- java double 装箱_Java自动拆箱和装箱
一.什么是装箱/拆箱 在讲之前,得先提一下为什么两个概念:基本数据类型及其包装类,我们都知道Java是一种面向对象的语言,但是Java中的基本数据类型是不面向对象的,这时在使用中便会存在诸多的不便,为 ...
- java 基本数据类型的自动拆箱与装箱
--> -128~127之间的特殊性.为什么要这样设计,好处? --> 享元模式(Flyweight Pattern):享元模式的特点是,复用我们内存中已存在的对象,降低系统创建对象实 ...
- Java中的拆箱与装箱
我们先来了解一下拆箱与装箱的概念: 装箱:将基本数据类型转换为包装类: 拆箱:将包装类转换为基本数据类型 我们来看两串代码: Integer b1 = 127;Integer b2 = 127;Sys ...
- 【JAVA】谈谈拆箱与装箱
谈谈装箱与拆箱 一.何为包装类型 Java是一种面向对象的语言,但是它不是纯面向对象的.Java中存在基本数据类型,谈不上对象.为了向纯面向对象靠拢,Java5的时候推出了基本数据类型的包装类型. 基 ...
- java的自动拆箱会发生NPE
平时的小细节,总能在关键时刻酿成线上事故,最近在代码中使用了Integer的自动拆箱功能,结果NPE(NullPointException)了,悲剧啊... 一.何为自动拆箱 要说自动拆箱,就必须说自 ...
- Java基础之拆箱和装箱
一.什么是拆箱和装箱 1)什么是拆箱 • 拆箱:将包装类类型转换为基本数据类型 • 拆箱调用Integer.intValue方法 2)什么是装箱 • 装箱:将基本数据类型转换为包装类类型 • 装箱调用 ...
最新文章
- 使用MOSS2007内置的更多FieldType
- python 7-10梦想的度假胜地_7-8----7-10练习
- python写的游戏怎么给别人玩-一步步教你怎么用python写贪吃蛇游戏
- 2019春季学期第四周作业
- pyinstaller相关错误
- 计算机运行一段时间黑屏,电脑运行一段时间之后间歇性黑屏,黑屏一秒钟恢复,过几秒又黑屏,是显卡问题还是cpu问题...
- c 计算机操作步进器,自制AT89C2051驱动步进电机的电路
- 降噪耳机简介及降噪技术-ANC、ENC、DSP、CVC
- html2canvas 下载图片 报网络错误
- JS(JavaScript)中实现深浅拷贝的几种方式(详细阅读 非常重要)。
- 高阶函数,太有用啦!
- Python爬虫进阶(十):实战,Scrapy爬取贴吧
- 第一届“GBASE技术文章”有奖征文圆满收官
- 安娜尔机器人冻结资金设置_安娜尔机器人冻结资金设置如何
- 百度地图离线化(API v=1.3)
- 无人机倾斜摄影测量测绘技术的实际应用
- Unity 3D追踪效果的实现 目标箭头指引
- 通过微信搜一搜功能制作自己喜欢的表情
- 【微信小程序】简易音乐播放器,进度条拖拉、音乐的播放与暂停
- JZOJ3815. 【NOIP2014模拟9.7】克卜勒