目录
装箱
概念:即将基本类型转换为对应的包装类型。它们的对应关系如下:
装箱的方法:
/*
装箱:将基本类型的数据转换成包装类型,下面以Integer类为例:
构造方法:
Integer(int value) 构造一个新的Integer对象,将基本类型int类型的值包装成Integer对象
Integer(String s) 构造一个新的Integer对象,将引用类型String类型的值包装转换成Integer对象
静态方法:
static Integer valueOf(int i) 返回一个表示指定int值的Integer实例对象
static Integer valueOf(String s) 返回指定的String值的Integer实例对象
*/
public class Demo {
public static void main(String[] args) {
/*
所以实现装箱有两种方法:
构造函数
Integer i1 = new Integer(10);
静态方法
Integer i2 = Integer.valueOf(10);
*/
Integer i1=new Integer(10);
System.out.println(i1);
Integer i2=Integer.valueOf(10);
System.out.println(i2);
}
}
拆箱
概念:从包装类对象转换成对应的基本类型。
方法:
/*
拆箱:从包装类对象转换成对应的基本类型
成员方法:
int intValue() Integer对象调用该方法将转换成int基本数据类型
*/
public class Demo {
public static void main(String[] args) {
/*
所以实现拆箱的一种方法:
Integer i1 = new Integer(10);
int i = i1.intValue();
*/
Integer i1 = new Integer(10);
int i = i1.intValue();
System.out.println(i);
}
}
自动装箱
概念:将基本类型的变量赋值给对应的包装类。
自动装箱发生的情况:
/*
自动装箱:直接将基本数据类型转换对应的包装类型,下面以int->Integer为例
自动装箱发生的情况:
1.直接将基本数据类型赋值给包装类
例如:Integer i = 10;
2.集合方法添加元素
例如:new ArrayList<Integer>().add(10);
*/
public class Demo {
public static void main(String[] args) {
// 1.直接将基本数据赋值给包装类
// 相当于 Integer i = new Integer(10); 或 Integer i = Integer.valueOf(10);
Integer i = 10;// 发生了自动装箱
// 2.集合添加元素
ArrayList<Integer> list = new ArrayList<>();
list.add(10);// 发生了自动装箱
}
}
自动拆箱
概念:将包装类型直接转换成对应的基本类型
自动拆箱发生的情况:
/*
自动拆箱:直接将包装类型转换成对应的基本类型(包装类型无法直接参与运算,属于对象)。下面以Integer->int为例
自动拆箱发生的情况:
1.包装类型发生运算的时候
例如:Integer i += 2;
2.集合中获取元素时
例如:new ArrayList<Integer>().get(2);
*/
public class Demo {
public static void main(String[] args) {
// 1.包装类型参与运算的时候发生自动拆箱
// 相当于 i = i.intValue()
Integer i = 2;// 发生了自动装箱
i = i + 2;// 发生了自动拆箱
// 2.获取集合中的元素
List<Integer> list = new ArrayList<>();
list.add(1);// 发生了自动装箱
list.add(2);// 发生了自动装箱
Integer integer = list.get(2);// 发生了自动拆箱
}
}
装箱、拆箱的深入了解
参考博客:深入剖析Java中的装箱和拆箱
参考博客:包装类的缓存及自动装箱、拆箱
在这篇博客中见到了比较有意思的是面试题,让我对装箱、拆箱有了更深刻的理解。
下面这个问题就是里面的一道面试题,询问输出的结果,在未看之前,觉得两个都是一样的,但输出结果确是大出所料:
public class Demo {
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);
}
}
/**
* 打印结果:
* true
* false
*/
下面认识一个javap命令,关于该命令的详解可以参考博客:https://www.cnblogs.com/frinder6/p/5440173.html
简单来说,javap命令就是对javac生成的class文件反编译,它的帮助如下:
我们要用到的就是javap -c命令参数。
使用CMD或者IDEA的Terminal跳转到Demo.class所在的目录下,然后执行javap -c Demo.class命令。
那么控制台就会打印下面的内容:
Compiled from "Demo.java"
public class Demo {
public Demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 100
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 100
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: sipush 200
15: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: astore_3
19: sipush 300
22: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
25: astore 4
27: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
30: aload_1
31: aload_2
32: if_acmpne 39
35: iconst_1
36: goto 40
39: iconst_0
40: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
43: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_3
其实我也看不懂写了些什么,不过可以看双斜杠后面的注释,这是属于Java范畴的。
比如:
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
意思是调用了java.lang包下的Integer类的valueOf()方法。
我们学过自动装箱、自动拆箱就知道Integer i1=100;就是对100进行了自动装箱,通过这里的注释我们明白了它的自动装箱是调用了Integer类的valueOf()方法来完成的。
Integer.valueOf(int i)方法正是将一个int类型的数装箱成Integer类型的。
除此之外,我们还需要明白"=="这个运算符的含义:如果“==”的左右两侧是基本数据类型,则比较的是值,如果是引用数据类型,那么比较的就是对象的地址值。
那么执行下下面的代码如何:
public class Demo {
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);
System.out.println("i1地址值:"+System.identityHashCode(i1));
System.out.println("i2地址值:"+System.identityHashCode(i2));
System.out.println("i3地址值:"+System.identityHashCode(i3));
System.out.println("i4地址值:"+System.identityHashCode(i4));
}
}
注:System.identityHashCode(obj)可以来打印一个对象在内存中的地址值。
由于不同机器打印的值可能不一样,所以这里截图展示:
可以看到i1和i2的地址值相等,而==比较的正是两个对象的地址值,所以为true,但i3和i4的地址值居然不想等,所以就为false了,那么为什么是这样呢?
从上面javap反编译出来的代码中可以得知调用了Integer.valueOf()方法,那么去Integer.valueOf()方法的源码一窥究竟吧。
该方法的源码是这样的,注意参数类型是int的,不是其他重载方法:
注意它有一个比较范围IntegerCache.low和IntegerCache.high,通过阅读源码知道low为-128,high为127。
而IntegerCache的源码为:
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 =
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() {}
}
从参考的博客来看,IntegerCache类是Java为包装类型采用的缓存机制,即在类加载时,初始化一定数量的常用数据对象(即常说的热点数据),放入缓存中,等到使用时直接命中缓存,减少资源浪费。
从下图来看,很多包装类都采用了缓存机制,除了Float和Double
其中i1和i2的值为100,正好在缓存类的范围内,就直接命中事先缓存好的Integer类对象,所以它们的地址值是一样的,都指向了同一个对象。
但i3和i4的值为200,超出了缓存类的范围,即超出了-128<=i<=127的范围,所以调用了new Integer(i),重新生成了一个Integer对象,故内存地址值不一样,所以返回false。
Byte、Short、Integer、Long中缓存类设置的范围都是-128到127。
Character类的缓存类中缓存数组的范围是128。
Boolean类只有一个TRUE或FALSE,没有缓存类:
TRUE和FALSE的值就是两个对象:
Float类和Double类没有缓存数组,所以都是创建新对象,所以内存地址不一样。
总结:
包装类 | 缓存类中的缓存数组(cache)的范围 |
---|---|
Byte | -128~127 |
Short | -128~127 |
Integer | -128~127 |
Long | -128~127 |
Float | 无 |
Double | 无 |
Character | 0~127 |
Boolean | 无,但对象不可变 |
所以,对于下面的结果应该很容易得出答案了。
public class Demo {
public static void main(String[] args) {
Byte b1 = 100;
Byte b2 = 100;
System.out.println(b1 == b2);
Short s1 = 100;
Short s2 = 100;
Short s3 = 200;
Short s4 = 200;
System.out.println(s1 == s2);
System.out.println(s3 == s4);
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
Long l1 = 100L;
Long l2 = 100L;
Long l3 = 200L;
Long l4 = 200L;
System.out.println(l1 == l2);
System.out.println(l3 == l4);
Float f1 = 100F;
Float f2 = 100F;
Float f3 = 200F;
Float f4 = 200F;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
Double d1 = 100D;
Double d2 = 100D;
Double d3 = 100D;
Double d4 = 100D;
System.out.println(d1 == d2);
System.out.println(d3 == d4);
Character c1 = 100;
Character c2 = 100;
Character c3 = 200;
Character c4 = 200;
System.out.println(c1 == c2);
System.out.println(c3 == c4);
Boolean boo1 = true;
Boolean boo2 = true;
Boolean boo3 = false;
Boolean boo4 = false;
System.out.println(boo1 == boo2);
System.out.println(boo3 == boo4);
}
}
/**
* 打印结果:
* true
* true
* false
* true
* false
* true
* false
* false
* false
* false
* false
* true
* false
* true
* true
*/
下面说下自动装箱和自动拆箱,看下面的代码,研究下运行结果:
public class Demo {
public static void main(String[] args) {
int i1 = 100;
Integer i2 = 100;// i2发生了自动装箱
System.out.println(i1 == i2);// i2由于参与了运算,所以发生了自动拆箱
System.out.println(i2.equals(i1));// i1发生了自动装箱
int i3 = 200;
Integer i4 = 200;// i4发生了自动装箱
System.out.println(i3 == i4);// i4由于参与了运算,所以发生了自动拆箱
System.out.println(i4.equals(i3));// i3发生了自动装箱
Long l5 = 200L;
System.out.println(i4 == (i1 + i2));// i2由于参与了运算,所以发生了自动拆箱,所以(i1+i2)是一个int类型,而i4又参与了运算,所以发生自动拆箱,所以为true
System.out.println(i4.equals(i1 + i2));
System.out.println(l5 == (i1 + i2));
System.out.println(l5.equals(i1 + i2));
}
}
/**
* 打印结果:
* true
* true
* true
* true
* true
* true
* true
* false
*/
解释如下:
public class Demo {
public static void main(String[] args) {
int i1 = 100;
Integer i2 = 100;// i2发生了自动装箱
/*
i2由于参与了运算,所以发生了自动拆箱,所以比较的是i1和i2的值,都是100,所以返回true
*/
System.out.println(i1 == i2);
/*
i1原本是int类型的,发生了自动装箱,变成了Integer类型,并且i1和i2的值还相等,所以返回true
*/
System.out.println(i2.equals(i1));
int i3 = 200;
Integer i4 = 200;// i4发生了自动装箱
/*
i3由于参与了运算,所以发生了自动拆箱,所以比较的是i3和i4的值,都是200,所以返回true
*/
System.out.println(i3 == i4);
/*
i3原本是int类型的,发生了自动装箱,变成了Integer类型,并且i3和i4的值还相等,所以返回true
*/
System.out.println(i4.equals(i3));
Long l5 = 200L;
/*
i2原本是Integer类型的,参与了加法(+)运算,发生自动拆箱,完成了与i1相加,得到的值是200,是int类型的
i4是Integer类型的,参与了运算,并且等号的右边是int,所以发生自动拆箱,比较值,都是200相等,返回true
*/
System.out.println(i4 == (i1 + i2));
/*
i2原本是Integer类型的,参与了加法(+)运算,发生自动拆箱,完成了与i1相加,得到的值是200,是int类型的,
但在equal()方法中又发生自动装箱,这时候都是Integer类型并且值相等,所以返回true
*/
System.out.println(i4.equals(i1 + i2));
/*
i2原本是Integer类型的,参与了加法(+)运算,发生自动拆箱,完成了与i1相加,得到的值是200,是int类型的
l5是Long类型的,参与了运算,并且等号的右边是int类型,所以发生了自动拆箱,比较值,都是200相等,返回true
*/
System.out.println(l5 == (i1 + i2));
/*
i2原本是Integer类型的,参与了加法(+)运算,发生自动拆箱,完成了与i1相加,得到的值是200,是int类型的,
但在equal()方法中又发生自动装箱,注意,这时候l5是Long类型,而(i1+i2)是Integer类型,所以类型不相等,直接返回false
*/
System.out.println(l5.equals(i1 + i2));
}
}
对于最后一个为false,可以查看下equals()方法的源码:
要同时类型相同并且值相等equals方法才会返回true。
执行javap -c命令,查看反编译代码:
Compiled from "Demo.java"
public class Demo {
public Demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 100
2: istore_1
3: bipush 100
5: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
8: astore_2
9: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
12: iload_1
13: aload_2
14: invokevirtual #4 // Method java/lang/Integer.intValue:()I
17: if_icmpne 24
20: iconst_1
21: goto 25
24: iconst_0
25: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
28: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
31: aload_2
32: iload_1
33: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
36: invokevirtual #6 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
39: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
42: sipush 200
45: istore_3
46: sipush 200
49: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
52: astore 4
54: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
57: iload_3
58: aload 4
60: invokevirtual #4 // Method java/lang/Integer.intValue:()I
63: if_icmpne 70
66: iconst_1
67: goto 71
70: iconst_0
71: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
74: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
77: aload 4
79: iload_3
80: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
83: invokevirtual #6 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
86: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
89: ldc2_w #7 // long 200l
92: invokestatic #9 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
95: astore 5
97: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
100: aload 4
102: invokevirtual #4 // Method java/lang/Integer.intValue:()I
105: iload_1
106: aload_2
107: invokevirtual #4 // Method java/lang/Integer.intValue:()I
110: iadd
111: if_icmpne 118
114: iconst_1
115: goto 119
118: iconst_0
119: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
122: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
125: aload 4
127: iload_1
128: aload_2
129: invokevirtual #4 // Method java/lang/Integer.intValue:()I
132: iadd
133: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
136: invokevirtual #6 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
139: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
142: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
145: aload 5
147: invokevirtual #10 // Method java/lang/Long.longValue:()J
150: iload_1
151: aload_2
152: invokevirtual #4 // Method java/lang/Integer.intValue:()I
155: iadd
156: i2l
157: lcmp
158: ifne 165
161: iconst_1
162: goto 166
165: iconst_0
166: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
169: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
172: aload 5
174: iload_1
175: aload_2
176: invokevirtual #4 // Method java/lang/Integer.intValue:()I
179: iadd
180: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
183: invokevirtual #11 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
186: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
189: return
}
既有装箱又有拆箱。
总结:
- 当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。
- 关于obj1.equals(obj2)方法,其中obj1和obj2的数据类型必须相同,并且值相等才会返回true,否则返回false。
- 当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
- 注意不要写出这样的代码,虽然编译通过,但不合法。Integer i=null; int n=i;