1、基本类型
Java基本类型共有八种,基本类型可以分为三类:
- 字符类型char
- 布尔类型boolean
- 整数类型byte、short、int、long
- 浮点数类型float、double。
Java中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。
基本类型的好处
我们都知道在Java语言中,new一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。
对于经常用到的类型,如int等,如果我们每次使用这种变量的时候都需要new一个Java对象的话,就会比较笨重。
所以,和C++一样,Java提供了基本数据类型,这种数据的变量不需要使用new创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。
2、包装类型
Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
下表是基本数据类型对应的包装器类型:
基本类型 | 包装类型 |
---|---|
int(4字节) | Integer |
byte(1字节) | Byte |
short(2字节) | Short |
long(8字节) | Long |
float(4字节) | Float |
double(8字节) | Double |
char(2字节) | Character |
boolean(未定) | Boolean |
3、自动装箱、拆箱
JDK5开始就提供自动装、拆箱的语法糖。这里的装箱和拆箱的概念描述的其实就是Java中这八种基本数据类型和对应的包装类型之间的转换过程。我们把基本数据类型转换成对应的包装类型的过程叫做装箱。反之就是拆箱。在Java中的装箱和拆箱不是人为操作的,是程序在编译的时候编译器帮助我们完成这项任务的,因此说它是自动的。
JDK5之前,如果要生成一个数值为10的Integer对象,必须这样进行:
Integer i = new Integer(10);
从JDK5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:
Integer i = 10;
4、自动装箱、拆箱如何实现的
以下面这段代码为例,查看其字节码
(
通过javac TestClass.java命令生成对应类的字节码文件
通过javap -verbose TestClass.class命令,反编译字节码文件
IDEA操作,可以通过右键复制类的路径,到对应类的所在目录下,输入命令行输入命令
)
public class Test {
public static void main(String[] args) {
Integer i=10;
int x=i;
}
}
从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。其他的也类似。
5、自动装箱和拆箱的触发时机
自动装箱和拆箱的触发时机,具体如下:
- 进行 = 赋值操作(装箱或拆箱)
- 进行+,-,*,/混合运算 (拆箱)
- 进行>,<,==比较运算(拆箱)
- 调用equals进行比较(装箱)
- ArrayList,HashMap等集合类 添加基础类型数据时(装箱)
6、自动拆装箱与缓存
首先,看下面一段代码,猜测其输出
public class Test {
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);
}
}
看上面的代码,可能会认为连个都为false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个i输出都是false的。
在Java中,==比较的是对象应用,而equals比较的是值。
结果:
原因就和Integer中的缓存机制有关。在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
查看Integer的valueOf源码
可以看出在valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。
Double、Float的valueOf方法的实现是类似的。
看下面这段代码
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);
}
对应的valueof方法
7、相关问题
1、
public class Test {
public static void main(String[] args) {
Integer num1 = 200;
int num2 = 200;
System.out.println(num1 == num2); //true
}
}
说明num1 == num2进行了拆箱操作
2、
public static void main(String[] args) {
Integer num1 = 100;
int num2 = 100;
System.out.println(num1.equals(num2)); //true
}
equals源码
我们指定equal比较的是内容本身,并且我们也可以看到equal的参数是一个Object对象,我们传入的是一个int类型,所以首先会进行装箱,然后比较,之所以返回true,是由于它比较的是对象里面的value值。
3、
public static void main(String[] args) {
Integer num1 = 100;
int num2 = 100;
Long num3 = 200l;
System.out.println(num1 + num2); //200
System.out.println(num3 == (num1 + num2)); //true
System.out.println(num3.equals(num1 + num2)); //false
}
1、当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。
2、对于num3.equals(num1 + num2)为false的原因很简单,我们还是根据代码实现来说明:
它必须满足两个条件才为true:
- 类型相同
- 内容相同
上面返回false的原因就是类型不同。
4、
public static void main(String[] args) {
Integer num1 = 100;
Integer num2 = 200;
Long num3 = 300l;
System.out.println(num3 == (num1 + num2)); //true
}
可以看到运算的时候首先对num3进行拆箱(执行num3的longValue得到基础类型为long的值300),然后对num1和mum2进行拆箱(分别执行了num1和num2的intValue得到基础类型为int的值100和200),然后进行相关的基础运算。
基础类型的测试
public static void main(String[] args) {
int num1 = 100;
int num2 = 200;
long num3 = 300;
System.out.println(num3 == (num1 + num2)); //true
}
总结
- 需要知道什么时候会引发装箱和拆箱
- 装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。
- equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱
- 当两种不同类型用==比较时,包装器类的需要拆箱。
当同种类型用
==比较时,会自动拆箱或者装箱
参考:https://www.cnblogs.com/wang-yaz/p/8516151.html
https://www.cnblogs.com/dolphin0520/p/3780005.html