自动装箱和自动拆箱
1.摘要
众所周知,java是一门面向对象的编程语言,所以除了8大基础类型之外一切皆为对象,但为了方便面向对象开发,基础类型也有对应的包装类。但包装类和基础类型在赋值或比较时要进行类型转化,使用起来不够丝滑,感觉他们之间有很大的鸿沟。所以jdk1.5之后就推出了自动装箱和自动拆箱机制,使开发更加方便,代码更加简洁。自动装箱就是在编译时自动将基础类型转化与之对应的包装类,而自动拆箱刚好与之相反,就是在编译时自动将包装类转化为与之对应的基础类型。
2.基础类型
基础类型 | 包装类型 | 大小(字节) | 取值范围 | 默认值 | 缓存范围 |
---|---|---|---|---|---|
byte | Byte | 1 | -27, 27-1 | 0 | -128,127 |
short | Short | 2 | -215, 215-1 | 0 | -128,127 |
int | Integer | 4 | -231, 231-1 | 0 | -128,127 |
long | Long | 8 | -263, 263-1 | 0L | -128,127 |
char | Character | 2 | 0, 216-1 (\u0000,\uffff) | \u0000 | 0,127 |
float | Float | 4 | 1.4E-45, 3.4028235E38 | 0.0f | 无 |
double | Double | 8 | 4.9E-324, 1.7976931348623157E308 | 0.0d | 无 |
boolean | Boolean | 1 | true, false | false | true,false |
取值范围的计算过程:
对于有符号整型,我们在内存中存的是按照补码来存的,第一位表示符号,1为负数,0为正数。正数的补码为其本身,负数的补码为源码按位取反(反码),末位加1。用补码的好处是方便计算机的运算,计算机只需要用补码按位加就可以计算正负数的加减法。
对于基础类型的取值范围主要跟其大小有关,比如说char共有2字节(Byte),共16位(bit),所以能存储16个二进制位,一个二进制可以表示1或0,所以可以表示216个数,从零开始的话就可以表示到216-1。但有的类型是有正负数的,比如说short,我们要用一位二进制位来表示正负符号,所以就只有15位来表示数值,所以其取值是+0到+215-1,-215-1到-0。+0和-0我们只用+0一个,-0就可多表示一位负数所以其取值范围是-215到215-1。
对于浮点数来说有所不同,共有三个部分来表示,及符号位,阶码和尾数三部分组成。单精度共有32位,我们用1位表示符号,8位表示阶码,23位表示尾数。双精度共有64位,我们用1位表示符号,11位表示阶码,52位表示尾数。
3.自动拆箱
自动拆箱就是自动将包装类里对应的基本类型的值返回出来,其实是在编译时自动调用了包装类的xxxValue()方法。我们用包装类给基础类进行赋值、传参或他们之间进行比较(<,>,<=,>=,==)或运算的时候会自动拆箱。
我们可以看一下Integer的源码
private final int value;
public int intValue() {
return value;
}
4.自动装箱
自动装箱就是自动将基本类型转化成与之对应的包装类,其实是系统在编译时自动调用了包装类的valueOf()方法,但需要注意的是装箱时涉及缓存问题,如果该值在缓存范围内就在缓存中取,不在的话就新建一个对象,所以和直接new包装类的对象是不同的。我们用基础类型给包装类进行赋值或传参的时候会自动装箱。
4.1 Integer
由原码可知,Integer是有缓存的,其最小值是-128,最大值默认为127,可由jvm参数配置,如果比127小就取127,最大值和最小值的区间不能超过Integer的容量。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
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;//断言,如果IntegerCache.high<127就抛错
}
private IntegerCache() {}
}
jvm启动参数配置
-Djava.lang.Integer.IntegerCache.high=XXX;
4.2 Short
由源码可知,Short有缓存,其缓存区间为[-128,127]
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
private static class ShortCache {
private ShortCache(){}
static final Short cache[] = new Short[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Short((short)(i - 128));
}
}
4.3 Long
由源码可知, Long有缓存,其缓存区间为[-128,127]
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
4.4 Byte
由源码可知, Byte有缓存,其缓存区间为[-128,127]
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
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));
}
}
4.5 Character
由源码可知, Character有缓存,其缓存区间为[0,127]([\u0000,\u007F])
Character
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
4.6 Boolean
由源码可知, Boolean有缓存,其缓存值为true和false
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
4.7 Float
由源码可知,Float无缓存
public static Float valueOf(float f) {
return new Float(f);
}
4.8 Double
由源码可知,Double无缓存
public static Double valueOf(double d) {
return new Double(d);
}
5.思考
(1)浮点数为什么没有缓存
因为在一个指定的区间内有无数个浮点数
(2)判断以下程序得到输出结果
Integer a=new Integer(10);//新建对象
Integer b=new Integer(10);//新建对象
int c=10;
System.out.println(a==b);//false,不是同一个对象
System.out.println(a.equals(b));//true,两个对象的内容相同
System.out.println(a==c);//自动拆箱,true
System.out.println(b==c);//自动拆箱,true
Integer d=10; //自动装箱取缓存对象
Integer e=10;
System.out.println(d==e); //true,d、e指向的是从缓存中取的同一个对象
Integer g=128;
Integer h=128;
System.out.println(g==h);//false,自动装箱,由于该值不在缓存范围内,所以新建了两个对象
System.out.println(0.1+0.2==0.3);//false,有的小数转化为二进制会丢失精度,所以计算出的结果不是0.3