Java自动装箱和拆箱

参考:

博客园

廖雪峰的官方网站

知乎专栏

前置:

Java为每种基本数据类型都提供了包装器类型。

image-20211028125119235

  • 为什么要有包装器类型?

    1. 包装类多了一个Null值,增加了表达性;

    2. 让基本类型也具有对象的特征,(以致于能够)兼容集合,泛型的使用(PS.容器装的都是Object);

    3. 包装类可以数据缓存;

    4. 包装类里面有一些很有用的方法和属性,如HashCode,ParseInt。

  • 什么时候用包装类,什么时候用基本类型?

    1. 在pojo类中定义的属性用包装类

    2. 在rpc方法中定义参数和返回值的类型用包装类

    3. 定义局部变量用基本类型

包装类的特点

  1. 不变类

所有的包装类型都是不变类。我们查看Integer的源码可知,它的核心代码如下:

1
2
3
public final class Integer {
private final int value;
}

因此,一旦创建了Integer对象,该对象就是不变的。

对两个Integer实例进行比较要特别注意:绝对不能用==比较,因为Integer是引用类型,必须使用equals()比较。(绝不能因为Java标准库的Integer内部有缓存优化就用==比较,必须用equals()方法比较两个Integer。)

  1. 数据缓存
1
2
3
4
5
6
7
8
9
10
11
12
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);
System.out.println(i3==i4);
}
}

输出:

1
2
true
false

原因:

Integer的valueOf方法的具体实现:

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

各包装类缓存的取值范围:

· Boolean:使用静态 final 定义;

· Byte:缓存区 -128~127;

· Short:缓存区 -128~127;

· Character:缓存区 0~127;

· Long:缓存区 -128~127;

· Integer:缓存区 -128~127;

· Float 和 Double 不会有缓存。

PS:

为什么Double类,Float类的valueOf方法会采用与Integer类的valueOf方法不同的实现?

在某个范围内的整型数值的个数是有限的,而浮点数却不是。

  1. 创建实例的方法

因为Integer.valueOf()可能始终返回同一个Integer实例,因此,在我们自己创建Integer的时候,以下两种方法:

  • 方法1:Integer n = new Integer(100);
  • 方法2:Integer n = Integer.valueOf(100);

方法2更好,因为方法1总是创建新的Integer实例,方法2把内部优化留给Integer的实现者去做,即使在当前版本没有优化,也有可能在下一个版本进行优化。

我们把能创建“新”对象的静态方法称为静态工厂方法。Integer.valueOf()就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。

创建新对象时,优先选用==静态工厂方法==而不是new操作符。

如果我们考察Byte.valueOf()方法的源码,可以看到,标准库返回的Byte实例全部是缓存实例,但调用者并不关心静态工厂方法以何种方式创建新实例还是直接返回缓存的实例。

  1. 提供的方法
  • 进制转换
1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
System.out.println(Integer.toString(100)); // "100",表示为10进制
System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制
System.out.println(Integer.toHexString(100)); // "64",表示为16进制
System.out.println(Integer.toOctalString(100)); // "144",表示为8进制
System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制
}
}

  • 解析

parseInt()把字符串解析成一个整数:

1
2
int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析
  • 获得基本类型

所有的整数和浮点数的包装类型都继承自Number,因此,可以非常方便地直接通过包装类型获取各种基本类型:

1
2
3
4
5
6
7
8
// 向上转型为Number:
Number num = new Integer(999);
// 获取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();
  • 处理无符号整型

在Java中,并没有无符号整型(Unsigned)的基本数据类型。byteshortintlong都是带符号整型,最高位是符号位。而C语言则提供了CPU支持的全部数据类型,包括无符号整型。无符号整型和有符号整型的转换在Java中就需要借助包装类型的静态方法完成。

例如,byte是有符号整型,范围是-128+127,但如果把byte看作无符号整型,它的范围就是0255。我们把一个负的byte按无符号整型转换为int

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
byte x = -1;
byte y = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(y)); // 127
}
}

因为byte-1的二进制表示是11111111,以无符号整型转换后的int就是255

类似的,可以把一个short按unsigned转换为int,把一个int按unsigned转换为long

自动装箱和拆箱(Auto Boxing)

1
2
Integer i = 10;  //装箱
int n = i; //拆箱

自动将基本数据类型转换为包装器类型–>自动装箱

自动将包装器类型转换为基本数据类型–>自动拆箱

装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。

底层实现:https://www.cnblogs.com/dolphin0520/p/3780005.html

面试题

题1:

1
2
3
4
5
6
7
8
9
10
11
12
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);
System.out.println(i3==i4);
}
}

输出:

1
2
true
false

解释:

包装类数据缓存,Integer类[-128,127],IntegerCache.cache中缓存的是同一个对象,超过该范围创建新对象。

题2:

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {

Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;

System.out.println(i1==i2);
System.out.println(i3==i4);
}
}

输出:

1
2
false
false

解释:

Double,Float无缓存

题3:

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {

Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true;

System.out.println(i1==i2);
System.out.println(i3==i4);
}
}

输出:

1
2
true
true

解释:

面是Boolean的valueOf方法的具体实现:

1
2
3
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}

  而其中的 TRUE 和FALSE又是什么呢?在Boolean中定义了2个静态成员属性:

1
2
3
4
5
6
7
public static final Boolean TRUE = new Boolean(true);

/**
* The <code>Boolean</code> object corresponding to the primitive
* value <code>false</code>.
*/
public static final Boolean FALSE = new Boolean(false);

题4:

谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

  当然,这个题目属于比较宽泛类型的。但是要点一定要答上,我总结一下主要有以下这两点区别:

  1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;

  2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。

题5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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));
}
}

输出:

1
2
3
4
5
6
7
true
false
true
true
true
false
true

解释:

当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。

对于包装器类型,equals方法并不会进行类型转换。

3.第三句由于 a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。

4.而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。

6.Long.equals(obj)是先判断obj是否是Long或者Long的子类,否则直接返回false;而g.equals(a+b),a+b是先经过拆箱相加然后将结果再装箱,
最终是Long.equals(Integer)–>Long instanceof Intger返回false

7.对于a+h,先自动触发拆箱,就变成了int类型和long类型相加,这个会触发类型晋升,结果是long类型的,然后会触发装箱过程,就变成Long了。因此比较结果是true。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2022 Doke
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信