JavaBean使用包装类型还是基本类型?

深入了解基本类型和包装类

了解基本数据类型和包装类的区别,最终了解:什么时候应该使用基本数据类型,什么时候应该使用包装类

开始

我们知道,Java中有八种基本数据类型,它们又对应八种包装类型。那么,回想一下你在代码中使用基本数据类型或包装类型时,选择标准是什么?有没有标准呢?今天,我们就来了解一下包装类型。

不妨也先这道题开始思考。输出结果是什么?

img

基本知识

基本数据类型 对应包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

Java有八种基本类型,byte, short, int, long, float, double, char, boolean。

对应八种包装类,Byte, Short, Integer, Long, Double, Character, Boolean。

以int为例,创建一个基本类型的变量时,基本类型如下:

int a = 0;

包装类型如下:

Integer a = new Integer(0);

内存方式

基本类型属于原始数据类型,变量中存储的就是原始值。包装类型属于引用数据类型,变量中存储的是存储原始值的地址的引用。

我们先不考虑内存空间各种不同区域对应不同的用途,将其看做一个整体,思考以下语句的内存分配过程。

int a = 0;

分配一块内存,即为a,存入的值为0。

Integer b = new Integer(0);

分配一块内存,我们称之为内存1,存储0,再分配一块内存,我们称之为内存2。内存2中存放的是指向内存1的引用。

==和equals

基本类型的比较要用==,包装类型的比较要用equals。==是Java本身的一个操作符,它的作用是比较两个变量在内存中的地址,相同为true,不同为false。equals则是一个方法。Java中,所有的类都有一个根级父类,Ojbect,包括包装类。在不重写的情况下,直接equals实际上就是在调用Ojbect类的equals方法。而Ojbect类的equals方法,也是使用==来作判断的。换句话说,如果没有经过重写,equals和==的结果是一致的。而如果我们想要达到某些其它目的,比如说,我们希望在地址不同,但是值相同的情况下,也能返回true,就需要自己重写equals方法。

看到这里,产生了一个疑惑,如下:

int a = 0;

int b = 0;

System.out.println(a == b);

true还是false?

初看觉得是true,再想觉得是false,程序运行结果是true。

疑惑:两个变量的值虽然是相同的,但是,它们在内存中的地址应该是不同的,只是其中的值相同。按照==的运算规则,理由得到false的结果(内存中的地址不同),结果却是true,为什么?

事实:当定义b时,JVM会先检查内存中是否已经有了0这个值,如果没有,则创建,如果有(如之前已经执行过int a = 0),则不会再创建,而是直接将变量b指向变量a所有内存的地址,就好像执行的语句是int b = a。换句话说,a和b最终指向的内存空间,其实还是一致的。

基于上述事实,又产生了一个疑惑,如下:

int a = 0;

int b = 0;

b = b + 1;

System.out.printlt(a == 2);

疑惑:因为变量b实质上指向的是变量a的内存地址,所以,对b的修改,实质上就是对a的修改,输出结果应该是true,但实际上false。

事实:这里,又有一个误解。当执行b = b + 1时,JVM事实上并不是直接去对变量a的内存地址中的值做操作。事实上是,它先取出了a的值,为1。然后进行 +1 操作,得到2。这时候,JVM并不会直接把变量a内存地址中的值直接改成2,而是会在内存中寻找是否有2,有就将b指向相应地址,没有就把b内存地址中的值修改为2。

总结:

1.**int 和 Integer比较:**会发生自动拆箱,如果值相同就相同(不看地址 不看是否创建了对象 new)
2.两个Integer比较:
如果有创建对象new,就不相同(加入地址比较)
如果都没有创建对象new,要看是否再缓冲区对象的范围之内,在相同,不在不相同

自动装箱和自动拆箱

自动装箱: 从基本数据类型→包装类型 int → Integer
自动拆箱: 从包装类型->基本数据类型 Integer → int

