夯实Java基础系列:包装类型

1. 基本数据类型

Java基本数据类型(也称原生数据类型,primitive type)一共有8种。

1.1 特性

1.1.1 高性能

原生数据类型的声明方式一般如下:

int a = 3;

这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知生存期可知(这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中,存在栈中的数据拥有较高的存取速率,所以原生数据类型比引用类型性能更高一些。

1.1.2 可共享

另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。比如: 我们同时定义:

int a=3; int b=3;

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b这个引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
定义完a与b的值后,再令a = 4;那么,b不会等于4,还是等于3。在编译器内部,遇到时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

1.2 整型

Java提供了4种整型类型,与其他语言不同的是,Java中整型的取值范围与宿主机器无关,具有跨平台性,无论什么平台,int都是4个字节,long都是8个字节。

类型 存储需求 取值范围
int 4字节 -2147483648〜2147483647
short 2字节 -32768〜32768
long 8字节 -9223372036854775808〜�9223372036854775807
byte 1字节 -128〜127

1.3 浮点型

Java提供了2种浮点类型

类型 存储需求 有效整数位
float 4字节 6~7位(比int能表示的范围小)
double 8字节 15位(比int能表示的范围大,比long小)

默认浮点类型采用的是double,在浮点数后加f表示float类型。

注意,当需要得到精确的计算结果时,不要使用浮点型。主要是因为浮点数采用二进制系统表示,而二进制中无法精确地表示分数1/10。应使用BigDecimal或者转换为整型进行计算。

1.4 字符型

char类型用来表示单个字符,事实上,有些字符需要2个char才能表示。char表示的是一个码元。
与字符串不同,char类型的字面量是用单引号括起来的。

'A' //这是一个char
"A" // 这是一个String对象

1.5 布尔类型

Java用boolean来定义布尔类型,只有两个取值:true和false.

1.6 基本数据类型之间的转换

当使用两个不同类型的数值进行计算时,先要将两个操作数转换成同一类型,规则如下:

  • 如果两个操作数中有一个是double类型,另一个操作数就会转换为double类型
  • 否则,如果其中一个是float类型,另一个操作数将会转换成float类型
  • 否则,如果其中一个操作数是long类型,另一个操作数就会转换为long类型
  • 否则,两个操作数都将是int类型(char, byte, short与int运算,都要先转换为int)。

可以简单地理解为,转换的优先级是: double > float > long > int�

而这种转换实际上会导致数据失真。
比如一个较大的int数值与float数值进行运算时,按规则会将int转换为float,但我们知道float能表示的有效整数位仅为6到7位,如果int数值大于7位,就会失真。
如下图所示:实线表示数值转换不会失真的情况,虚线表示数值转换可能会失真的情况。


夯实Java基础系列:包装类型_第1张图片
image.png

当然,也可以直接进行强制类型转换,比如

double a = 100.50
int b = (int)a; // b == 100

强制类型转换,可能会导致数据失真。

2. 包装类型

2.1 为什么需要包装类

大多数情况下,我们使用Java基本数值类型进行数值运算。那为什么还需要包装类型呢?
一般来说,以下三种情况,必须使用包装类型

  • 作为泛型的参数类型时,Java规定泛型的参数类型必须是引用类型
Collection numbers;//不合法,编译失败
Collection numbers; //合法
  • 触发反射方法时。被触发的反射方法中的参数必须定义为包装类型,因为Java反射的时候会把基础数据类型获取的数据类型都变成包装类,当你需要调用的那个方法却不是包装类而是基础数据类型,就会报找不到方法的异常,NoSuchMethodException。
  • 想要使用一些包装类的特性时,比如得到类中的常量值,如Integer.MAX_VALUE,或者比如调用包装类的一些方法进行便利的计算时,比如调用Integer的valueOf方法将一个字符串转换为一个整型数值。

2.2 自动拆装箱

自动装箱是Java编译器自发地将原生类型转换为包装类型。比如,将一个int类型转换为Integer。 自动拆箱则是反向。那么什么情况下会触发这种自动拆装箱呢?

2.2.1 触发自动拆装箱的情况

  • 以下情况,会触发自动装箱
    (1)将一个基本数值类型传递给一个接收参数为相应包装类型的方法时。
    比如下面的consume方法,它接收的是Integer类型的参数
public void consume(Integer value){}

当将一个int值传给这个方法,编译器并不会报错,因为编译器自动做了装箱操作。

int param = 100;
consume(param);

编译器会将上述代码编译成类似以下形式:

int param = 100;
consume(Integer.valueOf(param));//自动装箱

(2)将一个基本数值类型直接赋值给相应包装类型时。

Integer number = 100;
  • 以下情况,会触发自动拆箱

(1)将一个包装类型传递给一个接收参数为相应基本数值类型的方法时

public void sum(int value){} //方法定义为接收int类型
sum(new Integer(100);//调用时传入的是对应的Integer类型

(2)对包装类型执行算术运算时

Integer number = new Integer(100);
number++;

(3)直接将包装类型赋值给一个基本数值类型时

Integer number = new Integer(100);
int num = number;

之所以不会报错,是因为编译器自动做了相应的拆装箱操作。而装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。

2.2.2 整型包装类的缓存机制

先来看下面的程序片断

public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        
        System.out.println(a == b);
        
        Integer c = 300;
        Integer d = 300;
        
        System.out.println(c == d);
        
    }

输出结果是:

true
false

为什么是这个结果呢?
我们知道,将int数值直接赋值给Integer类型,会触发自动装箱,也就是实际运行时,Integer a = 100会转换为Integer a = Integer.valueOf(100);来执行。那么,让我们直接来查看一下Integer类的valueOf方法的实现源码:

 public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

可以看到,当包装的int的值在IntegerCache的low和high区间内时,会直接从IntegerCache这个缓存中读取一个Integer对象,而不是直接创建一个新的Integer对象。只有不在这个缓存区间内,才会直接new一个Integer对象。这个缓冲区间是多少呢?

 private static class IntegerCache {
        static final int low = -128;
//...省略部分代码
 // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;

可以看出,这个缓存区间是 -128 到127。所以上例中c和d的值为300,超出了这个区间,自动装箱时是直接new出来的对象,==运算符比较两个不同的对象,自然返回false。

2.2.3 浮点型包装类没有缓存机制

除了Integer类,Boolean、Byte、Short、Long和Character也都有各自的缓存区间,实现缓存机制。
注意,Double和Float类没有缓存机制,我们可以看一下Double类和Float类的valueOf方法源码

    //double原生类型自动装箱成Double
    public static Double valueOf(double d) {
        return new Double(d);
    }

    //float原生类型自动装箱成Float
    public static Float valueOf(float f) {
        return new Float(f);
    }

之所以使用缓存机制是因为缓存的对象都是经常使用到的(如字符、-128至127之间的数字),防止每次自动装箱都创建一次对象的实例。
而double、float是浮点型的,没有特别的热的数据(比如在某个范围内的整型数值的个数是有限的,而浮点数却是不是有限的),缓存效果没有其它几种类型使用效率高。

3. 原生数据类型与包装类型的区别及注意点

3.1 原生数据类型与包装类型的区别

原生数据类型与包装类型主要有三个区别。
(1)原生数据类型是值类型,只是用来表示值的,它是存在方法区中的,相同值的原生数据类型共享同一个内存空间。而包装类型是引用类型,一个包装类型除了表示值,还可以表示一个内存空间。换句话说,两个值相同的包装类型对象,也许是存在不同的内存空间的
(2)包装类型可能存在null的情况。一个包装类型如果只定义,未初始化或赋值,则默认就是null的。
(3)原生数据类型比包装类型在时间和空间上拥有更高的性能。

3.2 尽量使用原生数据类型,避免使用包装类型

来看以下程序片断:

    public static void main(String[] args) {
        Long sum = 0L;
        for(long i = 0;i 

这个程序执行起来会非常慢,只因为它写错了一个小小的地方:Long sum = 0L;
为什么呢?sum定义为一个Long类型的包装对象,那么在接下来的for循环中,当执行sum +=i;因为i是long类型的,所以会先将sum进行自动拆箱,以便于与i进行算术运算,然后将结果赋予sum时,又得进行自动装箱,当超出Long类型的缓存区间时,就会不断地在堆内创建新的Long对象。所以这个程序非常慢,仅仅只是因为将sum定义为了Long,如果将sum定义为long时,问题就解决了。
所以我们应该尽量使用原生数据类型,在万不得已的情况下,不要使用包装类型。

3.3 包装类对象未初始化导致NullPointerException问题

我们来看下面的例子:

public class WrapperMess{
  
    static Integer i ;
    public static void main(String[] args){
       if(i==0){
            System.out.println(" i is 0");
       } 
   }
}

程序会不会输出"i is 0"呢?答案是不会,而且还会抛出NullPointerException。
当程序执行到 if(i==0)时,因为要将Integer对象i与int值0进行比较,会进行自动拆箱。而Integer i还未初始化,它现在的值是null,当对一个null对象进行拆箱操作,即调用Integer的intValue方法时,就会抛出NullPointerException了。

3.4 使用==与equals方法的注意点

当我们想要判断两个包装类型对象的值是否相等时,不要使用==,而应该使用equals。
因为==判断的是对象的地址,我们知道两个包装类型对象即使值相等,也可能是存在堆中不同的空间的。
而包装类都重写了Object类的equals方法,直接比较所包装的值。比如Integer的equals方法源码:

 public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

参考资料:

  • Effective Java 3rd Edition
  • Core Java
  • Java Tutorial - Numbers

你可能感兴趣的:(夯实Java基础系列:包装类型)