在java中存在八大基本类型boolean、char、byte、short、int、long、float、double,这八种基本类型分别有对应的包装类(java.lang包下)Boolean、Character、Byte、Short、Integer、Long、Float、Double以供自动拆装箱使用。
自动装箱:自动将基本类型的数据转换为包装器类型。
自动拆箱:自动将包装器类型转换为基本类型。
以Integer为例:
Integer integer = 66; // 自动装箱
int i = integer; // 自动拆箱
此段代码的字节码形式为
L0
LINENUMBER 22 L0
BIPUSH 66
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 1
L1
LINENUMBER 23 L1
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 2
也就是说,此段代码自动转义为
Integer integer = Integer.valueOf(66);
int i = integer.intValue();
具体细节稍后再讲,下表为各包装类拆装箱调用的方法
基本类型 | 包装类 | 拆箱方法 | 装箱方法 |
boolean(1个字节) | java.lang.Boolean | booleanValue() | valueOf() |
char(2个字节) | java.lang.Character | charValue() | |
byte(1个字节) | java.lang.Byte | byteValue() | |
short(2个字节) | java.lang.Short | shortValue() | |
int(4个字节) | java.lang.Integer | intValue() | |
long(8个字节) | java.lang.Long | longValue() | |
float(4个字节) | java.lang.Float | floatValue() | |
double(8个字节) | java.lang.Double | doubleValue() |
这些包装类其实也没什么好讲的,在各个包装类的内部其实包含了相应的基本类型变量,他们都实现了java.io.Serializable接口能够序列化、以及java.langComparable接口实现了比较方法,同时提供了相应的String类型转化为对应类型的实例,以及对应类型的实例转化为String类型的实例,对于java.lang.Number的实现的整型与浮点类型的封装类,还提供了进制转换等操作,类名使用了final进行修饰,也就是无法被继承。其中大部分的内容在jdk1.5甚至jdk1.0中已经实现,本篇不会考虑那么多内容,只是重点关注一下内部的构造函数、拆装箱方法、hashCode()、equals()和静态缓存内容等。
1、Boolean
a.内部变量
public static final Boolean TRUE = new Boolean(true); // 静态true Boolean实例,做缓存使用
public static final Boolean FALSE = new Boolean(false); // 静态false Boolean实例,做缓存使用
private final boolean value; // 包含的实际基本变量内容
b.构造函数
public Boolean(boolean value) {
this.value = value;
}
可以看到,针对boolean类型的构造函数,其实现的是对内部基本类型boolean的value类型的初始化操作。无参构造函数因为虽然没有初始化value,但作为基本类型,其默认值为false。
c.拆箱方法booleanValue()
public boolean booleanValue() {
return value;
}
实际上booleanValue()方法返回的就是内部包含的实际基本类型的值。
d.装箱方法valueOf()
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
也就是说返回的是已经存在的Boolean实例变量,因为 static final类型的成员变量,在类加载时期就已经存在。
e.hashCode()
@Override
public int hashCode() {
return Boolean.hashCode(value);
}
public static int hashCode(boolean value) {
return value ? 1231 : 1237;
}
也就是说Boolean实例只会返回两种结果,在为true的状态下返回1231,在为false的状态下返回1237。
f.equals()
public boolean equals(Object obj) {
if (obj instanceof Boolean) {
return value == ((Boolean)obj).booleanValue();
}
return false;
}
如果传入参数是boolean基本类型的参数,其自然也涉及到自动装箱为Boolean类型的过程。
在判断的过程中,传入参数若不是Boolean类型,直接返回false;若是,则通过内部基本类型的等于号判断得出答案。
案例总结:
通过以上分析,得出以下结论
public class Main {
public static void main(String[] args){
Boolean b = new Boolean(true);
System.out.println("b == new Boolean(true):" + (b == new Boolean(true))); // false 两个新创建的Boolean实例比较内存地址
System.out.println("b.equals(true):" + b.equals(true)); // true 参数true通过自动装箱返回的是静态成员实例,再在equals方法中比较基本类型成员
System.out.println("b == Boolean.TRUE:" + (b == Boolean.TRUE)); // false 新创建实例和静态True实例比较内存地址
System.out.println("b.booleanValue() == Boolean.valueOf(true):" + (b.booleanValue() == Boolean.valueOf(true))); // true 新创建的实例内部基本类型和静态实例比较,静态实例自动拆箱
System.out.println("Boolean.valueOf(true) == b.booleanValue():" + (Boolean.valueOf(true) == b.booleanValue())); // true 得出结论:等号一侧为基本类型,则非基本类型自动拆箱
System.out.println("true == Boolean.TRUE:" + (true == Boolean.TRUE)); // true 与上一例子其实一样
}
}
其他基本类型也是和Boolean一样的实现思路。
2、Character
a.内部变量
private final char value;
与Boolean类似,内部包含一个对应的基本类型变量
b.内部缓存
不同的是,使用了静态内部类作为缓存,实现方式如下:
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);
}
}
内部使用了长度为128的数组实现,也就是说ASCII集当中的字符的实例变量在类加载过程中已经被填充值数组相应的位置。其作用稍后介绍。
c.构造函数
public Character(char value) {
this.value = value;
}
虽然在java中char的缺省值为‘\u0000’,但是Character没有提供无参构造函数。
d.拆箱方法
public char charValue() {
return value;
}
同样,其拆箱方法返回的是实例内部及基本类型变量。
e.装箱方法valueOf()
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
可以看到,当字符类型值属于ASCII码集的字符是,返回的是缓存中已经存在的实例;当值大于127时,才会返回新创建的实例。
f、hashCode()
@Override
public int hashCode() {
return Character.hashCode(value);
}
public static int hashCode(char value) {
return (int)value;
}
Character实例的hashCode()方法返回的即其内部字符变量转为int值得形式。
g、equals()
public boolean equals(Object obj) {
if (obj instanceof Character) {
return value == ((Character)obj).charValue();
}
return false;
}
equals()方法就没什么好介绍的了。
案例总结:
public class Main {
public static void main(String[] args){
Character c1 = 99; // 自动装箱 'c',Character.valueOf(99)
Character c2 = 129; // 自动装箱, 缓存中不存在,是新创建的实例
System.out.println("c1 == new Character('c'):" + (c1 == new Character('c'))); // false 缓存中实例与新建实例比较内存地址
System.out.println("c1 == Character.valueOf('c'):" + (c1 == Character.valueOf('c'))); //true 缓存中的实例自己比较自己
System.out.println("c2 == new Character((char) 129):" + (c2 == new Character((char) 129))); // false 两个新建实例的内存地址比较
System.out.println("c2 == Character.valueOf((char) 129)):" + (c2 == Character.valueOf((char) 129))); //true 两个新建实例的内存地址比较
}
}
3、Byte、Short、Integer、Long
这四个包装类分别是byte、short、int、long的包装类,都是整形实例,差别不大,干脆放在一块讲。
a、内部变量
// Byte
private final byte value; // [-2^7, 2^7 - 1]
// Short
private final short value; // [-2^15, 2^15 - 1]
// Integer
private final int value; // [-2^31, 2^31 - 1]
//Long
private final long value; // [-2^63, 2^63 - 1]
b、内部缓存
没错,整型类也是用了静态内部类缓存的机制,存储的是[-128,127]的整型实例(Integer默认是这个范围)。
// Byte
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));
}
}
// Short
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));
}
}
// 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"); // 这个值可以通过启动类参数设置为如“-XX:AutoBoxCacheMax=1000”设置
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() {}
}
// Long
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);
}
}
在Byte、Short、Long中,缓存的实例范围为[-128,127],而Integer的缓存范围为[-128,high],high的值可以通过“-XX:AutoBoxCacheMax=”设置,当没有设置或设置的大小小于127时,最高值取127,因为数组的最大长度为2^32 - 1,因此high的值应该小于2^32 - 1 - 128 - 1。
c.构造函数
就不贴代码了,它们都是有参构造函数。
d.拆箱方法
就不贴代码了,它们都是返回相应的基本成员变量。
e.装箱方法
// Byte
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
// Short
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);
}
// Integer
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// Long
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);
}
对于Byte、Short、Long来说,对于[-128,127](Byte就在这个区间内)在这个区间内的数值从返回缓存中的实例,其他数值返回的是新创建的实例;对于Integer来说,在[-128,high]的区间内,是从缓存当中取数,在这个区间之外是新创建的实例。
f.hashCode()
// Byte
@Override
public int hashCode() {
return Byte.hashCode(value);
}
public static int hashCode(byte value) {
return (int)value;
}
// Short
@Override
public int hashCode() {
return Short.hashCode(value);
}
public static int hashCode(short value) {
return (int)value;
}
// Integer
@Override
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
// Long
@Override
public int hashCode() {
return Long.hashCode(value);
}
public static int hashCode(long value) {
return (int)(value ^ (value >>> 32));
}
对于Byte、Short、Integer来说,其实相当于返回其包含的基本类型的值,只不过对于byte和short来说,需要强制转换;对于Long来说是其无符号右移32位与原值异或运算后的值,也就是说对于同一数值的Long实例,返回的hashCode也是能够保证一致的。
g.equals()
// Byte
public boolean equals(Object obj) {
if (obj instanceof Byte) {
return value == ((Byte)obj).byteValue();
}
return false;
}
// Short
public boolean equals(Object obj) {
if (obj instanceof Short) {
return value == ((Short)obj).shortValue();
}
return false;
}
// Integer
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
// Long
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
可以得出结论:对于不同类型的整型实例,即便是数值相等,调用equals方法返回的也是false。
案例总结:
public class Main {
public static void main(String[] args){
Byte b = new Byte((byte) 123);
Short s = new Short((short) 123);
Integer i = new Integer(123);
Long l1 = new Long(256L);
Long l2 = new Long(256L);
Long l3 = new Long(123L);
System.out.println("b.equals(i):" + (b.equals(i))); // false 不同的封装类,equals方法返回false
System.out.println("i == Integer.valueOf(123):" + (i == Integer.valueOf(123))); // false 新建的实例和缓存的实例内存地址比较
System.out.println("Integer.valueOf(123) == Integer.valueOf(123):" + (Integer.valueOf(123) == Integer.valueOf(123))); // true 都是缓存中同一个实例
System.out.println("Integer.valueOf(129) == Integer.valueOf(129):" + (Integer.valueOf(129) == Integer.valueOf(129))); // false >127 都是新建的实例
System.out.println("l1.hashCode() == l2.hashCode():" + (l1.hashCode() == l2.hashCode())); // true
System.out.println("l1.equals(l2):" + l1.equals(l2)); // true 数值相等
System.out.println("l1 == l2:" + (l1 == l2)); // false 不同实例
System.out.println("s.hashCode() == i.hashCode():" + (s.hashCode() == i.hashCode())); // true 返回包含的基本类型的int类型数据
System.out.println("i.hashCode() == l3.hashCode():" + (i.hashCode() == l3.hashCode())); // true
}
}
4、Float和Double
a.内部变量
// Float
private final float value;
// Double
private final double value;
b.装箱函数valueOf
// Float
public static Float valueOf(float f) {
return new Float(f);
}
// Double
public static Double valueOf(double d) {
return new Double(d);
}
Float 和 Double没有使用前面类似的缓存结构,因此每次都会返回一个新的实例对象。
c.拆箱函数
// Float
public float floatValue() {
return value;
}
// Double
public double doubleValue() {
return value;
}
d.hashCode()
实现较为复杂,涉及到了native函数,就不贴代码了。
e.equals()
同样不贴代码了。
案例总结:
public class Main {
public static void main(String[] args){
Double d1 = new Double(3.141592d);
Double d2 = 3.141592000d; // Double.valueOf(3.141592000d)装箱
System.out.println("d1.hashCode() == d2.hashCode():" + (d1.hashCode() == d2.hashCode())); // true
System.out.println("d1.equals(d2):" + (d1.equals(d2))); // true
System.out.println("d1 == d2:" + (d1 == d2)); // false
}
}