基本类型与包装类型

阿里开发手册中关于基本类型的问题

7. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。 说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。

8. 关于基本数据类型与包装数据类型的使用标准如下:

1) 【强制】所有的POJO类属性必须使用包装数据类型。

2) 【强制】RPC方法的返回值和参数必须使用包装数据类型。

3) 【推荐】所有的局部变量使用基本数据类型。

说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。

正例:数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。

反例:比如显示成交总额涨跌情况,即正负x%,x为基本数据类型,调用的RPC服务,调用不成功时,返回的是默认值,页面显示为0%,这是不合理的,应该显示成中划线。所以包装数据类型的null值,能够表示额外的信息,如:远程调用失败,异常退出。

Java共有8种基本数据类型

在这八个类名中,除了Integer和Character类以后,其它六个类的类名和基本数据类型一直,只是类名的第一个字母大写即可。

序号

基本类型

包装类

1

boolean

Boolean

2

byte

Byte

3

short

Short

4

int

Integer

5

long

Long

6

float

Float

7

double

Double

8

char

Character

 基本类型范围

序号

基本类型

位数

数据范围

1

boolean

1位

只有true和false两个取值

2

byte

8位

-2^7~2^7-1

3

short

16位

-2^15~2^15-1

4

int

32位

-2^31~2^31-1。

5

long

64位

-2^63~2^63-1

6

float

32位

3.4e-45~1.4e38

7

double

64位

4.9e-324~1.8e308

8

char

16位

0, 2^16-1

超出范围怎么办

上面说过了,整型中,每个类型都有一定的表示范围,但是,在程序中有些计算会导致超出表示范围,即溢出。如以下代码:

int i = Integer.MAX_VALUE;
int j = Integer.MAX_VALUE;
int k = i + j;
System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")");

运行结果

这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。所以,在程序中,使用同类型的数据进行运算的时候,一定要注意数据溢出的问题。

为什么要有包装类

由于Java是一种面向对象的变成语言,OO的思想渗透在Java编程的点点滴滴,而8种基本类型并不属于对象,这会给Java程序带来很大的困难,因此Java提供了封装类型,将基本类型包装成对象并提供了自动拆装箱机制,使得基本数据类型也可以作为对象在程序中被使用和传递。

举个例子,具体一点

当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装Object的,但是基本类型并不是对象,所以就需要这些基本类型的包装类了。Java中每种基本类型都有相应的包装类。

为什么保留基本数据类型

按照上面说的,基本类型在使用中有诸多不便,为什么Java不直接把基本类型删去呢?

我们都知道在Java语言中,new一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;而基本类型是存储在栈里的,而栈的存取相比与堆,是更高效的。所以,相比于基本类型,对象的存取比较消耗资源的。

两者区别

1、声明方式不同

包装类创建的是对象,拥有方法和字段.对象的调用都是通过引用对象的地址。基本类型不是.

基本类型不使用new关键字,而包装类型需要使用new关键字来在堆中分配存储空间;

2、存储区域不同

基本类型是在栈内存中分配 而对象是在存储堆内存中 

即 

int a= 5;//直接在栈中分配空间 
Integer b = new Integr(5);//对象是在堆内存中

Tips:在堆中分配空间所需的时间远大于从栈中分配存储空间,相比而言,栈更高效,这也是Java保留基本类型的原因

3、存储内容不一样

基本类型保存就是数值

而对象类型保存的是对象的内存地址

4、占用空间大小不同

显然,相对于基本类型的变量来说,对象类型的变量需要占用更多的内存空间

​之所以 Java 里没有一刀切了基本类型,就是看在基本类型占用内存空间相对较小,在计算上具有高于对象类型的性能优势

5、初始值不同

基本类型的初始值如int为0,boolean为false,而包装类型的初始值为null;

6、使用方式不同

  • 集合类中只能使用包装类(基本类型可自动装箱)
  • 泛型中使用包装类
  • 反射调用函数中使用包装类
  • 数据库查询的结果为null时,不能赋值给基本类型,应该使用包装类

