为什么在Java中要使用自动装箱和拆箱?

97
自动装箱是Java编译器在原始类型和相应的对象包装类之间进行的自动转换。例如,将int转换为Integer,double转换为Double等。如果转换反向进行,则称为自动拆箱。那么我们为什么需要它,为什么在Java中使用自动装箱和拆箱呢?

1
基本上针对泛型.. - nachokk
4
IntegerparseInt方法,而int没有。 :) - Vishal Zanzrukia
@VishalZanzrukia 所以只是为了获得更多的功能吗? - user1968030
16
你可以有 List<Integer>,但你不能有 List<int> - Vishal Zanzrukia
是的,确切地说,为了获得更多的功能。 - Vishal Zanzrukia
10个回答

192

为了完全理解其主要原因,需要一些背景知识。

基本类型与类

Java中的基本变量包含值(整数、双精度浮点二进制数等)。因为这些值可能有不同的长度,包含它们的变量也可能有不同的长度(考虑floatdouble)。

另一方面,类变量包含对实例的引用。在许多语言中,引用通常被实现为指针(或非常类似于指针的东西)。这些东西通常具有相同的大小,而不管它们所引用的实例的大小(例如ObjectStringInteger等)。

这个类变量的属性使它们所包含的引用可以在一定程度上互换。这使我们能够进行所谓的替换:广义上来说,就是使用特定类型的实例作为另一个相关类型的实例(例如将String用作Object)。 原始变量并不具备同样的可互换性,无论是彼此之间还是与Object之间。这种情况最明显的原因(但不是唯一的原因)是它们的大小差异。这使得原始类型在这方面不太方便,但我们仍然需要它们存在于语言中(主要出于性能方面的原因)。

泛型和类型擦除

通用类型是具有一个或多个类型参数的类型(确切的数量称为泛型度)。例如,List<T> 的通用类型定义具有类型参数 T,可以是 Object(产生一个具体类型 List<Object>),StringList<String>),IntegerList<Integer>) 等等。

Generic类型比非泛型类型更加复杂。当它们被引入Java(在其最初发布之后)时,为了避免对JVM进行根本性的改变并可能破坏与旧二进制文件的兼容性,Java的创造者决定以最不具侵入性的方式实现泛型类型:所有List<T>的具体类型实际上都被编译成(二进制等效的)List<Object>(对于其他类型,绑定可能是其他内容而不是Object,但您明白我的意思)。在这个过程中,泛型度和类型参数信息丢失了,这就是我们称之为类型擦除的原因。
将两者结合起来
现在的问题是上述情况的组合:如果在所有情况下 List<T> 变成了 List<Object>,那么 T 必须始终是一种可以直接赋值给 Object 的类型。其他任何类型都不被允许。由于前面所说的,intfloatdouble 不能与 Object 相互替换,所以就不能有 List<int>List<float>List<double>(除非 JVM 中存在更为复杂的泛型实现)。
但是 Java 提供了像 IntegerFloatDouble 这样的类型,它们将这些原始类型封装在类实例中,从而使它们有效地可替换为 Object,因此允许泛型类型间接地使用基本类型(因为你可以有 List<Integer>List<Float>List<Double> 等等)。

将一个int转换为Integer,将一个float转换为Float等过程称为装箱(boxing)。相反的过程称为拆箱(unboxing)。由于每次想要将基本类型转换为Object时都必须进行装箱操作,这样做很不方便,因此在某些情况下,语言会自动执行此操作 - 这就是所谓的自动装箱(autoboxing)


根据您的解释,我们需要这些类 Integer、String 等来实现泛型。还有其他原因吗? - Bishwas Mishra

23

自动装箱用于将原始数据类型转换为它们的包装器类对象。 包装器类提供了广泛的功能来执行原始类型。最常见的示例是:

int a = 56;
Integer i = a; // Auto Boxing

由于程序员可以直接编写代码,而JVM会处理装箱和拆箱,因此需要它。

当我们使用java.util.Collection类型时,自动装箱也非常方便。当我们想要创建原始类型的Collection时,我们无法直接创建原始类型的Collection,只能创建对象类型的Collection。例如:

ArrayList<int> al = new ArrayList<int>(); // not supported 

ArrayList<Integer> al = new ArrayList<Integer>(); // supported 
al.add(45); //auto Boxing 

包装类

Java的8种基本类型(byte、short、int、float、char、double、boolean、long)每一种都有一个对应的包装类,这些包装类中预定义了许多处理基础数据类型的实用方法。

使用包装类

String s = "45";
int a = Integer.parseInt(s); // sets the value of a to 45.

包装类提供了许多有用的函数。在这里查看java文档:这里

拆箱(Unboxing)是与自动装箱相反的过程,它将包装类对象转换回其基本数据类型。JVM会自动完成此操作,以便我们可以使用包装类进行某些操作,然后将它们转换回基本数据类型,因为基本数据类型处理速度更快。例如:

Integer s = 45;
int a = s; auto UnBoxing;

对于仅使用对象的集合,会自动执行自动拆箱操作。具体如下:

ArrayList<Integer> al = new ArrayList<Integer>();
al.add(45);

int a = al.get(0); // returns the object of Integer . Automatically Unboxed . 

4
原始(非对象)类型的存在是为了效率。

