java中的8种基本数据类型:boolean byte char short int float double long
自动拆装箱的问题引入:
由于在一开始学习java的时候,”万物皆对象“这种面向对象的看问题方式,时刻围绕在脑海中。因为静态的变量和基本数据类型不属于对象,但是由8种基本数据类型的自动装拆箱解决了基本数据类型不是对象。
在jdk1.5中引入了自动拆装箱的新特性,在jdk1.5之前,我们想要使用integer类中的方法,我们先要把int变量变成integer类型,可以通过new Integer(intNumber) 或者调用Integer.valueOf(intNumber)方法
自动装拆箱何时发生?
1、在调用方法时把基本数据类型作为参数,但是参数的类型是基本数据类型的包装类时。
在学习过javaSe基础之后,我们知道通过使用集合框架ArrayList或者Map来添加元素的时候,添加的是Object对象,在这里引入ArrayList.add()的源码:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
1
2
3
4
5
6
7
8
9
10
11
对于源码的解读:首先我们来看看参数中的(E e),为什么是E ?而不是Object?因为E代表的是元素(集合中存储的是元素),我们平时在泛型中可以看到 <T extends List<T>> ,T 代表的是Type 类,再比如键值对中的Map<K,V>,K 表示的是Key,V 表示的是Value。E K V 等泛型在使用该参数之前就已经限定好了类型,如果赋值给Object的话,就不用再进行强制类型转换了。
首先把数组的长度+1,这个操作会导致modCount加一,这个变量的作用就是记录当前数组被操作的次数,然后直接把参数传进来的对象赋值给上一次长度位置的元素。返回true表示添加成功
当我们调用ArrayList.add()方法的时候,可以直接调用
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(10);
1
2
我们反编译这段代码:
public class AutoboxingTest
{
public AutoboxingTest()
{
}
public static void main(String args[])
{
ArrayList arrayList = new ArrayList();
arrayList.add(Integer.valueOf(100));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
可以看到,编译器在编译的时候,检测到arrayList.add()需要的是Integer对象,所以把int类型自动装箱成Integer类型。
2、 给基本数据类型的包装类赋值为基本数据类型的时候。
我们还是以Integer和int类型的变量作为例子:
public class AutoboxingTest2 {
public static void main(String[] args) {
Integer integer = 10;
}
}
1
2
3
4
5
6
7
继续反编译例子:
public class AutoboxingTest2
{
public AutoboxingTest2()
{
}
public static void main(String args[])
{
Integer integer = Integer.valueOf(10);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
什么时候自动装箱不起作用?
当我们要调用的方法中存在重载的时候,即基本类型数据作为唯一参数的方法与该基本类型包装类作为唯一参数的方法重载,这时候自动装箱不起作用。
例子:
public class InvalidateAutoboxing {
public void print(int num) {
System.out.println("i am int !");
}
public void print(Integer num) {
System.out.println("i am integer!");
}
}
public class InvalidateAutoboxingTest {
public static void main(String[] args) {
InvalidateAutoboxing invalidateAutoboxing = new InvalidateAutoboxing();
invalidateAutoboxing.print(5);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行结果:
这里写图片描述
使用自动装箱拆箱需要注意的地方:
1、循环与自动装箱拆箱
public class CirculateAndAutoboxingAndAutounboxing {
public static void main(String[] args) {
Integer sum = 0;
for (int i = 0; i < 200; i++) {
sum += i;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
反编译:
public class CirculateAndAutoboxingAndAutounboxing
{
public CirculateAndAutoboxingAndAutounboxing()
{
}
public static void main(String args[])
{
Integer sum = Integer.valueOf(0);
for(int i = 0; i < 200; i++)
sum = Integer.valueOf(sum.intValue() + i);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
反编译代码解读:由于sum是Integer类型,但是sum+=i 需要sum先自动拆箱成int类型(调用intValue()方法),进行相加之后,再自动装箱成Integer类型把结果赋给sum。
Integer.valueOf源码解析:
/**
* Returns a Integer instance representing the specified
* int value.
* If a new Integer instance is not required, this method
* should generally be used in preference to the constructor
* Integer(int), as this method is likely to yield
* significantly better space and time performance by caching
* frequently requested values.
*
* @param i an int value.
* @return a Integer instance representing i.
* @since 1.5
*/
public static Integer valueOf(int i) {
final int offset = 128;
if (i >= -128 && i <= 127) { // must cache
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
首先我们在源码说明中看到了该构造器缓存经常使用的数据以减少内存的使用和提高效率,Integer把缓冲区的上限脚标设置成128,如果传进来的数据i在-128~127之中,直接返回缓冲区中的IntegerCache.cache[i + 128] 位中的元素
IntegerCache的源码解读:
private static class IntegerCache {
private IntegerCache(){}
static final Integer cache[] = new Integer[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Integer(i - 128);
}
}
1
2
3
4
5
6
7
8
9
10
IntegerCache是Integer的内部类,并且内部数组的上限为256元素,在这个内部类中使用了静态代码块(在类加载的时只执行一次),把-128~127都缓存在数组中。所以以后调用的时候就可以直接返回Integer对象,而不用return new Integer(i); Integer Short Long的缓冲数组是一样的,但是Character的范围为0~127,Double和Float没有缓冲数组
话又说回来,刚刚我们在分析循环与自动装拆箱的使用需要注意:当参与的数值不在缓存的范围内,会产生大量的对象,这样会产生很多垃圾对象,增加GC的工作压力。
2、自动装拆箱与三元运算符造成的空指针异常(NPE)
public class AutounboxingWithConditionalOperator {
public static void main(String[] args) {
HashMap<String, Boolean> hashMap = new HashMap<String, Boolean>();
Boolean b = (hashMap != null ? hashMap.get("test") : false);
}
}
1
2
3
4
5
6
7
8
9
10
运行:
这里写图片描述
发生了空指针异常
如果在Map中添加了test这条数据:
public class AutounboxingWithConditionalOperator {
public static void main(String[] args) {
HashMap<String, Boolean> hashMap = new HashMap<String, Boolean>();
hashMap.put("test", true);
Boolean b = (hashMap != null ? hashMap.get("test") : false);
System.out.println(b);
}
}
1
2
3
4
5
6
7
8
9
10
运行:
这里写图片描述
我们再反编译刚刚NPE异常的代码:
public class AutounboxingWithConditionalOperator
{
public AutounboxingWithConditionalOperator()
{
}
public static void main(String args[])
{
HashMap hashMap = new HashMap();
Boolean b = Boolean.valueOf(hashMap == null ? false : ((Boolean)hashMap.get("test")).booleanValue());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
下面是hashMap.get(key)方法的说明:
Returns the value to which the specified key is mapped,
*or null if this map contains no mapping for the key.
由上面可以看出,由于Map.get(key)方法,如果没有指定的数据,返回的是null。
再由于三元运算符有如下定义:
The type of a conditional expression is determined as follows:
1、If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
2、If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
3、If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.
译文:
三元运算符的类型由以下情况决定:
1、如果第二和第三个操作结果拥有同样类型(这个类型可能为null),那么这个类型就是该三元运算符的结果类型
2、如果第二个操作和第三个操作结果其中一个的结果类型为基本数据类型,另外一个操作结果为可以装箱转换成与前面一种类型一致的包装类,那么这个三元运算符的运算结果类型为基本数据类型,即把包装类拆装。
3、如果在第二、三个操作结果中,有一个为null类型,另外一个为引用类型,那么这个三元运算符的表达式为引用类型。
综上所述,对于上面出现的情况,对于一个操作结果为基本类型,另外一个为包装类型的三元运算符表达式来说,为了避免NEP的产生,可以通过把它们变成同样类型(参考三元运算符定义的第一条)。
参考资料:
Conditional Operator ? :
Boxing Conversion
The Java™ Tutorials about Autoboxing and Unboxing
What is Autoboxing and Unboxing in Java
Java中的自动装箱与拆箱
要裝箱還是拆封
4.2 自動裝箱、拆箱
Java 自动装箱和拆箱
Java泛型中K T V E ? object等的含义
自动拆箱导致空指针异常