类型转换与基本类型包装类

类型转换与基本类型包装类

基本数据类型转换

在实际应用中,经常需要在不同类型的值之间进行操作,这时就需要进行数据类型的转换。 数据类型转换有两种:

  • 自动类型转换:编译器自动完成类型转换,不需要在程序中编写代码;
    规则:从存储范围小的类型到存储范围大的类型。
    具体规则: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 or False

            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的存储结构是很不一样的

156795497950633
以这一例子来说明,由图可知,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位是表示小数位的。

156795497950633
就以图的例子来讲。

符号位我想不用我讲了
指数位就跟我们算十进制一样的方法,可以算出指数位是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】

posted @ 2022-06-07 21:20  Faetbwac  阅读(88)  评论(0编辑  收藏  举报