在实际使用时,通过我们在定义包装类型的定义时,不会使用new关键字,而是如下:

Integer a = 0;

思考这条语句为什么成立?难道是说字面量0就相当于一个Integer对象吗?当然不是,这其实是Java为了方便,为我们提供了自动装箱的机制。事实上,这条语句相当于:

Integer a = Integer.values(0);

values是Integer 类提供一个静态方法,返回值是一个Integer实例。

和自动装箱相反,当你把一个包装类型的变量赋值到一个基本类型的变量时,Java会进行自动拆箱的过程,执行intValue方法。

Integer a = 0;(自动装箱)

int b = a;(自动拆箱)

缓存机制

在执行Integer a = new Integer(0)时,JVM会先创建一个对象,值为0,再把这个对象的引用赋值给a。基于节约内存和效率等的考虑,Java对包装类的values方法做了一些优化。如Integer,在执行Integer a = Integer.values(0)时,Java会在缓存中直接找到之前创建好的缓存,直接把0相应的引用给a。

又产生了之前的疑惑吗?其实Java的操作还和之前是一样的。所以,并不会出现不合理的问题。

观察源码发现,Boolean的缓存是true,false,Byte、Short、Integer、Long的缓存都是-128到127。Character是缓存是0~127对应的字符。Float,Double没有缓存。为什么没有?你也这样疑惑吗?

其实答案很简单,思考一下缓存的意义就可以想到,为了使缓存真正有效果,应该把最常用的一部分数据放到缓存里。但是,对于小数来说,选定一个集合,其中元素的个数是无限的。所以,Java可能在想这个问题的时候,实在是想不出来,应该以什么标准判断,把什么元素放到缓存里,于是,就放弃了,干脆不要缓存了。

初始化

基本类型和包装类型的另外一个区别时,在用作类变量时,在初始化的时候,包装类型会被初始化成null,基本类型会被初始化成特定的值,如int为0,boolean为false等。

思考一下这个差距,会带来什么样的影响?

使用原则

最后我们来整理一下基本类和包装类在实际使用时,应该遵循哪些原则?

1. 尽量使用values方法。最大可能使用缓存,提高程序的效率。

2. 类变量使用包装类。想象有一个和数据库表对应的实体类,如果你使用的基本数据类型,在插入时,可能会插入一些让你意想不到的初始值。

3. 方法的参数要使用包装类。使用包装类意味着你在调用时,可以令若干个参数为null。null是无意义的。但是如果你使用了基本数据类型,那么,你将不得不传入一个值,即使这个值对你来说,没有什么意义。

4. 方法的返回值要根据是否可为null来确定使用包装类还是基本类。当一个方法的返回值,一定不会出现null的情况时,推荐使用基本类来作为返回值。这样,调用者在拿到这个方法的返回值时,就不必担心它是为null了。

5. 方法内部(局部变量)使用基本类型。基本类型的时间效率和效率上来说,都是要优于包装类的。所以,在方法内部,能使用基本类型尽量不要使用包装类。

6. 小数的计算。严格来说,这条不属于基本类型和包装类的内容,但是,这里顺便提交,当涉及到小数的计算时,要考虑到计算的精度问题,可以使用BigDecimal,也可以通过缩小计量单位,达到化零为整的目的,这个,根据具体场景来确定。

JavaBean 使用包装类型还是基本类型

int优缺点

优点:

1.用于Bean的时候,有默认值。比如自己拼接sql增加一个User时,会方便很多,不过现在都用ORM框架,所以这也不算是优点啦。

2.两个值比较方便,使用 == 就可以了。

缺点:

1
2
3
4
5
6
7
//错误
int a1 = (Integer) null;
//错误
boolean x1 = (Boolean)null;
//正确
Integer a2 = (Integer) null;
Boolean x2 = (Boolean)null;

阿里巴巴开发手册中写的很明确,基本类型接收NULL值有NPE风险(java.lang.NullPointerException NPE 空值异常),而且默认值和NULL值不能传达同一种信息。