缓存池介绍

除了Float和Double之外,其他六个包装类都有常量缓存池 .

​ 拿Integer来举例子,Integer类内部中内置了 256个Integer类型数据,当使用的数据范围在 -128~127之间时,会直接返回常量池中数据的引用,而不是创建对象,超过这个范围则会创建对象。

常见面试题

public static void main(String[] args){
    Integer i1 = 100;
    Integer i2 = 100;
    Integer i3 = 200;
    Integer i4 = 200;
    System.out.println(i1==i2);      // true
    System.out.println(i3==i4);      // false
}

这种赋值方式会触发自动装箱操作,而自动装箱操作调用的是valueOf方法。

valueOf方法会根据数组的大小来判断是否从缓存池中拿,数值的在[-128~127]范围内,则直接返回缓存池中的数据,如果不是,则返回一个戏对象

注意:这个问题只会发生在装箱时,也就是调用valueOf方法时。

如果没有触发装箱操作时,不论值是否相等,新建对象的==比较都是false。

Integer a1 = 1;//调用Integer.valueOf()自动装箱
Integer b1 = 1;//调用Integer.valueOf()自动装箱
Integer c1 = 300;//调用Integer.valueOf()自动装箱
Integer d1 = 300;//调用Integer.valueOf()自动装箱
System.out.println(a1==b1);//true
System.out.println(c1==d1);//false

Integer a2 = Integer.valueOf(1);//手动装箱
Integer b2 = Integer.valueOf(1);//手动装箱
Integer c2 = Integer.valueOf(300);//手动装箱
Integer d2 = Integer.valueOf(300);//手动装箱
System.out.println(a2==b2);//true
System.out.println(c2==d2);//false

// 构造器传值,并没有调用valueOf方法,所以也没有缓冲池

Integer a3 = new Integer(1);//新建对象
Integer b3 = new Integer(1);//新建对象
Integer c3 = new Integer(300);//新建对象
Integer d3 = new Integer(300);//新建对象
System.out.println(a3==b3);//false
System.out.println(c3==d3);//false

Integer.valueOf();源码分析

/**
 * Returns an {@code Integer} instance representing the specified
 * {@code int} value.  If a new {@code Integer} instance is not
 * required, this method should generally be used in preference to
 * the constructor {@link #Integer(int)}, as this method is likely
 * to yield significantly better space and time performance by
 * caching frequently requested values.
 *
 * This method will always cache values in the range -128 to 127,
 * inclusive, and may cache other values outside of this range.
 *
 * @param  i an {@code int} value.
 * @return an {@code Integer} instance representing {@code i}.
 * @since  1.5
 */
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

其实官方JavaDoc讲的很清楚了,英文好的小伙伴直接看官方JavaDoc。

IntegerCache.cache是一个数组,范围是[-128~127],如果传进来的数值在这个范围内,就直接返回这个数组里的内容。如果超出这个范围,就返回一个新建对象。

其他包装类缓存池

包装类

缓存池类型

缓存池内容

Integer

IntegerCache

-128~127

Byte

ByteCache

-128~127 (此区间的数字转换成字节,其他包装类也是转成自己的类型)

Long

LongCache

-128~127

Short

ShortCache

-128~127

Character

CharacterCache

0~127

Boolean

true , false

true , false

类型之间的比较

基本类型

==

equals

基本类型

不可用

包装类

地址

内容

有两个类型

  1. 基本数据类型
  2. 包装类型

比较有两种方式

  1. ==
  2. equals(仅包装类型的方法)

小结

  1. ==如果是基本类型,比较的是数值
  2. ==如果是对象,比较的是对象的内存地址
  3. equals是包装类型的方法,只能包装类使用
  4. 因为基本类型的包装类都重写了equals方法,所以比较的是数值

Tips:包装类比较,阿里强制使用equals

7. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。 说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。

代码,同类型比较

// 同类型比较
int a1 = 1;
int b1 = 1;
System.out.println(a1==b1);//true,比较的是数值

Integer a2 = new Integer(1);
Integer b2 = new Integer(1);
System.out.println(a2==b2);//false,比较的是对象的地址
System.out.println(a2.equals(b2));//true,重写了equals,比较的是数值

equals源码

包装类型都重写了equals方法,比较的都是数值

Integer.equals()举例

public boolean equals(Object obj) {
    // 先判断是不是Integer类型
    if (obj instanceof Integer) {
    // 拆箱成基本类型,然后比较数值
    return value == ((Integer)obj).intValue();
    }
    return false;
}

代码,交叉对比

int a1 = 300;
Integer a2 = new Integer(300);
// 交叉对比
System.out.println(a1==a2);//true自动拆箱,比较的是数值
System.out.println(a2.equals(a1));//true,a1自动装箱后传入equals方法,equals比较的是数值

类型之间转换

类型转换方式

  1. 自动转换
  2. 强制转换

基本类型之间的转换

Java中除了boolean类型之外,其他7中类型相互之间可以进行转换。转换分为自动转换和强制转换。对于自动转换(隐式),无需任何操作,而强制类型转换需要显式转换,即使用转换操作符(type)。7种类型按照其占用空间大小进行排序:

byte <(short=char)< int < long < float < double

类型转换的总则是:小可直接转大、大转小会失去精度。这句话的意思是较小的类型直接转换成较大的类型,没有任何影响;

而较大的类型也可以强制转换为较小的类型,但是会失去精度。他们之间的转换都不会抛出任何运行时异常。小转大是Java帮我们自动进行转换的,与正常的赋值操作完全一样;大转小需要进行强制转换操作,其语法是target-type var =(target-type) value。

数据类型转换必须满足如下规则:

1.基本数据类型中,布尔类型boolean占有一个字节,由于其本身所代码的特殊含义,boolean2.类型与其他基本类型不能进行类型的转换(既不能进行自动类型的提升,也不能强制类型转换), 否则将编译出错。

3.在把容量大的类型转换为容量小的类型时必须使用强制类型转换。

4.转换过程中可能导致溢出或损失精度,例如:

int i =128;  
byte b = (byte)i;//溢出

因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出。

5. 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入,例如:

(int)23.7 == 23;        
(int)-45.89f == -45

6. byte,char,short虽然类型从小到大自动转换,但是byte不能转成char,char也不能转成short。因为byte和short是数值型的变量,char字符型的变量。数值型变量有正负之分而在char中则无正负之分。byte转short自然就是可以的了。

自动类型转换

强制类型转换

// 强制类型转换,大转小
double b1 = 1.1;
float b2 = (float) b1;
System.out.println(b2);//1.1
int b3 = (int) b2;
System.out.println(b3);//1,损失精度

注意:有些时候会损失精度

值得注意是,大转小是一个很不安全的动作,可能导致莫名其妙的错误。比如,1111111111111L强转成int类型后,其值(-1285418553)与转换前的值相差巨大。这是由于在进行强制转换时,在二进制层面上直接截断,导致结果“面目全非”。

char的转换

byte,char,short虽然类型从小到大自动转换,但是byte不能转成char,char也不能转成short。因为byte和short是数值型的变量,char字符型的变量。数值型变量有正负之分而在char中则无正负之分。byte转short自然就是可以的了。

因为char是保存ASIIC码,并不是数值,所以单独拿出来讲。

​char->int转换

char c = 'a';
int i = c;
System.out.println(i);// 97

​byte,short,char之间的转换

注意

  1. byte可以自动转换成short
  2. byte不能自动转换成char
  3. shrot不能自动转换成char

数据类型自动提升

1、所有的byte,short,char型的值将被提升为int型;

因为Java中最小运算为int。小于int的byte,short都会自动被转换成int

