一、基本类型

Java一种静态编程语言,所有变量和表达式是在编译时就确定的。同时,Java又是一种强类型语言,所有的变量和表达式都有具体的类型,并且每种类型是严格定义的。类型限制了变量可以hold什么样的值,表达式最终会产生什么样的值,可以进行哪些操作。在Java中共有8中基本类型数据,同时每种基本类型又有对应的包装类。

最简单的理解,基本类型有默认值,而包装类型初始为null。然后再根据这两个特性进行分业务使用,在阿里巴巴的规范里所有的POJO类必须使用包装类型,而在本地变量推荐使用基本类型。 

基本类型介绍

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

1、整数:包括int、short、byte、long初始值为0。

2、浮点型:float、double初始值为0.0

3、字符:char初始值为空格,即'' ",如果输出,在Console上是看不到效果的。

4、布尔:boolean初始值为false

基本型别大小最小值最大值包装类
boolean----------------Boolean
char16-bitUnicode0Unicode 2^16-1 Character
byte8-bit128+127Byte
short16-bit-2^15+2^15-1Short
int32-bit-2^31+2^31-1Integer
long64-bit-2^63+2^63-1Long
float32-bitIEEE754IEEE754Float
double64-bitIEEE754IEEE754Double

基本类型的运算和相互转换

基本类型运算

boolean类型数据可以进行逻辑运算(&&、||、!),其他的基本类型都可以进行数值计算(+、-、*、/、%等)。逻辑运算比较简单易懂,完全与逻辑数学的规则一致。而数值运算涉及到运算后的结果的类型问题,稍微比较复杂一点。一般来说,运算最终结果的类型与表达式中的最大(占用空间最大)的类型。

long l = 1 + 2L;      // 与1L的类型一致
int i = 1 + 2L;       //编译不通过
float f = 1 + 2 + 1.2f;      //与1.2f的类型一致
double d = 1 + 2 + 1.2;        //与1.2的类型一致

如果两种相同的类型的数据进行运算,按理来说,运算结果应该还是那个类型。但事实上,byte、char、short等类型是满足这个结论的。

// 编译不通过,编辑器报:Type mismatch: cannot convert from int to byte 。
byte s1 = 1;
byte s2 = 1;
byte s = s1 + s2;
 
// 编译不通过,编辑器报:Type mismatch: cannot convert from int to char 。
char s1 = 1;
char s2 = 1;
char s = s1 + s2;
 
// 编译不通过,编辑器报:Type mismatch: cannot convert from int to short 。
short s1 = 1;
short s2 = 1;
short s = s1 + s2;

从字面上来看,1+1=2绝对没有超过这个类型的范围。下面的例子都可以编译通过

byte s1 = 1 + 1;
char s2 = 1 + 1;
short s3 = 1 + 1;

这是因为Java中的数值运算最低要求是int类型,如果参与运算的变量类型都没有超过int类型,则它们都会被自动升级为int类型再进行运算,所以它们运算后的结果类型也是int类型。这种方式所得到结果是否超过了对应类型所表示的范围只能在运行时才能确定,在编译时是无法知晓的。而编译器会直接将byte s1 = 1 + 1编译成byte s1 = 2,这个表达式在编译器就可以确定是合法表达式,故可以通过编译。可以通过字节码来进行佐证。

short s = 1 + 1;

上面伪代码所对应的字节码如下,iconst_2表示直接生成常量,然后赋值给s变量。

Code:
      stack=1, locals=2, args_size=1
         0: iconst_2
         1: istore_1
         2: return

类型转换

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

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

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

// 自动转换
long l = 10;
double d = 10;
float = 10;
 
// 强制转换
int a = (int) 1.0;
char c = (char) a;

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

二、包装类型

Java中每一种基本类型都会对应一个唯一的包装类,基本类型与其包装类都可以通过包装类中的静态或者成员方法进行转换。每种基本类型及其包装类的对应关系如下,值得注意的是,所有的包装类都是final修饰的,也就是它们都是无法被继承和重写的。

包装类与基本类型的转换

基本类型------>包装器类
Integer obj=new Integer(145);
 
包装器类------>基本类型
int num=obj.intValue();
 
字符串------>包装器类
Integer obj=new Integer("-45.36");
 
包装器类------>字符串包装器类
String str=obj.toString();
 
字符串------>基本类型
int num=Integer.parseInt("-45.36");
 
基本类型------>字符串包装器类
String str=String.valueOf(5);

“莫名其妙”的NullPointException在笔者开发经历中,碰到过不少因为请求参数或者接口定义字段设置为int(或者其他基本类型)而导致NullPointException。代码大致地运行步骤如下所示,当然不会跟这个完全一样。

Integer a =null;...intb = a;// 抛出NullPointException上面的代码可以编译通过,但是会抛出空指针异常(NullPointException)。前面已经说过了,int b = a实际上是int b = a.intValue(),由于a的引用值为null,在空对象上调用方法就会抛出NullPointException。

三、拆箱与装箱