原始类型 int,boolean,double 是即时数据,而对象是引用。因此字段(或变量)

int i;
double x;
Object s;

需要本地内存4+8+8吗?对于对象,只需存储对内存的引用(地址)。
使用Object包装器和其他内容,将引入间接引用,引用堆内存中的某个Integer / Double实例。
为什么需要装箱?
这是一个相对范围的问题。在未来的Java中,计划能够拥有ArrayList,提升原始类型。
答案:目前,ArrayList仅适用于Object,为对象引用保留空间,并管理垃圾回收。因此,通用类型是Object子类。 因此,如果想要浮点值的ArrayList,则需要将double包装在Double对象中。
在这里,Java与传统的C ++及其模板不同:C ++类vector,vector将创建两个编译产品。 Java设计选择了一个ArrayList.class,不需要为每种参数类型创建新的编译产品。
因此,如果没有将装箱到Object中,则需要为每个参数类型的每个出现编译类。具体而言:每个集合或容器类都需要Object,int,double,boolean的版本。 Object的版本将处理所有子类。
事实上,IntBuffer,CharBuffer,DoubleBuffer等在Java SE中已经存在这种分化的需求,它们操作int,char,double。通过从常见源生成这些源的方式解决了这个问题。

4
从JDK 5开始,Java添加了两个重要的功能:自动装箱和自动拆箱。自动装箱是指在需要对象时,原始类型会自动封装为等效的包装类型。您无需显式构造对象。自动拆箱是指当需要其值时,封装在类型包装器中的值将自动提取。您不需要调用intValue()doubleValue()等方法。
自动装箱和自动拆箱的添加大大简化了算法的编写,消除了手动装箱和拆箱的烦恼。它也有助于避免错误,并且对于只操作对象的泛型非常重要。最后,自动装箱有助于与集合框架一起使用。

2

为什么我们需要(拆箱和装箱)?

为了使混合使用基本类型和它们的面向对象(OO)替代品的代码编写更加舒适/简洁。

为什么我们有基本类型和它们的OO替代品?

基本类型不是类(与C#不同),因此它们不是Object的子类,也不能被覆盖。

我们有像int这样的基本类型是出于性能原因,而IntegerObject替代品则是为了享受OO编程的好处,并且作为一个小点,为实用程序常量和方法提供一个良好的位置(如Integer.MAX_VALUE和Integer.toString(int))。

OO的好处最容易在泛型(List<Integer>)中看到,但不仅限于此,例如:

Number getMeSome(boolean wantInt) {

    if (wantInt) {
        return Integer.MAX_VALUE;
    } else {
        return Long.MAX_VALUE;
    }
}

2
一些数据结构只能接受对象,不能接受原始类型。
例如:HashMap中的键。
有关更多信息,请参见此问题:HashMap和int作为键
还有其他很好的理由,比如数据库中的“int”字段,它也可以为NULL。 Java中的int不能为null,但Integer引用可以。自动装箱和拆箱提供了一种便利的方法,避免在转换过程中编写冗余代码。

1

ArrayList不支持原始类型,只支持类。但我们需要使用原始类型,例如int、double等。

ArrayList<String> strArrayList = new ArrayList<String>(); // is accepted.

ArrayList<int> intArrayList = new ArrayList<int>(); // not accepted.

整型类将基本类型int的值封装在一个对象中,因此下面的代码是被接受的。

ArrayList<Integer> intArrayList = new ArrayList<Integer>(); // is accepted.

我们可以使用add(value)方法添加一个值。 要在strArrayList中添加一个字符串值,比如"Hello",代码只需:
strArrayList.add("Hello");  

"并且添加一个整数值,比如说54,我们可以写成:"
intArrayList.add(54);

但是当我们写intArrayList.add(54)时,编译器会将其转换为以下行:

intArrayList.add(Integer.valueOf(54)); 

作为用户方面更容易接受的方法,intArrayList.add(54)很容易实现,因此编译器执行自动装箱的艰巨工作,即intArrayList.add(Integer.valueOf(54))。
同样地,为了检索值,我们只需键入intArrayList.get(0),编译器会将其转换为autoUnboxing的intArrayList.get(0).intValue()。

0

另一个特殊情况是,

Integer intval = null;
int toPrimitive = intval;
System.out.println(toPrimitive);

针对上述情况,我们遇到了NullPointerException。这意味着我们可以捕获NPE。


0

因为它们是不同的类型,并且作为一种便利。性能很可能是拥有原始类型的原因。


0
自动装箱:将原始值转换为相应包装器类的对象。
拆箱:将包装类型的对象转换为其相应的原始值。
// Java program to illustrate the concept 
// of Autoboxing and Unboxing 
import java.io.*; 

class GFG 
{ 
    public static void main (String[] args) 
    { 
        // creating an Integer Object 
        // with value 10. 
        Integer i = new Integer(10); 

        // unboxing the Object 
        int i1 = i; 

        System.out.println("Value of i: " + i); 
        System.out.println("Value of i1: " + i1); 

        //Autoboxing of char 
        Character gfg = 'a'; 

        // Auto-unboxing of Character 
        char ch = gfg; 
        System.out.println("Value of ch: " + ch); 
        System.out.println("Value of gfg: " + gfg); 

    } 
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接