这是因为Java中的数值运算最低要求是int类型,如果参与运算的变量类型都没有超过int类型,则它们都会被自动升级为int类型再进行运算,所以它们运算后的结果类型也是int类型。

2、如果有一个操作数是long型,计算结果是long型;

这里表达式中有long的类型,所以结果也为long。不能用int接收

注意:计算结果虽然是long,但是可以用大于long的类型来接收,其他同理

之所以能这样,是因为前面讲的自动转换

 

3、如果有一个操作数是float型,计算结果是float型;

4、如果有一个操作数是double型,计算结果是double型;

 

其它类型向字符串的转换

几种方法

  1. 调用类的串转换方法:X.toString();
  2. 自动转换:X+””;
  3. 使用String的方法:String.valueOf(X);

代码

// 基本类型
int a1 = 1;
String s1 = a1 + "";
String s2 = String.valueOf(a1);

//包装类型
Integer a2 = 1;
String s3 = a2 + "";
String s4 = String.valueOf(a2);
String s5 = a2.toString();

字符串->其它类型的转换

字符串->包装类型

两种方法

  1. 构造器传值
  2. valueOf()传值

代码

String s = "3.9";
// 方法1,构造器传值
Byte b1 = new Byte(s);
Short t1 = new Short(s);
Integer i1 = new Integer(s);
Long l1 = new Long(s);
Float f1 = new Float(s);
Double d1 = new Double(s);

// 方法2,valueOf传值
Byte b2 = Byte.valueOf(s);
Short t2 = Short.valueOf(s);
Integer i2 = Integer.valueOf(s);
Long l2 = Long.valueOf(s);
Float f2 = Float.valueOf(s);
Double d2 = Double.valueOf(s);

字符串->基本类型

两种方法

  1. parseType方法
  2. 先转换成包装类型,然后拆包成基本类型

静态parseType方法

String s = "3.9";
byte b = Byte.parseByte(s);
short t = Short.parseShort(s);
int i = Integer.parseInt(s);
long l = Long.parseLong(s);
float f = Float.parseFloat(s);
double d = Double.parseDouble(s);

字符串->包装类型->基本类型

​int a1 = new Byte(s). intValue();
int a2 = Integer.valueOf(s).intValue();

自动装箱/拆箱(Autoboxing/unboxing)

包装类与基本类型的转换

包装类

包装类转基本类型

基本类型转包装类

Byte

Byte.valueOf(byte)

byteInstance.byteValue()

Short

Short.valueOf(short)

shortInstance.shortValue()

Integer

Integer.valueOf(int)

integerInstance.intValue()

Long

Long.valueOf(long)

longInstance.longValue()

Float

Float.valueOf(float)

floatInstance.floatValue()

Double

Double.valueOf(double)

doubleInstance.doubleValue()

Character

Character.valueOf(char)

charInstance.charValue()

boolean

Boolean.valueOf(booleann)

booleanInstance.booleanValue()

手动装箱拆箱

int a1 = 1;
Integer a2 = Integer.valueOf(a1);//手动装箱

Integer b1 = new Integer(1);
int b2 = b1.intValue();//手动拆箱

自动装箱与自动拆箱的实现原理

既然Java提供了自动拆装箱的能力,那么,我们就来看一下,到底是什么原理,Java是如何实现的自动拆装箱功能。

我们有以下自动拆装箱的代码:

int a1 = 1;
Integer a2 = a1;//自动装箱

Integer b1 = new Integer(1);
int b2 = b1;//自动拆箱

对以上代码进行反编译后可以得到以下代码:

从上面反编译后的代码可以看出,int的自动装箱都是通过Integer.valueOf()方法来实现的,Integer的自动拆箱都是通过integer.intValue来实现的。如果读者感兴趣,可以试着将八种类型都反编译一遍 ,你会发现以下规律:

自动装箱都是通过包装类的valueOf()方法来实现的.自动拆箱都是通过包装类对象的xxxValue()来实现的。

哪些地方会自动拆装箱

自动装箱

1、将基本数据类型放入集合类

我们知道,Java中的集合类只能接收对象类型,那么以下代码为什么会不报错呢?

ArrayList<Integer> list = new ArrayList<>();
for (int i = 1; i < 50; i ++){
    list.add(i);
}

将上面代码进行反编译,可以得到以下代码:

以上,我们可以得出结论,当我们把基本数据类型放入集合类中的时候,会进行自动装箱。

2、函数参数与返回值

这个比较容易理解,直接上代码了:

//自动装箱
public Integer getNum(int num) {
    return num;
}

自动拆箱

1、包装类型和基本类型的大小比较

有没有人想过,当我们对Integer对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢?看以下代码:

int a = 1;
Integer b = new Integer(1);
System.out.println(a==b);//true,比较时自动拆箱,比较的是数值

反编译

可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。

2、包装类型的运算

有没有人想过,当我们对Integer对象进行四则运算的时候,是如何进行的呢?看以下代码:

Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a+b);//2

反编译后代码如下:

我们发现,两个包装类型之间的运算,会被自动拆箱成基本类型进行。

3、三目运算符的使用

这是很多人不知道的一个场景,作者也是一次线上的血淋淋的Bug发生后才了解到的一种案例。看一个简单的三目运算符的代码:

boolean flag = true;
Integer i = 0;
int j = 1;
int k = flag ? i : j;

很多人不知道,其实在int k = flag ? i : j;这一行,会发生自动拆箱。反编译后代码如下:

boolean flag = true;
Integer i = Integer.valueOf(0);
int j = 1;
int k = flag ? i.intValue() : j;
System.out.println(k);

这其实是三目运算符的语法规范。当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。

因为例子中,flag ? i : j;片段中,第二段的i是一个包装类型的对象,而第三段的j是一个基本类型,所以会对包装类进行自动拆箱。如果这个时候i的值为null,那么久会发生NPE。

4、函数参数与返回值

这个比较容易理解,直接上代码了:

//自动拆箱
public int getNum(Integer num) {
    return num;
}

自动拆装箱带来的问题

  1. 自动拆箱时的NPL问题
  2. ==比较问题
  3. 效率问题

1、自动拆箱时的NPL问题

Integer a = null;
int b = a.intValue();//拆箱,报错

2、==比较问题

这个前面讲了一些,这里就不多讲了

// valueOf传值,(自动装箱调用的方法)
// 缓冲池范围-128~127
Integer a1 = Integer.valueOf(1);
Integer a2 = Integer.valueOf(1);
Integer a3 = Integer.valueOf(300);
Integer a4 = Integer.valueOf(300);
System.out.println(a1==a2);//true缓冲池内
System.out.println(a3==a4);//false缓冲池外

//构造器传值
Integer b1 = new Integer(1);
Integer b2 = new Integer(1);
Integer b3 = new Integer(300);
Integer b4 = new Integer(300);
// 并没有调用valueOf方法,所以并没有缓冲池。
// 用这种方法创建出来的都是新对象
System.out.println(b1==b2);//false
System.out.println(b3==b4);//false

3、效率问题

基本类型在栈里,包装类型在堆里。

拆装箱操作本身也会浪费资源

参考

http://hinylover.space/2016/06/16/relearn-java-base-type-and-wrapper/

http://blog.leanote.com/post/github-chemist0086/%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E4%B8%8E%E5%A2%9E%E5%BC%BA%E5%8C%85%E8%A3%85%E7%B1%BB

https://www.liupeng.mobi/archives/1198

http://www.importnew.com/11915.html

http://www.runoob.com/java/java-basic-datatypes.html

http://www.stormjie.top/2018/10/10/Java%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E4%B8%8E%E5%8C%85%E8%A3%85%E7%B1%BB/

http://alexyyek.github.io/2014/12/29/wrapperClass/

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值