本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
在写项目的时候发现一个问题,
public boolean connect(){
}
还有一种写法
public Boolean connect(){
}
两种写法在运行中都无报错,那有何区别呢?
引发思考:这里两种写法有什么区别?
boolean 是 Java 的基本数据类型,Boolean 是 Java 的一个类。boolean 类型会在“赋零值”阶段给属性赋 false。而 Boolean 是一个类,会在“赋零值”阶段给对象赋 null。
如果是静态属性,会在类加载时被赋值。如果是普通类属性,会在实例化对象时赋值。这两点可以了解一下“类加载机制”和“对象创建过程”。
类加载机制:
-
加载:根据类的全名获取类的二进制字节流,将类加载进内存并在堆中生成一个代表这个类的
Class
对象,作为方法区数据的访问入口 -
验证:验证
class
文件中的字节流是否符合JVM
规范 -
准备:在方法区中为类的静态属性分配内存,并初始化默认值(
boolean
的默认值是false
,Boolean
的默认值是null
) -
解析:将常量池中的符号引用转化为直接引用,可以理解为对象引用转成指针
-
初始化:真正开始执行类中的代码,静态属性赋值和静态块
对象实例化过程:
-
检查类是否已经被加载(双亲委派)
-
给对象分配内存空间(指针碰撞)
-
零值初始化(
false / null
) -
设置对象头(对象分代年龄等信息)
-
执行
<init>
方法(属性初始化,语句块和构造方法)
所以说,Boolean只是被加载了,还没有被实例化,在被实例化之前并没有分配内存,所以是 null
查看源码,看看 Boolean 的属性和构造方法,了解一下它如何包装 boolean
关于 Boolean 使用过程中有一个风险点,阿里巴巴Java开发手册中写的非常好 简单来说就是,boolean
定义的属性一定要有值,如果 Boolean
对象值为 null
,解包过程中就会出现NPE。
NPE: NullPointerException,即空指针异常
public class Main {
public static void main(String[] args) {
Integer n = null;
int i = n;
}
}
运行报错
想象一种场景:你女票问你:你爱我吗?但你没听清。如果你是 Boolean
就会回答,我没听清(null),如果你是 boolean
就会回答,*** (NPE)
之后就会有个狗头被打爆。
包装类和基本类型
什么是包装类?
所谓包装类,就是将基本数据类型,用一个类进行了一层包装,可以按照引用类型进行使用。同时还提供了若干用来进行数据转换的操作。
想要对基本类型数据进行更多的操作,最方便的方式就是将其封装成对象。为啥呢?因为在对象描述中就可以定义更多的属性和行为对该基本数据类型进行操作。 基本数据类型的包装类是为了解决基本数据类型有些操作不方便带来的问题。
Java在设计之初有一个基本原则:一切皆对象,一切的操作都要求用对象的形式进行描述。但是这里面就会出现一个矛盾,基本数据类型不是对象。那么为了符合于这种要求,所以最早的时候可以采用人为的方式来解决此问题。
class MyInt { // 一个类
private int num; // 这个类包装的基本数据类型
// 构造的目的是为了将基本数据类型传递给对象
public MyInt(int num) { // 将基本类型包装类
this.num = num;
}
public int intValue() { // 将包装的数据内容返回
return this.num;
}
}
public class TestDemo {
public static void main(String args[]) {
MyInt mi = new MyInt(10); // 将int包装为类
int temp = mi.intValue(); // 将对象中包装的内容取出
// 只有取出包装数据之后才可以进行计算
System.out.println(temp * 2); // 结果为20
}
}
因为这样的实现是比较容易的,所以专门提供了一组包装类,来包装所有的基本类型,包装类由此而生。 包装的过程就称为装箱
装箱和拆箱
因为基本类型和包装类可以互相转换:
int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();
所以,Java编译器可以帮助我们自动在int和Integer之间转型:
Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用Integer.intValue()
这种直接把int
变为Integer
的赋值写法,称为自动装箱(Auto Boxing),反过来,把Integer
变为int
的赋值写法,称为自动拆箱(Auto Unboxing)。
注意:自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。
包装类有以下用途
- 集合不允许存放基本数据类型,故常用包装类
- 包含了每种基本类型的相关属性,如最大值,最小值,所占位数等
- 作为基本数据类型对应的类类型,提供了一系列实用的对象操作,如类型转换,进制转换等
基本类型与包装类型的区别
-
在Java中,一切皆对象,但八大基本类型却不是对象。
-
声明方式的不同,基本类型无需通过
new
关键字来创建,而包装类型需new
关键字。 -
存储方式及位置的不同,基本类型是直接存储变量的值保存在堆栈中能高效的存取,包装类型需要通过引用指向实例,具体的实例保存在堆中。
-
初始值的不同,封装类型的初始值为null,基本类型的的初始值视具体的类型而定,比如int类型的初始值为0,boolean类型为false;
-
使用方式的不同,比如与集合类合作使用时只能使用包装类型。
-
什么时候该用包装类,什么时候用基本类型,看基本的业务来定:这个字段允允许null值,就需要使用包装类型,如果不允许null值,,使用基本类型就可以了,用到比如泛型和反射调用函数,就需要用包装类!
所以最基本的一点区别是:Ingeter是int的包装类,int的初值为0,Ingeter的初值为null。除此之外还有区别,请看代码:
public class TestInteger {
public static void main(String[] args) {
int i = 128;
Integer i2 = 128;
Integer i3 = new Integer(128);
System.out.println(i == i2); //Integer会自动拆箱为int,所以为true
System.out.println(i == i3); //true,理由同上
Integer i4 = 127;//编译时被翻译成:Integer i4 = Integer.valueOf(127);
Integer i5 = 127;
System.out.println(i4 == i5);//true
Integer i6 = 128;
Integer i7 = 128;
System.out.println(i6 == i7);//false
Integer i8 = new Integer(127);
System.out.println(i5 == i8); //false
Integer i9 = new Integer(128);
Integer i10 = new Integer(123);
System.out.println(i9 == i10); //false
}
}
为什么i4和i5比是true,而i6和i7比是false呢?
关键就是看valueOf()函数了,这个函数对于-128到127之间的数,会进行缓存, Integer i4 = 127时,会将127进行缓存,下次再写Integer i5 = 127时,就会直接从缓存中取,就不会new了。所以i4和i5比是true,而i6和i7比是false。
而对于后边的i5和i8,以及i9和i10,因为对象不一样,所以为false。
以上的情况总结如下:
- 无论如何,
Integer
与new Integer
不会相等。不会经历拆箱过程,new出来的对象存放在堆,而非new的Integer常量则在常量池(在方法区),他们的内存地址不一样,所以为false。 - 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false。因为java在编译
Integer i2 = 128
的时候,被翻译成:Integer i2 = Integer.valueOf(128)
;而valueOf()
函数会对-128到127之间的数进行缓存。 - 两个都是new出来的,都为false。还是内存地址不一样。
- int和Integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比。