为什么人们仍然在 Java 中使用原始类型?
从 Java 5 开始,我们对基本类型进行了装箱/拆箱,以便将 int
包装为 java.lang.Integer
,等等。
我最近看到很多新的 Java 项目(肯定需要版本至少为 5 的 JRE,如果不是 6),它们使用 int
而不是 java.lang. lang.Integer
,尽管使用后者更方便,因为它有一些帮助方法用于转换为 long
值等。
为什么有些仍然在 Java 中使用原始类型?有什么实实在在的好处吗?
Since Java 5, we've had boxing/unboxing of primitive types so that int
is wrapped to be java.lang.Integer
, and so and and so forth.
I see a lot of new Java projects lately (that definitely require a JRE of at least version 5, if not 6) that are using int
rather than java.lang.Integer
, though it's much more convenient to use the latter, as it has a few helper methods for converting to long
values et al.
Why do some still use primitive types in Java? Is there any tangible benefit?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(21)
在 Joshua Bloch 的 Effective Java 中,第 5 条:“避免创建不必要的对象”,他发布了以下代码示例:
并且运行需要 43 秒。将 Long 放入原语中会将其降低到 6.8 秒...如果这可以说明我们使用原语的原因的话。
对于 biziclop 来说,缺乏本机值相等性也是一个问题(
.equals()
与==
相比相当冗长):
结果为:
EDIT 为什么(3)返回
true
而(4)返回false
?因为它们是两个不同的对象。最接近零的 256 个整数 [-128; 127] 由 JVM 缓存,因此它们返回相同的对象。但是,超出该范围,它们不会被缓存,因此会创建一个新对象。为了让事情变得更复杂,JLS 要求至少缓存 256 个享元。 JVM 实现者可以根据需要添加更多,这意味着这可以在缓存最近的 1024 个并且所有这些都返回 true 的系统上运行... #awkward
In Joshua Bloch's Effective Java, Item 5: "Avoid creating unnecessary objects", he posts the following code example:
and it takes 43 seconds to run. Taking the Long into the primitive brings it down to 6.8 seconds... If that's any indication why we use primitives.
The lack of native value equality is also a concern (
.equals()
is fairly verbose compared to==
)for biziclop:
Results in:
EDIT Why does (3) return
true
and (4) returnfalse
?Because they are two different objects. The 256 integers closest to zero [-128; 127] are cached by the JVM, so they return the same object for those. Beyond that range, though, they aren't cached, so a new object is created. To make things more complicated, the JLS demands that at least 256 flyweights be cached. JVM implementers may add more if they desire, meaning this could run on a system where the nearest 1024 are cached and all of them return true... #awkward
自动拆箱可能会导致难以发现 NPE
在大多数情况下,对
in
的空赋值比上面的要不那么明显。Autounboxing can lead to hard to spot NPEs
In most situations the null assignment to
in
is a lot less obvious than above.盒装类型的性能较差并且需要更多内存。
Boxed types have poorer performance and require more memory.
原始类型:
现在评估:
它是
true
。不足为奇。现在尝试盒装类型:现在评估:
它是
false
。大概。取决于运行时间。这个理由够吗?Primitive types:
Now evaluate:
It's
true
. Hardly surprising. Now try the boxed types:Now evaluate:
It's
false
. Probably. Depends on the runtime. Is that reason enough?除了性能和内存问题之外,我想提出另一个问题: 如果没有
int
,List
接口将会被破坏。问题是重载的
remove()
方法(remove(int)
与删除(对象)
)。remove(Integer)
始终会解析为调用后者,因此您无法通过索引删除元素。另一方面,尝试添加和删除
int
时存在一个陷阱:Besides performance and memory issues, I'd like to come up with another issue: The
List
interface would be broken withoutint
.The problem is the overloaded
remove()
method (remove(int)
vs.remove(Object)
).remove(Integer)
would always resolve to calling the latter, so you could not remove an element by index.On the other hand, there is a pitfall when trying to add and remove an
int
:你真的能想象一个
用 java.lang.Integer 代替的循环吗? java.lang.Integer 是不可变的,因此循环中的每个增量都会在堆上创建一个新的 java 对象,而不是仅使用单个 JVM 指令在堆栈上增量 int 。表现将是恶魔般的。
我真的不同意使用 java.lang.Integer 比 int 更方便。相反。自动装箱意味着您可以在必须使用 Integer 的地方使用 int,并且 java 编译器会负责插入代码来为您创建新的 Integer 对象。自动装箱就是允许您在需要 Integer 的地方使用 int,并让编译器插入相关的对象构造。它从一开始就没有消除或减少对 int 的需求。通过自动装箱,您可以两全其美。当您需要基于堆的 java 对象时,您会自动创建一个 Integer,并且当您仅进行算术和本地计算时,您会获得 int 的速度和效率。
Can you really imagine a
loop with java.lang.Integer instead? A java.lang.Integer is immutable, so each increment round the loop would create a new java object on the heap, rather than just increment the int on the stack with a single JVM instruction. The performance would be diabolical.
I would really disagree that it's much mode convenient to use java.lang.Integer than int. On the contrary. Autoboxing means that you can use int where you would otherwise be forced to use Integer, and the java compiler takes care of inserting the code to create the new Integer object for you. Autoboxing is all about allowing you to use an int where an Integer is expected, with the compiler inserting the relevant object construction. It in no way removes or reduces the need for the int in the first place. With autoboxing you get the best of both worlds. You get an Integer created for you automatically when you need a heap based java object, and you get the speed and efficiency of an int when you are just doing arithmetic and local calculations.
基本类型快得多:
整数(所有数字以及字符串)是一种不可变类型:一旦创建就无法更改。如果
i
是 Integer,那么i++
将创建一个新的 Integer 对象 - 在内存和处理器方面要昂贵得多。Primitive types are much faster:
Integer (all Numbers and also a String) is an immutable type: once created it can not be changed. If
i
was Integer, thani++
would create a new Integer object - much more expensive in terms of memory and processor.首先,也是最重要的一点,习惯。如果您已经使用 Java 编写了八年的代码,您就会积累相当大的惰性。如果没有令人信服的理由,为什么要改变呢?使用盒装原语并没有带来任何额外的优势。
另一个原因是断言
null
不是有效选项。将两个数字之和或循环变量声明为 Integer 是毫无意义且具有误导性的。它也有性能方面的问题,虽然性能差异在很多情况下并不重要(尽管当它是时,情况非常糟糕),但没有人喜欢编写可以以我们已经采用的更快方式轻松编写的代码习惯了。
First and foremost, habit. If you've coded in Java for eight years, you accumulate a considerable amount of inertia. Why change if there is no compelling reason to do so? It's not as if using boxed primitives comes with any extra advantages.
The other reason is to assert that
null
is not a valid option. It would be pointless and misleading to declare the sum of two numbers or a loop variable asInteger
.There's the performance aspect of it too, while the performance difference isn't critical in many cases (though when it is, it's pretty bad), nobody likes to write code that could be written just as easily in a faster way we're already used to.
顺便说一句,Smalltalk 只有对象(没有基元),但他们优化了小整数(不是全部使用 32 位,只使用 27 位等),不分配任何堆空间,而只是使用特殊的位模式。其他常见对象(true、false、null)也有特殊的位模式。
因此,至少在 64 位 JVM(具有 64 位指针命名空间)上,应该可以根本不存在任何 Integer、Character、Byte、Short、Boolean、Float(和小型 Long)对象(除了这些创建的对象)通过显式的
new ...()
),只有特殊的位模式,可以由普通运算符非常有效地操作。By the way, Smalltalk has only objects (no primitives), and yet they had optimized their small integers (using not all 32 bits, only 27 or such) to not allocate any heap space, but simply use a special bit pattern. Also other common objects (true, false, null) had special bit patterns here.
So, at least on 64-bit JVMs (with a 64 bit pointer namespace) it should be possible to not have any objects of Integer, Character, Byte, Short, Boolean, Float (and small Long) at all (apart from these created by explicit
new ...()
), only special bit patterns, which could be manipulated by the normal operators quite efficiently.我不敢相信没有人提到我认为最重要的原因:
“int”就是这样,比“Integer”更容易输入。我认为人们低估了简洁语法的重要性。性能并不是避免使用它们的真正原因,因为大多数时候使用数字时都在循环索引中,并且在任何非平凡循环中递增和比较这些数字都不会产生任何成本(无论您使用的是 int 还是 Integer)。
另一个原因是您可以获得 NPE,但是使用装箱类型很容易避免这种情况(只要您始终将它们初始化为非空值,就可以保证避免这种情况)。
另一个原因是 (new Long(1000))==(new Long(1000)) 为 false,但这只是“.equals”对装箱类型没有语法支持的另一种说法(与运算符 <、 >、= 等),所以我们回到“更简单的语法”原因。
我认为 Steve Yegge 的非原始循环示例很好地说明了我的观点:
http://sites.google.com/site/steveyegge2/language-trickery-and-ejb< /a>
想一想:与必须使用 Runnable 等接口来模拟函数类型的 java 相比,您在具有良好语法的语言(例如任何函数式语言、python、ruby 甚至 C)中使用函数类型的频率有多高可调用类和无名类。
I can't believe no one has mentioned what I think is the most important reason:
"int" is so, so much easier to type than "Integer". I think people underestimate the importance of a concise syntax. Performance isn't really a reason to avoid them because most of the time when one is using numbers is in loop indexes, and incrementing and comparing those costs nothing in any non-trivial loop (whether you're using int or Integer).
The other given reason was that you can get NPEs but that's extremely easy to avoid with boxed types (and it is guaranteed to be avoided as long as you always initialize them to non-null values).
The other reason was that (new Long(1000))==(new Long(1000)) is false, but that's just another way of saying that ".equals" has no syntactic support for boxed types (unlike the operators <, >, =, etc), so we come back to the "simpler syntax" reason.
I think Steve Yegge's non-primitive loop example illustrates my point very well:
http://sites.google.com/site/steveyegge2/language-trickery-and-ejb
Think about this: how often do you use function types in languages that have good syntax for them (like any functional language, python, ruby, and even C) compared to java where you have to simulate them using interfaces such as Runnable and Callable and nameless classes.
不摆脱原语的几个原因:
如果它被消除,任何旧程序都将无法运行。
整个 JVM 必须重写才能支持这个新事物。
您需要存储值和引用,这会使用更多内存。如果您有一个巨大的字节数组,则使用
byte
的大小明显小于使用Byte
的大小。声明
int i
然后使用i
执行操作不会导致任何问题,但声明Integer i
然后执行相同操作会导致 NPE。考虑这段代码:
将是错误的。运算符必须重载,这将导致对内容进行重大重写。
对象包装器比原始对象包装器慢得多。
Couple of reasons not to get rid of primitives:
If it's eliminated, any old programs wouldn't even run.
The entire JVM would have to be rewritten to support this new thing.
You'd need to store the value and the reference, which uses more memory. If you have a huge array of bytes, using
byte
's is significantly smaller than usingByte
's.Declaring
int i
then doing stuff withi
would result in no issues, but declaringInteger i
and then doing the same would result in an NPE.Consider this code:
Would be false. Operators would have to be overloaded, and that would result in a major rewrite of stuff.
Object wrappers are significantly slower than their primitive counterparts.
对象比原始类型要重量级得多,因此原始类型比包装类的实例要高效得多。
原始类型非常简单:例如 int 是 32 位,并且在内存中恰好占用 32 位,并且可以直接操作。 Integer 对象是一个完整的对象,它(像任何对象一样)必须存储在堆上,并且只能通过对其的引用(指针)进行访问。它很可能还占用超过 32 位(4 字节)的内存。
也就是说,Java 区分原始类型和非原始类型这一事实也是 Java 编程语言时代的标志。较新的编程语言没有这种区别;这种语言的编译器足够聪明,可以自行判断您使用的是简单值还是更复杂的对象。
例如,在 Scala 中,没有原始类型;有一个用于整数的 Int 类,并且 Int 是一个真实的对象(您可以对其进行方法等)。当编译器编译代码时,它会在幕后使用原始 int,因此使用 Int 与在 Java 中使用原始 int 一样高效。
Objects are much more heavyweight than primitive types, so primitive types are much more efficient than instances of wrapper classes.
Primitive types are very simple: for example an int is 32 bits and takes up exactly 32 bits in memory, and can be manipulated directly. An Integer object is a complete object, which (like any object) has to be stored on the heap, and can only be accessed via a reference (pointer) to it. It most likely also takes up more than 32 bits (4 bytes) of memory.
That said, the fact that Java has a distinction between primitive and non-primitive types is also a sign of age of the Java programming language. Newer programming languages don't have this distinction; the compiler of such a language is smart enough to figure out by itself if you're using simple values or more complex objects.
For example, in Scala there are no primitive types; there is a class Int for integers, and an Int is a real object (that you can methods on etc.). When the compiler compiles your code, it uses primitive ints behind the scenes, so using an Int is just as efficient as using a primitive int in Java.
除了其他人所说的之外,原始局部变量不是从堆分配的,而是在堆栈上分配的。但对象是从堆中分配的,因此必须进行垃圾收集。
In addition to what others have said, primitive local variables are not allocated from the heap, but instead on the stack. But objects are allocated from the heap and thus have to be garbage collected.
很难知道幕后正在进行什么样的优化。
对于本地使用,当编译器有足够的信息来进行优化排除空值的可能性时,我期望性能相同或相似。
然而,基元数组显然与装箱基元集合非常不同。这是有道理的,因为在集合深处几乎不可能进行优化。
此外,与
int
相比,Integer
具有更高的逻辑开销:现在您必须担心int 是否为= b + c;
抛出异常。我会尽可能多地使用原语,并依靠工厂方法和自动装箱在需要时为我提供语义上更强大的装箱类型。
It's hard to know what kind of optimizations are going on under the covers.
For local use, when the compiler has enough information to make optimizations excluding the possibility of the null value, I expect the performance to be the same or similar.
However, arrays of primitives are apparently very different from collections of boxed primitives. This makes sense given that very few optimizations are possible deep within a collection.
Furthermore,
Integer
has a much higher logical overhead as compared withint
: now you have to worry about about whether or notint a = b + c;
throws an exception.I'd use the primitives as much as possible and rely on the factory methods and autoboxing to give me the more semantically powerful boxed types when they are needed.
顺便说一句,我不介意看到这样的东西进入了 Java。
其中for循环自动将loop1从0增加到1000
或者
其中 for 循环自动将 Loop1 递减 1000 到 0。
On a side note, I wouldn't mind seeing something like this find it's way into Java.
Where the for loop automatically increments loop1 from 0 to 1000
or
Where the for loop automatically decrements loop1 1000 to 0.
基本类型有很多优点:
Primitive types have many advantages:
您应该问为什么需要类/对象类型 拥有
对象类型的原因是让我们在处理集合时更轻松。基元不能直接添加到 List/Map,而是需要编写一个包装类。现成的整数类可以帮助您,而且它还有许多实用方法,例如 Integer.pareseInt(str)
You should ask why Class/Object type is required
Reason for having Object type is to make our life easier when we deal with Collections. Primitives cannot be added directly to List/Map rather you need to write a wrapper class. Readymade Integer kind of Classes helps you here plus it has many utility methods like Integer.pareseInt(str)
我同意之前的答案,使用原语包装对象可能会很昂贵。
但是,如果性能在您的应用程序中并不重要,那么您可以在使用对象时避免溢出。例如:
bigNumber
的值是 -2147483647,您期望它是 2147483649。这是代码中的一个错误,可以通过执行以下操作来修复:bigNumber
会为 2147483649。此类错误有时很容易被错过,并可能导致未知的行为或漏洞(请参阅 CWE-190)。如果您使用包装对象,则等效代码将无法编译。
因此,使用原语包装对象可以更轻松地阻止此类问题。
您的问题已经得到了回答,我的回复只是为了添加一些之前未提及的信息。
I agree with previous answers, using primitives wrapper objects can be expensive.
But, if performance is not critical in your application, you avoid overflows when using objects. For example:
The value of
bigNumber
is -2147483647, and you would expect it to be 2147483649. It's a bug in the code that would be fixed by doing:And
bigNumber
would be 2147483649. These kind of bugs sometimes are easy to be missed and can lead to unknown behavior or vulnerabilities (see CWE-190).If you use wrapper objects, the equivalent code won't compile.
So it's easier to stop these kind of issues by using primitives wrapper objects.
Your question is so answered already, that I reply just to add a little bit more information not mentioned before.
因为JAVA以原始类型执行所有数学运算。考虑这个例子:
这里,提醒和一元加运算不能应用于 Integer(Reference) 类型,编译器执行拆箱并执行操作。
因此,请确保java程序中发生了多少次自动装箱和拆箱操作。因为,执行此操作需要时间。
一般来说,最好保留引用类型的参数和原始类型的结果。
Because JAVA performs all mathematical operations in primitive types. Consider this example:
Here, reminder and unary plus operations can not be applied on Integer(Reference) type, compiler performs unboxing and do the operations.
So, make sure how many autoboxing and unboxing operations happen in java program. Since, It takes time to perform this operations.
Generally, it is better to keep arguments of type Reference and result of primitive type.
原始类型速度更快并且需要更少的内存。因此,我们可能更喜欢使用它们。
另一方面,当前的 Java 语言规范不允许在参数化类型(泛型)、Java 集合或反射 API 中使用原始类型。
当我们的应用程序需要包含大量元素的集合时,我们应该考虑使用尽可能“经济”类型的数组。
*有关详细信息,请参阅来源:https://www.baeldung.com/java-基元与对象
The primitive types are much faster and require much less memory. Therefore, we might want to prefer using them.
On the other hand, current Java language specification doesn’t allow usage of primitive types in the parameterized types (generics), in the Java collections or the Reflection API.
When our application needs collections with a big number of elements, we should consider using arrays with as more “economical” type as possible.
*For detailed info see the source: https://www.baeldung.com/java-primitives-vs-objects
简而言之:原始类型比盒装类型更快并且需要更少的内存
To be brief: primitive types are faster and require less memory than boxed ones