类型转换与基本类型包装类
类型转换与基本类型包装类
基本数据类型转换
在实际应用中,经常需要在不同类型的值之间进行操作,这时就需要进行数据类型的转换。 数据类型转换有两种:
- 自动类型转换:编译器自动完成类型转换,不需要在程序中编写代码;
规则:从存储范围小的类型到存储范围大的类型。
具体规则:byte(8b)→short(char)(16b)→int(32b)→long(64b)→float(32b)→double(64b)
注意:对于short、char两种类型而言,他们是相同级别的,因此,不能相互自动转换,但是可以强制类型转换 - 强制类型转换 :强制类型转换,也称显式类型转换,是指必须书写代码才能完成的类型转换。该类类型转换很可能存在精度的损失,所以必须书写相应的代码,并且能够忍受该种损失时才进行该类型的转换。
转换规则:从存储范围大的类型到存储范围小的类型。
具体规则为:double→float→long→int→short(char)→byte
包装类
- 基本类型在java.lang包中都有一个相应的包装类,如:Byte,Short,Character,Integer,Long,Float,Double,Boolean,通过这些包装类,我们就可以将基本数据类型包装成类对象。
- 包装类的构造方法:
所有包装类都可将与之对应的基本数据类型作为参数,来构造它们的实例,例如:Integer i=new Integer(1);
除Character类外,其他包装类可将一个字符串作为参数构造它们的实例,例如:Integer i=new Integer(“123”); - 注意事项:
- Boolean类构造方法参数为String类型时,若该字符串内容为true(不考虑大小写),则该Boolean对象表示true,否则表示false。
- Number包装类构造方法(上面的8种包装类中除了Character和Boolean,都继承了Number)参数为String 类型时,字符串不能为null,且该字符串必须可解析为相应的基本数据类型的数据,否则编译通过,运行时NumberFormatException异常。
- 包装类内部都是final类型(以Integer为例,内部为private final int value;),因此包装类是引用传递,但是值修改后会生成新的内存地址,从表现方式可以理解为”值传递”。
- 所有的包装类内部都是final类型的基本数据,所以都是不可变的
自动装箱与拆箱
自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象。
在JDK1.5之前,基本类型转换为对象需要程序显示用包装类进行处理,有了装箱和拆箱后就自动了,程序就不需要显示处理了。例如:Integer num = 1;
-
什么是自动装箱和拆箱?
自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。 -
自动装箱拆箱要点
自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,char转换成Character对象,float值转换成Float对象,int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。 -
何时发生自动装箱和拆箱
自动装箱和拆箱在Java中很常见,比如我们有一个方法,接受一个对象类型的参数,如果我们传递一个原始类型值,那么Java会自动讲这���原始类型值转换成与之对应的对象。ArrayList intList = new ArrayList(); intList.add(1); //自动装箱
自动拆箱(unboxing),也就是将对象中的基本数据从对象中自动取出。
Integer i = 10; //装箱 int t = i; //拆箱,实际上执行了 int t = i.intValue();
-
注意事项:
对于–128到127(默认是127)之间的值,Integer.valueOf(int i) 返回的是缓存的Integer对象(并不是新建对象)
包装类型的缓存机制
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回True
orFalse
。public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
【进阶】long如何自动转化为float?
long占八个字节,float只占四位,如何能自动转化呢?
对于byte,short,int,long四个整数类型而言,它们在内存中无一例外都是直接换算成二进制存储的,所以我们可以直接计算出它们的最大值。
二进制的第一位是符号位不计算入数值,所以Byte的最大值是01111111,即127
而浮点型在内存中的存储结构是不同的:
float在内存中占4个字节,共32位,但是浮点数在内存中是这样的:
V=(-1)^s ∗ M ∗ 2^E
浮点数的32位不是简单的直接表示大小,而是按照一定的标准分配的。
- 其中第1位,符号位,即S。【所以符号位1表示负数,0表示正数】
- 接下来的8位,指数域,即E。
- 剩下的23位,小数域,即M,M的取值范围为[0,1)或【1,2)。
所以这就是为什么float虽然只用到了4个字节,但是浮点数表述范围却比长整型long的表述范围要大的原因了。由此就可以就解释的通为什么小范围的long能自动转型成为大范围float了。
简单计算一下float的范围
2^-127 ~ 2^128-1,很明显比long的 [2^63 - 1 , -2^63]范围大
为什么long类型转float可能存在精度丢失的问题
V=(-1)^s ∗ M ∗ 2^E
M区域(小数域),M的取值范围为[1,2)或[0,1)。
所以尽管float的表数范围很大,但是不是区间内所有的值都一定能精确表示的。所以在long转float时可能存在精度丢失。
【范例】
int a = 123456789;
float f = a;
double d = a;
System.out.println("原始int值a: " + a);
System.out.println("a转换为float精度损失:" + f);
System.out.println("a转换为double精度未损失:" + d);
long l = 1234567899999999l;
f = l;
System.out.println("long类型" + 1 + "转换为float,精度损失: " + f);
输出为:
原始int值a: 123456789
a转换为float精度损失:1.23456792E8
a转换为double精度未损失:1.23456789E8
long类型1转换为float,精度损失: 1.23456795E15
【备注】在进行货币等精确计算时应使用BigDecimal。
程序中如果对精度要求不是很高的情况,可以使用float。但精度要求高的情况,要尽量使用double。如果要求更高的精度,则应使用BigDecimal
进一步理解精度丢失
在百度上搜到一个很形象的答复,就是int是准确值,而float是精确值,准确转精确当然会精度丢失。
Int是4字节32位来表示的,而float虽然也是4字节32位,但是float的存储结构是很不一样的
以这一例子来说明,由图可知,float的存储结构是1个符号位,8个指数位,23个尾数。
符号位,表述浮点数的正或者负,0代表正,1代表负。
指数位,实际也是有正负的,但是没有单独的符号位,在计算机的世界里,进位都是二进制的,指数表示的也是2的N次幂,8位指数表达的范围是0到255,而对应的实际的指数是-127到128。也就是说实际的指数等于指数位表示的数值减127。这里特殊说明,-127和+128这两个指数数值在IEEE当中是保留的用作多种用途的,这里就不多做介绍了,有兴趣的可以查阅其他资料。【其实这个就是IEEE的定义,因为公式中是2的N次幂-127,这个跟JAVA中byte值范围不一样】
尾数位,只代表了二进制的小数点后的部分,小数点前的那位被省略了,当指数位全部为0时省略的是0否则省略的是1。
由此我们可以明白,实际上尾数确定了浮点数的精度,而数的大小主要是靠指数位,尾数只有23位,加上省略的那一位便是24位,所以如果int类型的值在2^24以内,float是可以精确表示的,但是当超过这个数的时候就不一定能精确表示了。这里最重要的一点便是要理解确定精度的有效位数,不管是什么基本类型转换实际上都要明白这一点。就如int与float,都是32位,但是内存结构既存储结构是不一样的,float只能有24位来确定精度,而int是32位。其他类型也如此进行理解即可。
在讲这公式之前讲一下我在理解过程中遇到的难题,就是尾数的23位值为什么是介于1.0和2.0之间,当时看到一直想不明白,后来才了解到这23位是用来表示小数位的,而省略的那一位是1,因为0是没有意义的(因为如果是0,一个小数的不管乘以多少都是小数)。再次强调这里的尾数23位是表示小数位的。
就以图的例子来讲。
符号位我想不用我讲了
指数位就跟我们算十进制一样的方法,可以算出指数位是124(算法我就不在这里说了,网上自己查一下),套入以上的表达式的最后一个,既是2^(124-127) = 2^(-3)
然后尾数部分,看它的公式就可以看出来,b23-i就是尾数部分的哪一位数,i取1的时候就b22,既最左边的部分,然后再乘以2的负i次方。从上面这一公式也可以看出,尾数部分是介于1.0和2.0之间。【尾数部分:1+2的-2次方,值为1.25;再乘以指数部分的2的-3次方,相当于除以8,值为0.15625】