JDK1.5的新特性:自动装包/拆包(Autoboxing/unboxing)

  自动装包/拆包大大方便了基本类型数据和它们包装类地使用。

  自动装包:基本类型自动转为包装类.(int >> Integer)

  自动拆包:包装类自动转为基本类型.(Integer >> int)

  在JDK1.5之前,我们总是对集合不能存放基本类型而耿耿于怀,现在自动转换机制解决了我们的问题。

Java作为一种强类型的语言,对象直接赋值给引用类型变量,而基础数据只能赋值给基本类型变量,这个是毫无异议的。那么基本类型和包装类型为什么可以直接相互赋值呢?这其实是Java中的一种“语法糖”。“语法糖”是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会(来自百度百科)。换句话说,这其实是一种障眼法,那么实际上是怎么样的呢?下面是Integer a = 1;语句编译的字节码。

0: iconst_1
1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1

首先,生成一个常量1,然后调用Integer.valueOf(int)方法返回Integer对象,最后将对象的地址(引用)赋值给变量a。Integer a = 1;其实相当于Integer a = Integer.valueOf(1);。其他的也类似,比如Double、Character,不相信的朋友可以自己手动尝试一下。

Integer

在Java中,“==”符号判断的内存地址所对应的值得相等性,具体来说,基本类型判断值是否相等,引用类型判断其指向的地址是否相等。看看下面的代码,两种类似的代码逻辑,但是得到截然不用的结果。

Integer a1 = 1;
Integer a2 = 1;
System.out.println(a1 == a2); // true
 
Integer b1 = 222;
Integer b2 = 222;
System.out.println(b1 == b2); // false

这个必须从源代码中才能找到答案。Integer类中的valueOf()方法的源代码如下:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high) // 判断实参是否在可缓存范围内,默认为[-128, 127]
        return IntegerCache.cache[i + (-IntegerCache.low)]; // 如果在,则取出初始化的Integer对象
    return new Integer(i); // 如果不在,则创建一个新的Integer对象
}

由于1属于[-128, 127]集合范围内,所以valueOf()每次都会取出同一个Integer对象,故第一个“==”判断结果为true;而222不属于[-128, 127]集合范围内,所以valueOf()每次都会创建一个新的Integer对象,由于两个新创建的对象的地址不一样,故第一个“==”判断结果为false。 

Double

下面这段代码的输出结果又为什么不一样呢?

        Double i1 = 100.0;
        Double i2 = 100.0;
        System.out.println(i1==i2);   //false
 
        Double i3 = 200.0;
        Double i4 = 200.0;       
        System.out.println(i3==i4);   //false

至于具体为什么,读者可以去查看Double类的valueOf的实现。

  在这里只解释一下为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

Boolean

下面这段代码输出结果又是什么?

        Boolean i1 = false;
        Boolean i2 = false;
        System.out.println(i1==i2);     //true
 
        Boolean i3 = true;
        Boolean i4 = true;         
        System.out.println(i3==i4);     //true

至于为什么是这个结果,同样地,看了Boolean类的源码也会一目了然。下面是Boolean的valueOf方法的具体实现:

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

而其中的 TRUE 和FALSE又是什么呢?在Boolean中定义了2个静态成员属性:

public static final Boolean TRUE = new Boolean(true);
 
public static final Boolean FALSE = new Boolean(false);

至此,大家应该明白了为何上面输出的结果都是true了

  注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。

     Double、Float的valueOf方法的实现是类似的。
谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别

  • 1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;
  • 2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。

再来看看这个题,加深对装箱拆箱操作的理解

        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d);            //true
        System.out.println(e==f);            //false
        System.out.println(c==(a+b));        //true
        System.out.println(c.equals(a+b));   //true
        System.out.println(g==(a+b));        //true
        System.out.println(g.equals(a+b));   //false
        System.out.println(g.equals(a+h));   //true

注意:

  • 当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象
  • 如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)
  • 对于包装器类型,equals方法并不会进行类型转换

第一个和第二个输出结果没有什么疑问。第三句由于  a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。

三、基本类型与包装类型的异同

1、在Java中,一切皆对象,但八大基本类型却不是对象。

2、声明方式的不同,基本类型无需通过new关键字来创建,而封装类型需new关键字。

3、存储方式及位置的不同,基本类型是直接存储变量的值保存在堆栈中能高效的存取,封装类型需要通过引用指向实例,具体的实例保存在堆中。

4、初始值的不同,封装类型的初始值为null,基本类型的的初始值视具体的类型而定,比如int类型的初始值为0,boolean类型为false;

5、使用方式的不同,比如与集合类合作使用时只能使用包装类型。

6、什么时候该用包装类,什么时候用基本类型,看基本的业务来定:这个字段允不允许null值,如果允许null值,则必然要用封装类,否则值类型就可以了,用到比如泛型和反射调用函数.,就需要用包装类! 


版权声明:文章转载请注明来源,如有侵权请联系博主删除!
最后修改:2019 年 12 月 25 日 11 : 57 AM
如果觉得我的文章对你有用,请随意赞赏
评论打卡也可以哦,您的鼓励是我最大的动力!