Java是面向对象的程序设计语言,讲究的是万物皆对象的理念,基本数据类型在某些场景下无法使用,如某个方法的参数为一个泛型,由于泛型在编译阶段是要进行类型擦除的,而基本数据类型不是对象,无法进行类型擦除,而包装类作为一个类可以实现泛型参数的设定,包装类可以向操作其它类一样简单操作对“基本数据类型进行操作”,其中的奥秘就是Java的自动装箱与拆箱机制。
如下的一个简单的例子说明了java中的装箱与拆箱:
package com.example.demo.Integer;
public class Test {
public static void main(String[] args) {
int a = 10;
// 自动装箱,将基本数据类型转换为基本数据类型包装类
Integer b = a;
Integer c = 20;
// 自动拆箱,将基本数据类型包装类转换为基本数据类型
int d = c;
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
Java 为每个原始类型提供了包装类型:
原始类型: boolean,char,byte,short,int,long,float,double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
基本数据类型包装类提供了更多更实用的方法,如hashCode方法,equals方法以及valueOf方法等等,功能比基本数据类型丰富。从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
基本数据类型包装类与基本数据类型比较
1)基本数据类型包装类重写了hashcode和equals方法还有其它比较实用的方法,如valueOf、parseInt、parseDouble、toHexString等数值的常用进制转换方法等等。
2)包装类可以为泛型参数,而基本数据类型不可以。例如我们定义一个List集合时,可以使用包装类型,但是却无法使用基本数据类型。
3)包装数据类型都实现了Serializable接口,所以可以序列化和反序列化。
4)基本数据类型的默认值与基本数据类型的包装类的默认值不同。包装类因为是类,所以包装类的默认值为null;基本数据类型,byte、short、int、long为0,boolean默认为false,float和double为0.0,char类型默认为空(不是null哦)
但在使用基本数据类型包装类时也有需要注意的地方,使用包装类型进行值比较时,建议使用equals方法,而不是使用==去进行值比较,这是因为包装类型中对一个范围的数字使用了一种类似类似缓存池的东西,也就是我们的主题基本数据类型包装类的高速缓冲区。下面通过几个例子来感受一下:
public class DataTest {
public static void main(String[] args) {
// 定义了一个字符串,通过包装类将字符串转换为基本数据类型int
String numberString = "252";
int number = Integer.parseInt(numberString);
// number 为基本数据类型,进行加4操作,返回的应该是基本数据类型中的int型
// 之所以能用Integer包装类型去接,是因为自动装箱,将int数据类型自动装箱
Integer hexInteger = number + 4;
// 可以对包装类型赋值像给基本数据类型中的int赋值一样,自动装箱
// int自动装箱调用了Integer中的valueOf方法(请注意此方法)
// 下面等同于 Integer equalsInteger = Integer.valueOf(256);
Integer equalsInteger = 256;
// 这里很容易理解,==比较的是两个对象的地址,hexInteger和equalsInteger
// 是两个不同的Integer对象,他们的地址是不同的,==比较结果是false
// 比较结果为false,但是如果我们将数换为64呢?==比较的结果如何???
System.out.println(hexInteger == equalsInteger);
// 包装类型都重写了equals方法,所以这里比较的是两个对象的值内容
System.out.println(hexInteger.equals(equalsInteger));
// 将数字转化为16进制字符串(实用工具)
System.out.println(Integer.toHexString(hexInteger));
}
}
程序运行结果如下:
但是如果我们在上述程序示例中将数字换成64呢?结果还是一样吗?
public class DataTest {
public static void main(String[] args) {
// 定义了一个字符串,字符串的内容为60
String numberString = "60";
// Integer中的parseInt方法返回值类型为Integer
// 之所以能用int类型去接,是因为自动拆箱,将包装类型拆箱为基本数据类型
int number = Integer.parseInt(numberString);
// number 为基本数据类型,进行加4操作,返回的应该是基本数据类型中的int型
// 之所以能用Integer包装类型去接,是因为自动装箱,将int数据类型自动装箱
Integer hexInteger = number + 4;
// 可以对包装类型赋值像给基本数据类型中的int赋值一样,自动装箱
// int自动装箱调用了Integer中的valueOf方法(请注意此方法)
Integer equalsInteger = 64;
// 比较结果为false,但是如果我们将数换为64呢?==比较的结果如何???
System.out.println(hexInteger == equalsInteger);
// 包装类型都重写了equals方法,所以这里比较的是两个对象的值内容
System.out.println(hexInteger.equals(equalsInteger));
// 将数字转化为16进制字符串
System.out.println(Integer.toHexString(hexInteger));
}
}
程序输出结果如下:
当改为64以后,为什么==比较也是true了呢?
这里就需要对装箱的实现以及装箱的概念有所了解。当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf,看看valueOf方法的源码就知道了,valueOf方法源码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
这里面有一个IntegerCache是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) {
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() {
}
}
简单的说,如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,超出这个范围的数值才会真正的new一个对象出来。所以上面的当我们把比较的两个数换成64以后,虽然表面上看是两个不同的对象,但其实由于数值在包装类的高速缓冲区中,他们的地址是相同的,所以才会有这两段程序的输出结果是不一样的结果。
扩展:基本数据类型包装类中的Byte、Short、Integer、Long的高频缓存范围为-128到127;Character的高频缓存为-128到127;Float、Double没有高频缓存区。Integer是唯一一个可以修改高频缓存范围的包装类。通过在VM optons中如下设置:-XX:AutoBoxCacheMax=8866 即修改缓存最大值为8866。
再看如下程序:
public class DataTest {
public static void main(String[] args) {
int a = 100;
Integer b = new Integer(100);
Integer c = new Integer(100);
System.out.println(a == b);
System.out.println(a == c );
System.out.println(b == c);
System.out.println(b.equals(a));
System.out.println(c.equals(a));
System.out.println(b.equals(c));
}
}
程序运行结果如下:
a与b、a与c使用= =比较,会将Integer包装类自动拆箱为基本数据类型中的int,进行值比较与a与b、a与c、b与c使用equals(包装类重新了equals方法)方法一样进行的都是值比较,所以是true;而b与c使用==进行比较的结果却为false,这是因为new出来的对象不会使用高频缓存范围的数值,是先创建对象,这两个对象是不同的对象,所以地址是不同的,返回false;(当然这么写代码,如果你的编程软件安装了阿里代码开发检测工具的时候是会有黄色警告的)