对应关系
上一篇博文中我已经在使用包装类,说明了其对应基本数据类型的一些属性,我们直接上他们之间的对应关系:
基本数据类型 | 包装类 |
---|---|
boolean | Boolean |
byte | Byte |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Dounle |
两者区别
我个人觉得两者最主要的区别是:包装类是引用数据类型而另一个是基本数据类型。
结构
- 基本数据类型有且仅有一个数值
- 包装类是一个对象。除了数值,还有其特有的属性(成员变量)和方法
默认值
- 基本数据类型
boolean
默认值为false
,其他默认值为0
其中char
中的0
表示空格 - 包装类的默认值都为
null
内存存储位置
- 基本数据类型在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中
- 引用数据类型其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址
值传递方式
- 基本数据类型是数值传递
- 引用数据类型是引用传递(指针传递)
两者互相转换
Jdk1.5之前,基本数据类型要转换成包装类需要调用包装类的valueOf
方法。以int为例,则是java.lang.Integer.valueOf(int)
。而由Integer转化成int又需要调用java.lang.Integer.intValue()
。
从Jdk1.5之后,基本数据类型可以直接赋值给对应的包装类,非空包装类也可以直接赋值给对应的基本数据类型。为null
的包装类直接赋值给基本数据类型会抛出java.lang.NullPointerException
。
为什么Jdk1.5之后就可以通过直接赋值的方式完成转换呢?实际上这个就是我们经常提及的自动拆装箱。自动拆装箱其实是Java中的一个语法糖,实际java文件在编辑后会自动调用valueOf
和intValue
方法。
看下面的简单例子:
public static void main(String[] args) {
Integer a = 100;
int b = a;
}
在eclipse自带的反编译插件看下这段代码编译后的class:
public static void main(java.lang.String[] args);
0 bipush 100
2 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [16]
5 astore_1 [a]
6 aload_1 [a]
7 invokevirtual java.lang.Integer.intValue() : int [22]
10 istore_2 [b]
11 return
Line numbers:
[pc: 0, line: 5]
[pc: 6, line: 6]
[pc: 11, line: 7]
Local variable table:
[pc: 0, pc: 12] local: args index: 0 type: java.lang.String[]
[pc: 6, pc: 12] local: a index: 1 type: java.lang.Integer
[pc: 11, pc: 12] local: b index: 2 type: int
}
虽然这个反编译代码看着不直观,但是我们也可以清楚的看到在标注的第2行和第7行其分别调用了valueOf
和intValue
方法。
数据缓存
这部分是我们经常会忽视的内容。我们直接上例子:
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
Integer c = new Integer(100);
Integer d = 200;
Integer e = 200;
System.out.println(a == b);
System.out.println(b == c);
System.out.println(d == e);
}
这个代码运行出来的结果是什么?
true
false
false
为什么会出来这个结果?a == b
是true
为什么d == e
又是false
?
答案我们还是要从源码中找,这边赋值是一个自动装箱的过程我们直接看下对应的装箱方法:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看到当int值在最小值(-128)和最大值(默认是127)之间时会直接取得缓存数组中的值。再这范围之外的都会重新new
一个Integer
。
所以上面的结果也好理解了,100
在缓存范围内所以a
和b
实际是同一个对象故其内存地址实际是一个从而结果就是true
。而200
是在缓存之外所以是两个新的对象对应了两个内存地址,自然就是false
。
b == c
是false
,说明直接new
出的不会走缓存。从源码中会看到其构造方法只是做了一个简单的处理:
public Integer(int value) {
this.value = value;
}
除了Integer
还有那些包装类也有缓存,通过源码我们得到下面结论:
类型 | 是否有缓存 | 缓存范围 |
---|---|---|
Boolean | √ | true/false |
Byte | √ | -128~127 |
Short | √ | -128~127 |
Character | √ | 0~127 |
Integer | √ | -128~127(默认) |
Long | √ | -128~127 |
Float | × | - |
Double | × | - |
其他
Java作为一个面向对象的语言其包装类已经拥有了基本数据类型的全部功能了为什么还要基本数据类型?
对于这个问题顶级大佬们有过很多讨论,而保留的原因主要是因为性能:
- 包装类比基本基本数据类型占用更多的内存空间
- 基本数据类型使用的是栈内存要比堆内存快的多
- CPU能直接支持基本数据类型的四则运算和位运算
最后再上一个小问题(这个问题应该上一篇博文留较好):
下面程序输出结果是什么?
public static void main(String[] args) {
System.out.println((Integer.MAX_VALUE + 1) == Integer.MIN_VALUE);
System.out.println(Integer.MIN_VALUE - 1 == Integer.MAX_VALUE);
}
A : false false B : true true
C : 编译错误 D : 运行错误
答案在下次揭晓。感谢大家的阅读也欢迎您把这篇文章分享给更多的朋友一起阅读!本文有理解错误或存在误导性的地方欢迎私信指出,谢谢大家!