img

Integer优缺点

**优点:**可以存放null,从数据库中查出值时可能会有null

**缺点:**Intege不能使用 == 比较相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Integer i1 = 127;
Integer i2 = 127;
Integer i3 = 128;
Integer i4 = 128;
/**
* public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
*/
System.out.println(" i1 == i2 "+(i1 == i2));//true
System.out.println(" i3 == i4 "+(i3 == i4));//false
int i6 = 127;
int i7 = 127;
int i8 = 128;
int i9 = 128;
System.out.println(" i6 == i7 "+(i6 == i7));//true
System.out.println(" i8 == i9 "+(i8 == i9));//true

System.out.println(" i1 == i6 "+(i6 == i1));//true 与 int 类型的比较都是值比较
System.out.println(" i8 == i3 "+(i8 == i3));//true

int i10 = new Integer(128);
int i11 = new Integer(128);
System.out.println(" i10 == i11 "+(i10 == i11));//true


Integer i12 = new Integer(127);
Integer i13 = new Integer(127);
System.out.println(" i12 == i13 "+(i12 == i11));//false 对象地址比较

把int类型赋值给Integer类型。此时,int类型变量的值会自动装箱成Integer类型,然后赋给Integer类型的引用,这里底层就是通过调用valueOf()这个方法来实现所谓的装箱的。
把Integer类型赋值给int类型。此时,Integer类型变量的值会自动拆箱成int类型,然后赋给int类型的变量,这里底层则是通过调用intValue()方法来实现所谓的拆箱的。

Integer 和 int 进行比较分三情况

1、Integer与int类型的比较
这里就无所谓是谁与谁比较了,Integer == int与int == Integer的效果是一样的,都会把Integer类型变量拆箱成int类型,然后进行比较,相等则返回true,否则返回false。同样,拆箱调用的还是intValue()方法。
2、Integer之间的比较
这个就相对简单了,直接把两个引用的值(即是存储目标数据的那个地址)进行比较就行了,不用再拆箱、装箱什么的。
3、int之间的比较
这个也一样,直接把两个变量的值进行比较。
值得注意的是:Integer之间的比较,JVM会自动缓存-128~127范围内的值,所以所有在这个范围内的值相等的Integer对象都会共用一块内存,而不会开辟多个;超出这个范围内的值对应的Integer对象有多少个就开辟多少个内存。

img

Java实体类的属性类型与数据库表字段类型对应表

Java中的数据类型和SQL中的数据类型有很多不一样,需要仔细区分,不然易在开发中造成莫名的错误。

Java数据类型 Hibernate数据类型 标准SQL数据类型 (PS:对于不同的DB可能有所差异)
byte、java.lang.Byte byte TINYINT
short、java.lang.Short short SMALLINT
int、java.lang.Integer integer INGEGER
long、java.lang.Long long BIGINT
float、java.lang.Float float FLOAT
double、java.lang.Double double DOUBLE
java.math.BigDecimal big_decimal NUMERIC
char、java.lang.Character character CHAR(1)
boolean、java.lang.Boolean boolean BIT
java.lang.String string VARCHAR
boolean、java.lang.Boolean yes_no CHAR(1)(‘Y’或‘N’)
boolean、java.lang.Boolean true_false CHAR(1)(‘Y’或‘N’)
java.util.Date、java.sql.Date date DATE
java.util.Date、java.sql.Time time TIME
java.util.Date、java.sql.Timestamp timestamp TIMESTAMP
java.util.Calendar calendar TIMESTAMP
java.util.Calendar calendar_date DATE
byte[] binary VARBINARY、BLOB
java.lang.String text CLOB
java.io.Serializable serializable VARBINARY、BLOB
java.sql.Clob clob CLOB
java.sql.Blob blob BLOB
java.lang.Class class VARCHAR
java.util.Locale locale VARCHAR
java.util.TimeZone timezone VARCHAR
java.util.Currency currency VARCHAR