为什么我们在 Java 中使用自动装箱和拆箱? [英] Why do we use autoboxing and unboxing in Java?

查看:41
本文介绍了为什么我们在 Java 中使用自动装箱和拆箱?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

<块引用>

Autoboxing 是 Java 编译器进行的自动转换在原始类型和它们对应的对象包装器之间类.例如,将 int 转换为 Integer,将 double 转换为双等.如果转换方向相反,这是称为拆箱.

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

解决方案

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

基元与类

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

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

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

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

泛型和类型擦除

泛型类型是具有一个或多个类型参数的类型(确切的数字称为泛型参数).例如,泛型类型定义List有一个类型参数T,可以是Object(生成具体类型 List), String (List), Integer (List) 等等.

泛型比非泛型复杂得多.当它们被引入 Java 时(在其初始版本之后),为了避免对 JVM 进行根本性的更改并可能破坏与旧二进制文件的兼容性,Java 的创建者决定以侵入性最小的方式实现泛型类型:List 的所有具体类型实际上都编译为(二进制等价物)List(对于其他类型,边界可能是 Object 以外的东西,但你明白了).泛型参数和类型参数信息在这个过程中丢失了,这就是我们称之为输入擦除.

将两者放在一起

现在的问题是以上现实的结合:如果List在所有情况下都变成List,那么T 必须始终是可以直接分配给 Object 的类型.不能允许其他任何事情.因为,正如我们之前所说,intfloatdouble 不能与 Object 互换,所以可以't 是 ListListList(除非在JVM).

但是 Java 提供了诸如 IntegerFloatDouble 之类的类型,它们将这些原语包装在类实例中,使它们可以有效地替代为 对象,因此允许泛型类型间接使用原语(因为你可以拥有ListListList 等等).

int创建Integer的过程,从float创建Float的过程等等,称为装箱.反之称为拆箱.因为每次想要将原语用作 Object 时都必须装箱很不方便,有些情况下语言会自动执行此操作 - 这就是所谓的自动装箱.

Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes. For example, converting an int to an Integer, a double to a Double, and so on. If the conversion goes the other way, this is called unboxing.

So why do we need it and why do we use autoboxing and unboxing in Java?

解决方案

Some context is required to fully understand the main reason behind this.

Primitives versus classes

Primitive variables in Java contain values (an integer, a double-precision floating point binary number, etc). Because these values may have different lengths, the variables containing them may also have different lengths (consider float versus double).

On the other hand, class variables contain references to instances. References are typically implemented as pointers (or something very similar to pointers) in many languages. These things typically have the same size, regardless of the sizes of the instances they refer to (Object, String, Integer, etc).

This property of class variables makes the references they contain interchangeable (to an extent). This allows us to do what we call substitution: broadly speaking, to use an instance of a particular type as an instance of another, related type (use a String as an Object, for example).

Primitive variables aren't interchangeable in the same way, neither with each other, nor with Object. The most obvious reason for this (but not the only reason) is their size difference. This makes primitive types inconvenient in this respect, but we still need them in the language (for reasons that mainly boil down to performance).

Generics and type erasure

Generic types are types with one or more type parameters (the exact number is called generic arity). For example, the generic type definition List<T> has a type parameter T, which can be Object (producing a concrete type List<Object>), String (List<String>), Integer (List<Integer>) and so on.

Generic types are a lot more complicated than non-generic ones. When they were introduced to Java (after its initial release), in order to avoid making radical changes to the JVM and possibly breaking compatibility with older binaries, the creators of Java decided to implement generic types in the least invasive way: all concrete types of List<T> are, in fact, compiled to (the binary equivalent of) List<Object> (for other types, the bound may be something other than Object, but you get the point). Generic arity and type parameter information are lost in this process, which is why we call it type erasure.

Putting the two together

Now the problem is the combination of the above realities: if List<T> becomes List<Object> in all cases, then T must always be a type that can be directly assigned to Object. Anything else can't be allowed. Since, as we said before, int, float and double aren't interchangeable with Object, there can't be a List<int>, List<float> or List<double> (unless a significantly more complicated implementation of generics existed in the JVM).

But Java offers types like Integer, Float and Double which wrap these primitives in class instances, making them effectively substitutable as Object, thus allowing generic types to indirectly work with the primitives as well (because you can have List<Integer>, List<Float>, List<Double> and so on).

The process of creating an Integer from an int, a Float from a float and so on, is called boxing. The reverse is called unboxing. Because having to box primitives every time you want to use them as Object is inconvenient, there are cases where the language does this automatically - that's called autoboxing.

这篇关于为什么我们在 Java 中使用自动装箱和拆箱?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