Java自动装箱与自动拆箱以及拆箱空指针问题

1、基本数据类型

基本类型,或者叫做内置类型,是 Java 中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。

Java 是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。

Java 基本类型共有八种,基本类型可以分为三类:

字符类型 char

布尔类型 boolean

数值类型 byteshortintlongfloatdouble

基本数据类型有什么好处

我们都知道在 Java 语言中,new 一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。

对于经常用到的类型,如 int 等,如果我们每次使用这种变量的时候都需要 new 一个 Java 对象的话,就会比较笨重。所以,和 C++ 一样,Java 提供了基本数据类型,这种数据的变量不需要使用 new 创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。

包装类型

Java 语言是一个面向对象的语言,但是 Java 中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。

包装类均位于 java.lang 包,包装类和基本数据类型的对应关系如下表所示:

为什么需要包装类型?

这个问题,其实前面已经有了答案,因为 Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。

为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

2、装箱与拆箱

在 Java SE5 中,为了减少开发人员的工作,Java 提供了自动拆箱与自动装箱功能。

自动装箱: 就是将基本数据类型自动转换成对应的包装类。

自动拆箱:就是将包装类自动转换成对应的基本数据类型。

Integer i=6 可以替代 Integer i = new Integer(10);,这就是因为 Java 帮我们提供了自动装箱的功能,不需要开发者手动去 new 一个 Integer 对象。

package autoBox;

/**
 * 自动装箱拆箱
 */
public class AutoBoxOrUnBox {
    public static void main(String[] args) {
        Integer i = 6;
        int j = i;
    }
}

自动装箱与拆箱的原理:

我们通过反编译工具的代码:

package autoBox;

public class AutoBoxOrUnBox {
  public static void main(String[] args) {
    Integer i = Integer.valueOf(10);
    int j = i.intValue();
  }
}

我们可以看出,int的 自动装箱是通过 Integer i = Integer.valueOf() 实现的。

Integer 的自动拆箱都是通过 integer.intValue 来实现的

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

3、应用场景

场景一:将基本数据类型放入集合类

我们知道我们的结合中只能放入对象,所以我们的基本类型是不能放入到集合中的,所以我们就要使用包装类来实现。

package autoBox;

import java.util.ArrayList;

/**
 * 自动装箱拆箱
 */
public class AutoBoxOrUnBox {
    public static void main(String[] args) {
        Integer one = 1;
        Integer two = 2;
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(one);
        arrayList.add(two);
        for (Integer integer : arrayList) {
            System.out.println(integer);
        }
    }
}

把上述的代码进行反编译:

package autoBox;

import java.util.ArrayList;

public class AutoBoxOrUnBox {
  public static void main(String[] args) {
    Integer one = Integer.valueOf(1);
    Integer two = Integer.valueOf(2);
    ArrayList<Integer> arrayList = new ArrayList<>();
    arrayList.add(one);
    arrayList.add(two);
    for (Integer integer : arrayList)
      System.out.println(integer); 
  }
}

 可以看出会进行自动装箱。

场景二:包装类型和基本类型的比较

    Integer a = 1;
    System.out.println(a == 1 ? "等于" : "不等于");
    Boolean bool = false;
    System.out.println(bool ? "真" : "假");

 反编译上述的代码:

    Integer a = 1;
    System.out.println(a.intValue() == 1 ? "等于" : "不等于");
    Boolean bool = false;
    System.out.println(bool.booleanValue ? "真" : "假");

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

场景三:包装类型的运算:

package autoBox;

import java.util.ArrayList;

/**
 * 自动装箱拆箱
 */
public class AutoBoxOrUnBox {
    public static void main(String[] args) {
        Integer i = 1;
        Integer j = 2;
        int res = i + j;
        System.out.println(res);
    }
}

反编译看一下:

package autoBox;

public class AutoBoxOrUnBox {
  public static void main(String[] args) {
    Integer i = Integer.valueOf(1);
    Integer j = Integer.valueOf(2);
    int res = i.intValue() + j.intValue();
    System.out.println(res);
  }
}

 可以看到包装类的运算是先进行拆箱后进行运算的。

场景四:三目运算符的使用:

package autoBox;

public class AutoBoxOrUnBox {
    public static void main(String[] args) {
        boolean flag = true;
        Integer i = 0;
        int j = 1;
        int k = flag ? i : j;
        System.out.println(k);
    }
}

 反编译后的代码:

package autoBox;

public class AutoBoxOrUnBox {
  public static void main(String[] args) {
    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。(自动拆箱导致空指针异常

当第二位和第三位表达式都是包装类型的时候,该表达式的结果才是该包装类型,否则,只要有一个表达式的类型是基本数据类型,则表达式得到的结果都是基本数据类型。如果结果不符合预期,那么编译器就会进行自动拆箱。

场景五:函数参与返回值

    //自动拆箱
    public int getNum1(Integer num) {
     return num;
    }
    //自动装箱
    public Integer getNum2(int num) {
     return num;
    }

4、自动拆装箱与缓存

package autoBox;

/**
 * 包装类缓存
 */
public class AutoBoxOrUnBox {
    public static void main(String... strings) {

        Integer integer1 = 6;
        Integer integer2 = 6;

        if (integer1 == integer2)
            System.out.println("integer1 == integer2");
        else
            System.out.println("integer1 != integer2");

        Integer integer3 = 200;
        Integer integer4 = 200;

        if (integer3 == integer4)
            System.out.println("integer3 == integer4");
        else
            System.out.println("integer3 != integer4");
    }

}

 结果:

integer1 == integer2
integer3 != integer4

== 与 equals       equals方法是Object类的方法。

 public boolean equals(Object obj) {
        return (this == obj);
    }

从源码上看,equals底层还是调用的==方法,所以如果我们的类没有重equals方法,这两个方法效果是一样的,但是基本上所有的类都重写了这个方法,比较的都是对象的值是否相等。所以基本上==比较的是地址是否一样,equals比较的是值是否相等。

回到我们的正题,上面的代码,我们的对象的地址肯定是不一样的,但是为什么会是这个结果呢。这里就设计到我们Java的缓存机制。

在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。

在包装类中,Integer类型比较特殊,其缓存值为-128~127,其中127为默认上限,也是最低上限。其实,Integer的缓存下限固定为-128,而缓存上限是可以在运行时通过修改系统属性来设置的。

5、自动拆装箱带来的问题

当然,自动拆装箱是一个很好的功能,大大节省了开发人员的精力,不再需要关心到底什么时候需要拆装箱。但是,他也会引入一些问题。

包装对象的数值比较,不能简单的使用 ==,虽然 -128 到 127 之间的数字可以,但是这个范围之外还是需要使用 equals 比较。

前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为 null ,那么自动拆箱时就有可能抛出 NPE。

如果一个 for 循环中有大量拆装箱操作,会浪费很多资源。

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值