为啥我们在 Java 中使用自动装箱和拆箱?
Posted
技术标签:
【中文标题】为啥我们在 Java 中使用自动装箱和拆箱?【英文标题】:Why do we use autoboxing and unboxing in Java?为什么我们在 Java 中使用自动装箱和拆箱? 【发布时间】:2015-02-23 04:36:24 【问题描述】:自动装箱是 Java 编译器进行的自动转换 在原始类型及其对应的对象包装器之间 类。例如,将 int 转换为 Integer,将 double 转换为 双倍,以此类推。如果转换以另一种方式进行,这是 称为拆箱。
那么为什么我们需要它以及为什么我们在 Java 中使用自动装箱和拆箱?
【问题讨论】:
基本用于泛型..Integer
有parseInt
方法。 int
没有。 :)
@VishalZanzrukia 所以只是为了获得更多功能?
你可以拥有List<Integer>
,但你不能拥有List<int>
。
是..正是..获得更多功能
【参考方案1】:
需要一些上下文才能完全理解这背后的主要原因。
基元与类
Java 中的原始变量包含值(整数、双精度浮点二进制数等)。因为these values may have different lengths,包含它们的变量也可能有不同的长度(考虑float
与double
)。
另一方面,类变量包含对实例的引用。在许多语言中,引用通常被实现为指针(或与指针非常相似的东西)。这些东西通常具有相同的大小,无论它们引用的实例的大小如何(Object
、String
、Integer
等)。
类变量的这个属性使得它们包含的引用可以互换(在一定程度上)。这使我们能够进行我们所说的替换:广义上讲,to use an instance of a particular type as an instance of another, related type(例如,将String
用作Object
)。
原始变量不能以同样的方式互换,既不能相互互换,也不能与Object
互换。最明显的原因(但不是唯一的原因)是它们的尺寸差异。这使得原始类型在这方面不方便,但我们仍然需要在语言中使用它们(原因主要归结为性能)。
泛型和类型擦除
泛型类型是具有一个或多个类型参数的类型(确切的数字称为泛型参数)。例如,通用类型定义List<T>
有一个类型参数T
,可以是Object
(产生一个具体类型List<Object>
),@987654338 @ (List<String>
)、Integer
(List<Integer>
) 等等。
泛型类型比非泛型类型复杂得多。当它们被引入 Java 时(在其初始版本之后),为了避免对 JVM 进行彻底的更改并可能破坏与旧二进制文件的兼容性,Java 的创建者决定以侵入性最小的方式实现泛型类型: 实际上,List<T>
的所有具体类型都编译为(二进制等价物)List<Object>
(对于其他类型,边界可能不是Object
,但你明白了)。 泛型和类型参数信息在这个过程中丢失了,这就是我们称之为type erasure的原因。
将两者放在一起
现在的问题是上述现实的结合:如果List<T>
在所有情况下都变成List<Object>
,那么T
必须始终是可以直接分配给Object
的类型。其他任何事情都不允许。因为,正如我们之前所说,int
、float
和 double
不能与 Object
互换,所以不能有 List<int>
、List<float>
或 List<double>
(除非明显更复杂JVM 中存在泛型的实现)。
但是 Java 提供了像 Integer
、Float
和 Double
这样的类型,它们将这些原语包装在类实例中,使它们可以有效地替换为 Object
,因此 允许泛型类型间接使用原语 也是如此(因为您可以拥有List<Integer>
、List<Float>
、List<Double>
等等)。
从int
创建Integer
、从float
创建Float
等等的过程称为boxing。反过来称为拆箱。因为每次您想将它们用作Object
时都必须将它们装箱很不方便,在某些情况下语言会自动执行此操作 - that's called autoboxing。
【讨论】:
根据您的解释,我们需要这些类 Integer, String, ... 来实现泛型。还有其他原因吗?【参考方案2】:自动装箱使用将原始数据类型转换为其包装类对象。 Wrapper 类提供了对原始类型执行的广泛功能。最常见的例子是:
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 文档here
拆箱与自动装箱相反,我们将包装类对象转换回其原始类型。这是由 JVM 自动完成的,因此我们可以将包装类用于某些操作,然后将它们转换回原始类型,因为原始类型会导致 int 更快的处理。例如:
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 .
【讨论】:
【参考方案3】:原始(非对象)类型在效率上有理由。
基本类型int, boolean, double
是直接数据,而Object
s 是引用。因此字段(或变量)
int i;
double x;
Object s;
需要本地内存 4+8+8 吗?对象只存储对内存的引用(地址)。
使用对象包装器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 的版本将处理所有子类。
事实上,对于 IntBuffer、CharBuffer、DoubleBuffer 等在 Java SE 中已经存在这种多样化的需求,它们对 int、char、double 进行操作。通过从一个共同的来源生成这些来源以一种骇人听闻的方式解决了这个问题。
【讨论】:
【参考方案4】:从 JDK 5 开始,java 增加了两个重要的功能:自动装箱和自动拆箱。 AutoBoxing 是在需要此类对象时将原始类型自动封装在等效包装器中的过程。您不必显式构造对象。 自动拆箱是在需要封装对象的值时自动从类型包装器中提取其值的过程。您不需要调用 intValue() 或 doubleValue() 等方法。
添加自动装箱和自动拆箱大大简化了编写算法,消除了手动装箱和拆箱值的诱饵。 避免错误也很有帮助。 对于只对对象进行操作的泛型来说也很重要。最后,自动装箱有助于使用 Collections Framework。
【讨论】:
【参考方案5】:一些数据结构只能接受对象,不能接受原始类型。
示例:HashMap 中的键。
请参阅此问题了解更多信息:HashMap and int as key
还有其他很好的理由,例如数据库中的“int”字段也可能为 NULL。 Java 中的 int 不能为 null ;一个整数引用可以。自动装箱和拆箱提供了避免在来回转换中编写无关代码的工具。
【讨论】:
【参考方案6】:我们为什么要(取消)拳击?
让我们在混合原语及其面向对象 (OO) 替代方案的情况下编写代码更舒适/更简洁。
为什么我们有原语及其 OO 替代方案?
原始类型不是类(与 C# 不同),因此它们不是 Object
的子类,不能被覆盖。
出于性能原因,我们有像int
这样的原语,以及像Integer
这样的Object
替代品,因为它有利于OO 编程,并且作为一个次要的点,为实用程序常量和方法(整数. MAX_VALUE 和Integer.toString(int)
)。
使用泛型 (List<Integer>
) 最容易看到 OO 的好处,但不限于此,例如:
Number getMeSome(boolean wantInt)
if (wantInt)
return Integer.MAX_VALUE;
else
return Long.MAX_VALUE;
【讨论】:
【参考方案7】: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();
,即自动拆箱。
【讨论】:
【参考方案8】:因为它们是不同的类型,并且是为了方便。性能可能是使用原始类型的原因。
【讨论】:
【参考方案9】:自动装箱:将原始值转换为相应包装类的对象。
拆箱:将包装类型的对象转换为其对应的原始值
// 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);
【讨论】:
【参考方案10】:另一个特殊情况是,
Integer intval = null;
int toPrimitive = intval;
System.out.println(toPrimitive);
对于上述情况,我们收到了NullPointerException
。这意味着我们可以捕获 NPE
【讨论】:
以上是关于为啥我们在 Java 中使用自动装箱和拆箱?的主要内容,如果未能解决你的问题,请参考以下文章