从 Java 5 开始,我们已经对原始类型进行了装箱/拆箱,以便将int
其包装为 be java.lang.Integer
,等等。
我最近看到很多新的 Java 项目(如果不是 6,肯定需要至少版本 5 的 JRE)使用int
而不是java.lang.Integer
,尽管使用后者更方便,因为它有一些用于转换的辅助方法long
价值观等。
为什么有些人仍然在 Java 中使用原始类型?有什么实实在在的好处吗?
从 Java 5 开始,我们已经对原始类型进行了装箱/拆箱,以便将int
其包装为 be java.lang.Integer
,等等。
我最近看到很多新的 Java 项目(如果不是 6,肯定需要至少版本 5 的 JRE)使用int
而不是java.lang.Integer
,尽管使用后者更方便,因为它有一些用于转换的辅助方法long
价值观等。
为什么有些人仍然在 Java 中使用原始类型?有什么实实在在的好处吗?
在 Joshua Bloch 的Effective Java的第 5 项:“避免创建不必要的对象”中,他发布了以下代码示例:
public static void main(String[] args) {
Long sum = 0L; // uses Long, not long
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
运行时间为 43 秒。将 Long 带入原语可以将其降低到 6.8 秒……如果这是我们使用原语的任何迹象。
缺乏原生价值平等也是一个问题(.equals()
与此相比相当冗长==
)
对于 biziclop:
class Biziclop {
public static void main(String[] args) {
System.out.println(new Integer(5) == new Integer(5));
System.out.println(new Integer(500) == new Integer(500));
System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
System.out.println(Integer.valueOf(500) == Integer.valueOf(500));
}
}
结果是:
false
false
true
false
编辑 为什么(3)返回true
和(4)返回false
?
因为它们是两个不同的对象。最接近零的 256 个整数 [-128; 127] 被 JVM 缓存,因此它们返回相同的对象。但是,超出该范围,它们不会被缓存,因此会创建一个新对象。为了让事情变得更复杂,JLS 要求至少缓存 256 个享元。JVM 实现者可以根据需要添加更多内容,这意味着这可以在缓存最近的 1024 并且所有这些都返回 true 的系统上运行...#awkward
自动拆箱可能导致难以发现 NPE
Integer in = null;
...
...
int i = in; // NPE at runtime
在大多数情况下,null 赋值in
远没有上面那么明显。
盒装类型的性能较差,需要更多内存。
原始类型:
int x = 1000;
int y = 1000;
现在评估:
x == y
是true
。不足为奇。现在尝试盒装类型:
Integer x = 1000;
Integer y = 1000;
现在评估:
x == y
是false
。大概。取决于运行时。这个理由够吗?
除了性能和内存问题,我想提出另一个问题:List
如果没有int
.
问题是重载的remove()
方法(remove(int)
vs. remove(Object)
)。 remove(Integer)
将始终解决调用后者,因此您无法按索引删除元素。
另一方面,尝试添加和删除时存在一个陷阱int
:
final int i = 42;
final List<Integer> list = new ArrayList<Integer>();
list.add(i); // add(Object)
list.remove(i); // remove(int) - Ouch!
你真的能想象一个
for (int i=0; i<10000; i++) {
do something
}
用 java.lang.Integer 循环代替?java.lang.Integer 是不可变的,因此循环中的每个增量都会在堆上创建一个新的 java 对象,而不是仅使用单个 JVM 指令在堆栈上增加 int。表演将是恶魔般的。
我真的不同意使用 java.lang.Integer 比使用 int 方便得多。相反。自动装箱意味着您可以使用 int,否则您将被迫使用 Integer,并且 java 编译器会负责插入代码为您创建新的 Integer 对象。自动装箱就是允许您在需要 Integer 的地方使用 int,编译器插入相关的对象构造。它绝不会首先消除或减少对 int 的需求。通过自动装箱,您可以获得两全其美的效果。当您需要基于堆的 java 对象时,您会自动为您创建一个 Integer,而当您只进行算术和本地计算时,您可以获得 int 的速度和效率。
原始类型要快得多:
int i;
i++;
整数(所有数字和字符串)是一种不可变类型:一旦创建就无法更改。如果i
是 Integer,i++
则将创建一个新的 Integer 对象——在内存和处理器方面要昂贵得多。
首先,习惯。如果你已经用 Java 编码了八年,你就会积累相当多的惯性。如果没有令人信服的理由,为什么要改变?使用盒装原语并没有任何额外的优势。
另一个原因是断言这null
不是一个有效的选项。将两个数字之和或循环变量声明为Integer
.
它也有性能方面的问题,虽然在许多情况下性能差异并不重要(尽管当它是非常糟糕的时候),没有人喜欢编写可以以更快的方式轻松编写的代码,我们已经习惯了。
顺便说一句,Smalltalk 只有对象(没有原语),但他们已经优化了它们的小整数(不是全部使用 32 位,仅使用 27 位等)以不分配任何堆空间,而只是使用特殊的位模式。其他常见对象(true、false、null)在这里也有特殊的位模式。
因此,至少在 64 位 JVM(具有 64 位指针命名空间)上应该可能根本没有任何 Integer、Character、Byte、Short、Boolean、Float(和小 Long)对象(除了这些创建的通过显式new ...()
),只有特殊的位模式,可以由普通运算符非常有效地操作。
我不敢相信没有人提到我认为最重要的原因:“int”是如此,比“Integer”更容易输入。我认为人们低估了简洁语法的重要性。性能并不是避免它们的真正理由,因为大多数时候使用数字是在循环索引中,并且在任何非平凡循环中递增和比较这些成本都没有(无论您使用的是 int 还是 Integer)。
另一个给定的原因是您可以获得 NPE,但使用盒装类型非常容易避免这种情况(只要您始终将它们初始化为非空值,就可以保证避免这种情况)。
另一个原因是 (new Long(1000))==(new Long(1000)) 是假的,但这只是另一种说法,“.equals”对盒装类型没有语法支持(不像运算符 <, > , = 等),所以我们回到“更简单的语法”的原因。
我认为 Steve Yegge 的非原始循环示例很好地说明了我的观点: http ://sites.google.com/site/steveyegge2/language-trickery-and-ejb
想一想:与必须使用诸如 Runnable 和 Callable 等接口模拟它们的 java 相比,您在具有良好语法的语言(如任何函数式语言、python、ruby 甚至 C)中使用函数类型的频率是多少?无名的班级。
不摆脱原语的几个原因:
如果它被淘汰,任何旧程序甚至都不会运行。
必须重写整个 JVM 以支持这个新事物。
您需要存储使用更多内存的值和引用。如果你有一个巨大的字节数组,使用byte
's 比使用 's 小得多Byte
。
声明int i
然后做事情i
不会导致任何问题,但声明Integer i
然后做同样的事情会导致 NPE。
考虑这段代码:
Integer i1 = 5;
Integer i2 = 5;
i1 == i2; // Currently would be false.
会是假的。运算符将不得不重载,这将导致内容的重大重写。
对象包装器比它们的原始对应物慢得多。
对象比原始类型重得多,因此原始类型比包装类的实例更有效。
原始类型非常简单:例如 int 是 32 位,在内存中占用 32 位,可以直接操作。Integer 对象是一个完整的对象,它(像任何对象一样)必须存储在堆上,并且只能通过对其的引用(指针)来访问。它很可能还占用超过 32 位(4 字节)的内存。
也就是说,Java 区分原始类型和非原始类型这一事实也是 Java 编程语言时代的标志。较新的编程语言没有这种区别。这种语言的编译器足够聪明,可以自行判断您使用的是简单值还是更复杂的对象。
例如,在 Scala 中没有原始类型;有一个 Int 类用于整数,而 Int 是一个真实的对象(您可以对其进行方法等)。当编译器编译你的代码时,它在后台使用原始 int,因此使用 Int 与在 Java 中使用原始 int 一样有效。
除了其他人所说的,原始局部变量不是从堆中分配的,而是在堆栈中分配的。但是对象是从堆中分配的,因此必须进行垃圾回收。
很难知道幕后正在进行什么样的优化。
对于本地使用,当编译器有足够的信息进行优化排除空值的可能性时,我希望性能相同或相似。
然而,基元数组显然与盒装基元的集合非常不同。这是有道理的,因为在集合深处几乎没有优化可能。
此外,与 : 相比具有Integer
更高的逻辑开销int
,现在您必须担心是否int a = b + c;
引发异常。
我会尽可能多地使用原语,并在需要时依靠工厂方法和自动装箱来为我提供语义上更强大的装箱类型。
原始类型有很多优点:
int loops = 100000000;
long start = System.currentTimeMillis();
for (Long l = new Long(0); l<loops;l++) {
//System.out.println("Long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around Long: "+ (System.currentTimeMillis()- start));
start = System.currentTimeMillis();
for (long l = 0; l<loops;l++) {
//System.out.println("long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around long: "+ (System.currentTimeMillis()- start));
在 Long 周围循环“100000000”次所需的毫秒数:468
循环“100000000”次所需的毫秒数:31
在旁注中,我不介意看到这样的东西找到它进入 Java 的方式。
Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
...
}
其中 for 循环自动将 loop1 从 0 增加到 1000 或
Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
...
}
其中 for 循环自动将 loop1 1000 递减为 0。
你应该问为什么需要类/对象类型
使用 Object 类型的原因是为了让我们在处理 Collections 时更轻松。基元不能直接添加到 List/Map 中,而是需要编写一个包装类。现成的整数类在这里可以帮助你,而且它有很多实用方法,比如 Integer.pareseInt(str)
我同意以前的答案,使用原语包装对象可能很昂贵。但是,如果性能在您的应用程序中并不重要,您可以在使用对象时避免溢出。例如:
long bigNumber = Integer.MAX_VALUE + 2;
的值为bigNumber
-2147483647,您会认为它是 2147483649。这是代码中的一个错误,可以通过以下方式修复:
long bigNumber = Integer.MAX_VALUE + 2l; // note that '2' is a long now (it is '2L').
并且bigNumber
将是 2147483649。这类错误有时很容易被遗漏,并可能导致未知行为或漏洞(请参阅CWE-190)。
如果您使用包装器对象,则等效代码将无法编译。
Long bigNumber = Integer.MAX_VALUE + 2; // Not compiling
因此,使用原语包装对象更容易阻止此类问题。
您的问题已经得到解答,我回复只是为了添加一些之前未提及的信息。
因为 JAVA 以原始类型执行所有数学运算。考虑这个例子:
public static int sumEven(List<Integer> li) {
int sum = 0;
for (Integer i: li)
if (i % 2 == 0)
sum += i;
return sum;
}
这里,提示和一元加号操作不能应用于 Integer(Reference) 类型,编译器执行拆箱并执行操作。
因此,请确保在 java 程序中发生了多少次自动装箱和拆箱操作。因为,执行此操作需要时间。
通常,最好保留 Reference 类型的参数和原始类型的结果。
原始类型更快并且需要更少的内存。因此,我们可能更喜欢使用它们。
另一方面,当前的 Java 语言规范不允许在参数化类型(泛型)、Java 集合或反射 API 中使用原始类型。
当我们的应用程序需要包含大量元素的集合时,我们应该考虑使用尽可能“经济”类型的数组。
*有关详细信息,请参阅来源:https ://www.baeldung.com/java-primitives-vs-objects
简而言之:原始类型比盒装类型更快并且需要更少的内存