借鉴与:https://github.com/h2pl/Java-Tutorial/blob/master/docs/java/basic/8%E3%80%81Java%E8%87%AA%E5%8A%A8%E6%8B%86%E7%AE%B1%E8%A3%85%E7%AE%B1%E9%87%8C%E9%9A%90%E8%97%8F%E7%9A%84%E7%A7%98%E5%AF%86.md#%E5%AD%98%E5%9C%A8%E5%A0%86%E9%87%8C
引用类型:
对象、数组都是引用数据类型。
所有引用类型的默认值都是null。
一个引用变量可以用来引用任何与之兼容的类型。
例子:Site site = new Site(“Runoob”)。
自动拆箱和装箱(详解):
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类型的转换,这就叫自动装箱
简易实现:
在八种包装类型中,每一种包装类型都提供了两个方法:
静态方法valueOf(基本类型):将给定的基本类型转换成对应的包装类型;
实例方法xxxValue():将具体的包装类型对象转换成基本类型; 下面我们以int和Integer为例,说明Java中自动装箱与自动拆箱的实现机制。看如下代码:
class Auto //code1 { public static void main(String[] args)
{ //自动装箱 Integer inte = 10; //自动拆箱 int i = inte;
实现总结:
其实自动装箱和自动封箱是编译器为我们提供的一颗语法糖。在自动装箱时,编译器调用包装类型的valueOf()方法;在自动拆箱时,编译器调用了相应的xxxValue()方法。
自动装箱与拆箱中的“坑”
Integer源码:
public final class Integer extends Number implements Comparable { 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了,也就溢出了。 © 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);
了解基本类型缓存(常量池)的最佳实践
//基本数据类型的常量池是-128到127之间。
// 在这个范围中的基本数据类的包装类可以自动拆箱,比较时直接比较数值大小。
public static void main(String[] args) {
//int的自动拆箱和装箱只在-128到127范围中进行,超过该范围的两个integer的 == 判断是会返回false的。
Integer a1 = 128;
Integer a2 = -128;
Integer a3 = -128;
Integer a4 = 128;
System.out.println(a1 == a4);
System.out.println(a2 == a3);
Byte b1 = 127;
Byte b2 = 127;
Byte b3 = -128;
Byte b4 = -128;
//byte都是相等的,因为范围就在-128到127之间
System.out.println(b1 == b2);
System.out.println(b3 == b4);
//
Long c1 = 128L;
Long c2 = 128L;
Long c3 = -128L;
Long c4 = -128L;
System.out.println(c1 == c2);
System.out.println(c3 == c4);
//char没有负值
//发现char也是在0到127之间自动拆箱
Character d1 = 128;
Character d2 = 128;
Character d3 = 127;
Character d4 = 127;
System.out.println(d1 == d2);
System.out.println(d3 == d4);
结果
false true true true false true false true
总结:
通过上面的代码,我们分析一下自动装箱与拆箱发生的时机:
(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,这点也要注意。
本文介绍了“真实的”自动装箱与拆箱,为了避免写出错误的代码,又从包装类型的源码入手,指出了各种包装类型在自动装箱和拆箱时存在的陷阱,同时指出了自动装箱与拆箱发生的时机。