Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样 和八个基本数据类型对应的类统称为包装类 (Wrapper Class)。
基本数据类型和包装类的区别
- 初始值不同。包装类默认值为null,基本数据类型则不同的类型不一样
- 存储位置不同。基本数据类型存储在栈(stack)中,包装类则分成引用和实例,引用在栈(stack)中,具体实例在堆(heap)中。
- 声明和使用方式不同。包装类使用new初始化,有些集合类的定义不能使用基本数据类型,例如
ArrayList<Integer>
装箱和拆箱
基本类型和对应的包装类可以相互装换:
- 由基本类型向对应的包装类转换称为装箱,例如把 int 包装成 Integer 类的对象:
Integer i = Integer.valueOf(1); //手动装箱
Integer i = 1; //自动装箱
- 包装类向对应的基本类型转换称为拆箱,例如把 Integer 类的对象简化为 int。
int i1 = i.intValue(); //手动拆箱 (如果i为null, 则这行代码书写时不会报红,但编译不通过)
int i2 = i; //自动拆箱
jdk5.0开始增加自动装箱/拆箱
包装类的==与equals
- ==,比较的是地址值是否相同。
- 如果是Integer、Short、Byte、Character、Long这几个类型,直接返回缓存中的同一个引用,地址值相同。
- Integer类型有缓存[-128, 127]的对象。缓存上限可以通过配置jvm更改
- Byte,Short,Long类型有缓存(-128, 127)
- Character缓存[0, 127]
- Boolean缓存TRUE、FALSE
- 基本数据类型和包装类比较,会先把包装类拆箱,再去比较两边基本数据类型的数值。
- 如果其中有一个操作数是表达式(即包含算术运算),则两边分别进行自动拆箱,去比较两边的数值。
- 如果是Integer、Short、Byte、Character、Long这几个类型,直接返回缓存中的同一个引用,地址值相同。
- equals方法,比较的是包装的基本数据类型的值。
- 基本数据类型和包装类比较,会先把基本数据类型装箱,因为基本数据类型没有equals方法。
面试问题
1. 下面这段代码的输出结果是什么?
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2); // true
System.out.println(i3==i4); // false。
}
}
以上代码在经过编译时,会在声明的变量前家伙是那个valueOf方法,代码变成了:
public class Main {
public static void main(String[] args) {
Integer i1 = Integer.valueOf(100);
Integer i2 = Integer.valueOf(100);
Integer i3 = Integer.valueOf(200);
Integer i4 = Integer.valueOf(200);
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
看一下Integer的valueOf方法的源码:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
从这2段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在 [-128,127] 之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
上面的代码中 i1 和 i2 的数值为100,因此会直接从cache中取已经存在的对象,所以 i1 和 i2 指向的是同一个对象,而 i3 和 i4 则是分别指向不同的对象。
注意:
如果将
Integer换为Double或者Float,两个输出则都是False,因为这两个不取缓存
2. 下面这段代码的输出结果是什么?
public class Main {
public static void main(String[] args) {
Integer i1 = new Integer(100);
Integer i2 = new Integer(100);
Integer i3 = new Integer(100);
Integer i4 = new Integer(100);
System.out.println(i1==i2); // false
System.out.println(i3==i4); // false
}
}
原因:只有valueOf方法构造对象时会用到缓存,new方法等不会使用缓存!
小结:不会使用缓存的情况:
- Double、Float包装类
- new创建出的包装类
3. 下面这段代码的输出结果是什么?
关于equals
double i0 = 0.1;
Double i1 = new Double(0.1);
Double i2 = new Double(0.1);
System.out.println(i1.equals(i2)); //true 2个包装类比较,比较的是包装的基本数据类型的值
System.out.println(i1.equals(i0)); //true 基本数据类型和包装类型比较时,会先把基本数据类型装箱后再比
关于“==”
double i0 = 0.1;
Double i1 = new Double(0.1);
Double i2 = new Double(0.1);
System.out.println(i1 == i2); //false new出来的都是新的对象
System.out.println(i1 == i0); //true 基本数据类型和包装类比较,会先把包装类拆箱再比,“==”比的是地址。因为i1是拿i0创建的,所以地址一样
4. 下面这段代码的输出结果是什么?
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}
true
false
true
true
true
false
true
第一个和第二个输出结果没有什么疑问。第三句由于 a+b 包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。
alexyyek.github.io/2014/12/29/… zhuanlan.zhihu.com/p/65538963