繁体   English   中英

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

[英]Why do we use autoboxing and unboxing in Java?

自动装箱是 Java 编译器在原始类型与其对应的 object 包装类之间进行的自动转换。 例如,将 int 转换为 Integer,将 double 转换为 Double,等等。 如果转换以另一种方式进行,则称为拆箱。

那么为什么我们需要它,为什么我们在 Java 中使用自动装箱和拆箱?

需要一些上下文才能完全理解这背后的主要原因。

基元与类

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

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

类变量的这个属性使得它们包含的引用可以互换(在一定程度上)。 这允许我们做我们所说的替换:广义上讲, 使用特定类型的实例作为另一个相关类型的实例(例如,使用String作为Object )。

原始变量不能以相同的方式互换,无论是彼此之间还是与Object 最明显的原因(但不是唯一的原因)是它们的大小差异。 这使得原始类型在这方面不方便,但我们在语言中仍然需要它们(主要归结为性能的原因)。

泛型和类型擦除

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

泛型类型比非泛型复杂得多。 当它们被引入 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等等的过程称为装箱 反之称为拆箱 因为每次想要将原语用作Object必须装箱很不方便,所以在某些情况下语言会自动执行此操作 - 这称为自动装箱

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

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

之所以需要它,是因为程序员很容易能够直接编写代码,并且 JVM 将负责装箱和拆箱。

当我们使用 java.util.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)中的每一种都有一个单独的 Wrapper 类与它们相关联。 这些 Wrapper 类具有预定义的方法,用于对原始数据类型执行有用的操作。

包装类的使用

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

Wrapper 类提供了许多有用的功能。 在此处查看 Java 文档

拆箱与自动装箱相反,我们将包装类对象转换回其原始类型。 这是由 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 . 

原始(非对象)类型在效率上是有道理的。

原始类型int, boolean, double是直接数据,而Object是引用。 因此字段(或变量)

int i;
double x;
Object s;

需要4+8+8的本地内存吗? 对于对象,仅存储对内存的引用(地址)。

使用对象包装器Integer, Double和其他包装器Integer, Double可以引入一种间接引用,即对堆内存中某个 Integer/Double 实例的引用。

为什么需要拳击?

这是一个相对范围的问题。 在未来的 java 中,计划能够有一个ArrayList<int> ,提升原始类型。

答:目前 ArrayList 仅适用于 Object,为对象引用保留空间,并同样管理垃圾收集。 因此泛型类型是 Object 的孩子。 因此,如果想要一个浮点值的 ArrayList,则需要将一个 double 包装在一个 Double 对象中。

这里 Java 与传统 C++ 的不同之处在于其模板:C++ 类vector<string>, vector<int>将创建两个编译产品。 Java 设计倾向于拥有一个 ArrayList.class,而不需要为每个参数类型都创建一个新的编译产品。

因此,如果不对 Object 进行装箱,则需要为每次出现的参数类型编译类。 具体来说:每个集合或容器类都需要一个 Object、int、double、boolean 版本。 Object 的版本将处理所有子类。

事实上,Java SE 中已经存在对 IntBuffer、CharBuffer、DoubleBuffer 等多样化的需求,它们对 int、char、double 进行操作。 通过从一个共同的来源生成这些来源,它以一种hacky的方式解决了。

从JDK 5开始,java增加了两个重要的功能:自动装箱和自动拆箱。 AutoBoxing是在需要此类对象时将基本类型自动封装在等效包装器中的过程。 您不必显式构造对象。 自动拆箱是当需要封装对象的值时,自动从类型包装器中提取其值的过程。 您不需要调用诸如intValue()doubleValue() 之类的方法

自动装箱和自动拆箱的加入大大简化了算法的编写,消除了手动装箱和拆箱值的诱饵。 避免错误也很有帮助。 对于只对对象进行操作的泛型也非常重要。 最后,自动装箱有助于使用集合框架

为什么我们有(取消)拳击?

使我们在混合原语及其面向对象 (OO) 替代方案的情况下编写代码更舒适/更简洁。

为什么我们有原语及其面向对象的替代品?

原始类型不是类(与 C# 不同),因此它们不是Object子类,不能被覆盖。

出于性能原因,我们有像int这样的原语,还有像Integer这样的Object替代方案,是为了 OO 编程的好处,并且作为一个小点,为实用程序常量和方法(Integer.MAX_VALUE 和Integer.toString(int) )提供了一个好的位置.

使用泛型( List<Integer> )最容易看到面向对象的好处,但不仅限于此,例如:

Number getMeSome(boolean wantInt) {

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

一些数据结构只能接受对象,不能接受原始类型。

示例:HashMap 中的键。

有关更多信息,请参阅此问题: HashMap and int as key

还有其他很好的理由,例如数据库中的“int”字段也可能为 NULL。 Java 中的 int 不能为 null ; 整数引用可以。 自动装箱和拆箱提供了一种便利,可以避免在来回转换中编写无关代码。

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

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

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

Integer 类将原始类型 int 的值包装在一个对象中。因此可以接受以下代码。

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

我们可以使用 add(value) 方法添加一个值。 要添加字符串值,在 strArrayList 代码中说“Hello”只是

strArrayList.add("Hello");  

并添加一个 int 值说 54 我们可以写

intArrayList.add(54);

但是当我们写 intArrayList.add(54); 编译器转换为以下行

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

由于 intArrayList.add(54) 很容易并且更容易被用户接受,所以编译器完成了艰苦的工作,即intArrayList.add(Integer.valueOf(54)); 它是自动装箱。

与检索值类似,我们只需键入 intArrayList.get(0) 并编译器转换为<code>intArrayList.get(0).intValue(); 这是自动拆箱。

因为它们是不同的类型,并且为了方便。 性能可能是使用原始类型的原因。

自动装箱:将原始值转换为相应包装器类的对象。

拆箱:将包装类型的对象转换为其相应的原始值

// 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); 

    } 
} 

另一个特例是,

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM