3m网站源码,wordpress预解析,重庆网站建设解决方案及流程,无锡营销型网站建设参考链接#xff1a; Java中autoboxing自动装箱整数对象的比较
本文主要介绍Java中的自动拆箱与自动装箱的有关知识。 1、基本数据类型
基本类型#xff0c;或者叫做内置类型#xff0c;是Java中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。
Jav…参考链接 Java中autoboxing自动装箱整数对象的比较
本文主要介绍Java中的自动拆箱与自动装箱的有关知识。 1、基本数据类型
基本类型或者叫做内置类型是Java中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。
Java是一种强类型语言第一次申明变量必须说明数据类型第一次变量赋值称为变量的初始化。
Java基本类型共有八种基本类型可以分为三类
字符类型char
布尔类型boolean
整数类型byte、short、int、long
浮点数类型float、double。
Java中的数值类型不存在无符号的它们的取值范围是固定的不会随着机器硬件环境或者操作系统的改变而改变。
实际上Java中还存在另外一种基本类型void它也有对应的包装类java.lang.Void不过我们无法直接对它们进行操作。
基本数据类型有什么好处 我们都知道在Java语言中new一个对象是存储在堆里的我们通过栈中的引用来使用这些对象所以对象本身来说是比较消耗资源的。
对于经常用到的类型如int等如果我们每次使用这种变量的时候都需要new一个Java对象的话就会比较笨重。
所以和C一样Java提供了基本数据类型这种数据的变量不需要使用new创建他们不会在堆上创建而是直接在栈内存中存储因此会更加高效。
整型的取值范围
Java中的整型主要包含byte、short、int和long这四种表示的数字范围也是从小到大的之所以表示范围不同主要和他们存储数据时所占的字节数有关。
先来个简答的科普1字节8位bit。java中的整型属于有符号数。
先来看计算中8bit可以表示的数字
最小值10000000 -128(-2^7) 最大值01111111127(2^7-1) 整型的这几个类型中
bytebyte用1个字节来存储范围为-128(-2^7)到127(2^7-1)在变量初始化的时候byte类型的默认值为0。
shortshort用2个字节存储范围为-32,768 (-2^15)到32,767 (2^15-1)在变量初始化的时候short类型的默认值为0一般情况下因为Java本身转型的原因可以直接写为0。
intint用4个字节存储范围为-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1)在变量初始化的时候int类型的默认值为0。
longlong用8个字节存储范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1)在变量初始化的时候long类型的默认值为0L或0l也可直接写为0。
超出范围怎么办 上面说过了整型中每个类型都有一定的表示范围但是在程序中有些计算会导致超出表示范围即溢出。如以下代码
int i Integer.MAX_VALUE;
int j Integer.MAX_VALUE; int k i j;
System.out.println(i ( i ) j ( j ) k ( k ));
输出结果i (2147483647) j (2147483647) k (-2)
这就是发生了溢出溢出的时候并不会抛异常也没有任何提示。所以在程序中使用同类型的数据进行运算的时候一定要注意数据溢出的问题。 2、包装类型
Java语言是一个面向对象的语言但是Java中的基本数据类型却是不面向对象的这在实际使用时存在很多的不便为了解决这个不足在设计类时为每个基本数据类型设计了一个对应的类进行代表这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
包装类均位于java.lang包包装类和基本数据类型的对应关系如下表所示
基本数据类型 包装类
byte Byte
boolean Boolean
short Short
char Character
int Integer
long Long
float Float
double Double 在这八个类名中除了Integer和Character类以后其它六个类的类名和基本数据类型一致只是类名的第一个字母大写即可。
为什么需要包装类 很多人会有疑问既然Java中为了提高效率提供了八种基本数据类型为什么还要提供包装类呢
这个问题其实前面已经有了答案因为Java是一种面向对象语言很多地方都需要使用对象而不是基本数据类型。比如在集合类中我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。
为了让基本类型也具有对象的特征就出现了包装类型它相当于将基本类型“包装起来”使得它具有了对象的性质并且为其添加了属性和方法丰富了基本类型的操作。 3、拆箱与装箱
那么有了基本数据类型和包装类肯定有些时候要在他们之间进行转换。比如把一个基本数据类型的int转换成一个包装类型的Integer对象。
我们认为包装类是对基本类型的包装所以把基本数据类型转换成包装类的过程就是打包装英文对应于boxing中文翻译为装箱。
反之把包装类转换成基本数据类型的过程就是拆包装英文对应于unboxing中文翻译为拆箱。
在Java SE5之前要进行装箱可以通过以下代码
Integer i new Integer(10); 4、自动拆箱与自动装箱
在Java SE5中为了减少开发人员的工作Java提供了自动拆箱与自动装箱功能。
自动装箱: 就是将基本数据类型自动转换成对应的包装类。
自动拆箱就是将包装类自动转换成对应的基本数据类型。
Integer i 10; //自动装箱
int b i; //自动拆箱
Integer i10 可以替代 Integer i new Integer(10); 这就是因为Java帮我们提供了自动装箱的功能不需要开发者手动去new一个Integer对象。 5、自动装箱与自动拆箱的实现原理
既然Java提供了自动拆装箱的能力那么我们就来看一下到底是什么原理Java是如何实现的自动拆装箱功能。
我们有以下自动拆装箱的代码
public static void main(String[]args){ Integer integer1; //装箱 int iinteger; //拆箱
}
对以上代码进行反编译后可以得到以下代码 public static void main(String[]args){ Integer integerInteger.valueOf(1); int iinteger.intValue();
} 从上面反编译后的代码可以看出int的自动装箱都是通过Integer.valueOf()方法来实现的Integer的自动拆箱都是通过integer.intValue来实现的。如果读者感兴趣可以试着将八种类型都反编译一遍 你会发现以下规律
自动装箱都是通过包装类的valueOf()方法来实现的.自动拆箱都是通过包装类对象的xxxValue()来实现的。
6、哪些地方会自动拆装箱
我们了解过原理之后在来看一下什么情况下Java会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不介绍了那是最简单的也最容易理解的。
我们主要来看一下那些可能被忽略的场景。
场景一、将基本数据类型放入集合类
我们知道Java中的集合类只能接收对象类型那么以下代码为什么会不报错呢
ListInteger li new ArrayList();
for (int i 1; i 50; i ){ li.add(i);
}
将上面代码进行反编译可以得到以下代码
ListInteger li new ArrayList();
for (int i 1; i 50; i 2){ li.add(Integer.valueOf(i));
}
以上我们可以得出结论当我们把基本数据类型放入集合类中的时候会进行自动装箱。
场景二、包装类型和基本类型的大小比较
有没有人想过当我们对Integer对象与基本类型进行大小比较的时候实际上比较的是什么内容呢看以下代码
I
Integer a1;
System.out.println(a1?等于:不等于);
Boolean boolfalse;
System.out.println(bool?真:假);
对以上代码进行反编译得到以下代码
Integer a1;
System.out.println(a.intValue()1?等于:不等于);
Boolean boolfalse;
System.out.println(bool.booleanValue?真:假);
可以看到包装类与基本数据类型进行比较运算是先将包装类进行拆箱成基本数据类型然后进行比较的。
场景三、包装类型的运算
有没有人想过当我们对Integer对象进行四则运算的时候是如何进行的呢看以下代码
Integer i 10;
Integer j 20;
System.out.println(ij);
反编译后代码如下
Integer i Integer.valueOf(10);
Integer j Integer.valueOf(20);
System.out.println(i.intValue() j.intValue());
我们发现两个包装类型之间的运算会被自动拆箱成基本类型进行。
场景四、三目运算符的使用
这是很多人不知道的一个场景作者也是一次线上的血淋淋的Bug发生后才了解到的一种案例。看一个简单的三目运算符的代码
boolean flag true;
Integer i 0;
int j 1;
int k flag ? i : j;
很多人不知道其实在int k flag ? i : j;这一行会发生自动拆箱。反编译后代码如下
boolean flag true;
Integer i Integer.valueOf(0);
int j 1;
int k flag ? i.intValue() : j;
这其实是三目运算符的语法规范当第二第三位操作数分别为基本类型和对象时其中的对象就会拆箱为基本类型进行操作。
因为例子中flag ? i : j;片段中第二段的i是一个包装类型的对象而第三段的j是一个基本类型所以会对包装类进行自动拆箱。如果这个时候i的值为null那么久会发生NPE。自动拆箱导致空指针异常
场景五、函数参数与返回值
这个比较容易理解直接上代码了
//自动拆箱
public int getNum1(Integer num) { return num;
}
//自动装箱
public Integer getNum2(int num) { return num;
}
7、自动拆装箱与缓存
Java SE的自动拆装箱还提供了一个和缓存有关的功能我们先来看以下代码猜测一下输出结果
public static void main(String... strings) { Integer integer1 3; //通过反编译发现执行的是 Integer.valueOf(1); Integer integer2 3; if (integer1 integer2) System.out.println(integer1 integer2); else System.out.println(integer1 ! integer2); Integer integer3 300; Integer integer4 300; if (integer3 integer4) System.out.println(integer3 integer4); else System.out.println(integer3 ! integer4); }
我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的但是由于比较的是对象而对象的引用不一样所以会认为两个if判断都是false的。
在Java中比较的是对象应用而equals比较的是值。
所以在这个例子中不同的对象有不同的引用所以在进行比较的时候都将返回false。奇怪的是这里两个类似的if条件判断返回不同的布尔值。
上面这段代码真正的输出结果
integer1 integer2 integer3 ! integer4 原因就和Integer中的缓存机制有关。在Java 5中在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
适用于整数值区间-128 至 127。
只适用于自动装箱。使用构造函数创建对象不适用。
具体的代码实现可以阅读Java中整型的缓存机制一文这里不再阐述。
-- 源码 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); }
我们只需要知道当需要进行自动装箱时如果数字在-128至127之间时会直接使用缓存中的对象而不是重新创建一个对象。
扩展: int i 50; Integer i1 50; Integer i2 50; Integer i3 new Integer(50); Integer i4 new Integer(50); Integer i5 300; Integer i6 300; System.out.println(i i1);// truei1 自动拆箱变成基本类型,两基本类型比较值 System.out.println(i i3);// true i3自动拆箱变成基本类型,两基本类型比较值 System.out.println(i1 i2);// true; i1和i3都指向常量池中同一个地址 System.out.println(i1 i3);// false; 两个不同的对象 System.out.println(i3 i4);// false 两个不同的对象 System.out.println(i5 i6);// false; 自动装箱时如果值不在-128到127就会创建一个新的对象 System.out.println( 11.equals( 11 ));//false; 不同类型之间的额比较
其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过-XX:AutoBoxCacheMaxsize修改。缓存通过一个 for 循环实现。从小到大的创建尽可能多的整数并存储在一个名为 cache 的整数数组中。这个缓存会在 Integer 类第一次被使用的时候被初始化出来。以后就可以使用缓存中包含的实例对象而不是创建一个新的实例(在自动装箱的情况下)。
实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 127。后来在Java 6中可以通过java.lang.Integer.IntegerCache.high设置最大值。这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个-128到127范围呢因为这个范围的数字是最被广泛使用的。 在程序中第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。
/** * 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:AutoBoxCacheMaxsize} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ 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) { try { 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); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high h; cache new Integer[(high - low) 1]; int j low; for(int k 0; k cache.length; k) cache[k] new Integer(j); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high 127; } private IntegerCache() {}
}
在Boxing Conversion部分的Java语言规范(JLS)规定如下
如果一个变量p的值是
-128至127之间的整数(§3.10.1) true 和 false的布尔值 (§3.10.3) ‘\u0000’至 ‘\u007f’之间的字符(§3.10.4) 范围内的时将p包装成a和b两个对象时可以直接使用ab判断a和b的值是否相等。
补充: 注意Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。 Double、Float的valueOf方法的实现是类似的。
而其中double和float又有所不同我们就以double为例子贴出代码讨论
public class Test { /** * Double */ public static void first() { Double i1 100.0; Double i2 100.0; Double i3 200.0; Double i4 200.0; System.out.println(i1 i2);//false System.out.println(i3 i4);//false } /** * 测试方法 */ public static void main(String[] args) { first(); }
} 注意为什么上面的代码的输出结果都是false呢同样的我们依旧以Double类中的valueOf方法来讨论贴出源码就一目了然了 public static Double valueOf(double d) { return new Double(d); }
也就是说不管你的double是什么范围的值他都是给你返回一个新的对象。float同double就不过多赘述了。 8、自动拆装箱带来的问题
当然自动拆装箱是一个很好的功能大大节省了开发人员的精力不再需要关心到底什么时候需要拆装箱。但是他也会引入一些问题。包装对象的数值比较不能简单的使用虽然-128到127之间的数字可以但是这个范围之外还是需要使用equals比较。
前面提到有些场景会进行自动拆装箱同时也说过由于自动拆箱如果包装类对象为null那么自动拆箱时就有可能抛出NPE。
如果一个for循环中有大量拆装箱操作会浪费很多资源。
性能问题
首先在堆内存中创建对象的消耗肯定是要比使用栈内存要多的同时在自动拆装箱的时候也有一定的性能消耗如果在数据量比较大或者是循环的情况下频繁的拆装箱并且生成包装类的时候对性能的影响就是一星半点了所以不是特殊的需求例如上述被集合持有的情况还是使用基本类型而不是包装类
在循环的时候
Integer sum 0; for(int i1000; i5000; i){ sumi;
}
上面的代码sumi可以看成sum sum i在sum被操作符操作的时候会对sum进行自动拆箱操作进行数值相加操作最后发生自动装箱操作转换成Integer对象。其内部变化如下
sum sum.intValue() i;
Integer sum new Integer(result);
sum为Integer类型在上面的循环中会创建4000个无用的Integer对象在这样庞大的循环中会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时需要注意到这一点正确地声明变量类型避免因为自动装箱引起的性能问题
再举一个例子在Java中的HashMap的性能也受到自动拆装箱的影响 因为HashMap接收的参数类型是HashMap Object, Object所以在增删改查的时候都会对Key值进行大量的自动拆装箱为了解决这个问题Java提供了SparseArray包括SparseBoolMap, SparseIntMap, SparseLongMap, LongSparseMap,所接受的Key值都是基本类型的值例如SparseIntMap就是SparseIntMapint, Object,在避免了大量自动拆装箱的同时还降低的内存消耗。这里就点到为止具体的数据结构的问题我们就不深入了
2. 重载与自动装箱
在Java5之前value(int i)和value(Integer o)是完全不相同的方法开发者不会因为传入是int还是Integer调用哪个方法困惑但是由于自动装箱和拆箱的引入处理重载方法时稍微有点复杂例如在ArrayList中有remove(int index)和remove(Object o)两个重载方法如果集合持有三个Integer类型值为3,1,2的对象我们调用remove(3), 是调用了remove的哪个重载方法remove掉的是值为3的对象还是remove了index为3值为2的那个对象呢其实问题就是参数3是否会被自动打包呢答案是不会在这种情况下编译器不会进行自动拆装箱所以调用的是remove(int index),index为3值为2的这个Integer对象会被remove
通过以下例子我们可以验证
public void testAutoBoxing(int i){ System.out.println(primitive argument); } public void testAutoBoxing(Integer integer){ System.out.println(wrapper argument); } //calling overloaded method
int value 1;
test(value); //no autoboxing
Integer iValue value;
test(iValue); //no autoboxing Output:
primitive argument
wrapper argument
3. 缓存值问题
4. 警惕NullPointerException
我们在使用基本类型的时候在声明的时候即使我们没有对变量进行赋值编译器也会自动的为其赋予初始值比如int值就是0boolean就是flase所以我们在使用基本类型的时候是不会出现NullPointerException的但在使用包装类的时候我们就要注意这个问题了不能因为有自动拆装箱这个语法糖就忘记了包装类和基本类型的区别将其同等对待了如果你没有在使用包装类的时候通过显式、或是通过自动装箱机制为其赋值在你取出值、或是通过自动拆箱使用该值的时候就会发生NullPointerException这个是大家要注意的 链接https://www.jianshu.com/p/547b36f04239 https://www.jianshu.com/p/547b36f04239