为什么人们仍然在Java中使用原始类型?

时间:2011-03-04 21:16:46

标签: java primitive primitive-types autoboxing jdk1.5

从Java 5开始,我们已经对原始类型进行了装箱/取消装箱,因此int被包装为java.lang.Integer,依此类推。

我最近看到很多新的Java项目(肯定需要至少版本为5的JRE,如果不是6)使用int而不是java.lang.Integer虽然使用后者更方便,因为它有一些辅助方法可以转换为long值等。

为什么一些仍然在Java中使用原始类型?是否有任何实际的好处?

21 个答案:

答案 0 :(得分:372)

在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()==相比相当冗长)

for 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

EDIT 为什么(3)返回true和(4)返回false

因为它们是两个不同的对象。最接近零的256个整数[-128;缓存由JVM缓存,因此它们返回相同的对象。但是,超出该范围,它们不会被缓存,因此会创建一个新对象。为了使事情变得更复杂,JLS要求缓存至少 256个flyweights。如果需要,JVM实现者可以添加更多,这意味着它可以在最近的1024被缓存并且所有这些都返回true的系统上运行... #awkward

答案 1 :(得分:78)

Autounboxing可能导致难以发现NPE

Integer in = null;
...
...
int i = in; // NPE at runtime

在大多数情况下,in的空值分配比上面的要少得多。

答案 2 :(得分:39)

原始类型:

int x = 1000;
int y = 1000;

现在评估:

x == y

这是true。不足为奇。现在尝试盒装类型:

Integer x = 1000;
Integer y = 1000;

现在评估:

x == y

这是false。大概。取决于运行时。这个理由够了吗?

答案 3 :(得分:39)

盒装类型的性能较差,需要更多内存。

答案 4 :(得分:35)

除了性能和内存问题之外,我还想提出另一个问题:List interface在没有int的情况下会被破坏。
问题是重载的remove()方法(remove(int)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!

答案 5 :(得分:27)

你能想象一下

吗?
  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对象。 Autoboxing就是允许你在编译器中插入相关的对象结构时使用一个期望Integer的int。它绝不会首先消除或减少对int的需求。通过自动装箱,你可以获得两全其美的效果。当您需要基于堆的Java对象时,会自动为您创建一个Integer,并且当您进行算术和本地计算时,您可以获得int的速度和效率。

答案 6 :(得分:18)

原始类型更多更快:

int i;
i++;

Integer(所有Numbers和String)都是不可变类型:一旦创建,就无法更改。如果i是Integer,那么i++将创建一个新的Integer对象 - 在内存和处理器方面要贵得多。

答案 7 :(得分:16)

首先,习惯。如果你用Java编写了八年,你就会积累相当多的惯性。如果没有令人信服的理由,为什么要改变呢?这并不是说使用盒装基元带来任何额外的优势。

另一个原因是断言null不是有效选项。将两个数字或一个循环变量的总和声明为Integer将是毫无意义和误导。

它的性能方面也是如此,而性能差异在许多情况下并不重要(尽管它很糟糕),没有人喜欢编写可以更快速地编写代码的代码已经习惯了。

答案 8 :(得分:12)

顺便说一句,Smalltalk只有对象(没有基元),但他们已经优化了它们的小整数(不是全部使用32位,只有27位等)来不分配任何堆空间,而只是使用特殊的位模式。此外,其他常见对象(true,false,null)也有特殊的位模式。

所以,至少在64位JVM(具有64位指针命名空间)上,应该可以没有任何Integer,Character,Byte,Short,Boolean,Float(和小Long)的对象(分开)从显式new ...()创建的这些,只有特殊的位模式,可以非常有效地由普通运算符操作。

答案 9 :(得分:9)

我无法相信没有人提到我认为最重要的原因: “int”是这样的,比“Integer”更容易输入。我认为人们低估了简洁语法的重要性。性能并不是避免它们的真正原因,因为大多数时候使用数字都在循环索引中,并且在任何非平凡的循环中无论是使用int还是Integer,增量和比较这些都不会产生任何成本。 / p>

另一个原因是你可以获得NPE,但使用盒装类型非常容易避免(只要你总是将它们初始化为非空值,就可以避免它。)

另一个原因是(new Long(1000))==(new Long(1000))是假的,但这只是说“.equals”对盒装类型没有语法支持的另一种方式(与运算符不同) &lt;,&gt;,=等),所以我们回到“更简单的语法”的原因。

我认为Steve Yegge的非原始循环示例很好地说明了我的观点: http://sites.google.com/site/steveyegge2/language-trickery-and-ejb

想一想:与使用Runnable等接口模拟它们的java相比,你经常使用具有良好语法的语言中的函数类型(比如任何函数语言,python,ruby甚至C)和可调用和无名的类。

答案 10 :(得分:8)

有几个原因不能摆脱原语:

  • 向后兼容性。

如果它被淘汰,任何旧程序都无法运行。

  • JVM重写。

必须重写整个JVM才能支持这一新功能。

  • 更大的内存占用。

您需要存储使用更多内存的值和引用。如果你有大量字节,使用byte&#39; s比使用Byte&#39; s要小得多。

  • 空指针问题。

声明int i然后使用i执行操作会导致没有问题,但声明Integer i然后执行相同操作会导致NPE。

  • 平等问题。

考虑以下代码:

Integer i1 = 5;
Integer i2 = 5;

i1 == i2; // Currently would be false.

会是假的。操作员必须超载,这将导致重大的重写。

对象包装器的速度明显慢于原始对象。

答案 11 :(得分:7)

除了其他人所说的,原始局部变量不是从堆中分配的,而是在堆栈上分配的。但是对象是从堆中分配的,因此必须进行垃圾回收。

答案 12 :(得分:7)

对象比原始类型重得多,因此原始类型比包装类的实例更有效。

原语类型非常简单:例如,int是32位,在内存中占用32位,可以直接操作。 Integer对象是一个完整的对象,它(像任何对象一样)必须存储在堆上,并且只能通过它的引用(指针)来访问。它很可能也占用超过32位(4字节)的内存。

也就是说,Java在原始类型和非原始类型之间有区别这一事实也是Java编程语言时代的标志。较新的编程语言没有这种区别;这种语言的编译器足够智能,如果您使用简单值或更复杂的对象,就可以自行计算出来。

例如,在Scala中没有原始类型;有一个类Int用于整数,而Int是一个真实的对象(你可以使用方法等)。当编译器编译代码时,它在幕后使用原始int,因此使用Int与在Java中使用原始int一样高效。

答案 13 :(得分:5)

很难知道幕后发生了什么样的优化。

对于本地使用,当编译器有足够的信息进行优化以排除空值的可能性时,我希望性能相同或类似

然而,原始数组显然来自盒装基元的集合very different。这是有道理的,因为在集合中很少有可能进行优化。

此外,与Integer相比,int具有更高的逻辑开销:现在您必须担心int a = b + c;是否会引发异常

我尽可能地使用原语并依赖工厂方法和autoboxing在需要时为我提供更具语义功能的盒装类型。

答案 14 :(得分:5)

原始类型有许多优点:

  • 更简单的代码编写
  • 由于您没有为变量
  • 实例化对象,因此性能更好
  • 由于它们不代表对象的引用,因此无需检查空值
  • 除非您需要利用拳击功能,否则请使用原始类型。

答案 15 :(得分:5)

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

循环的百万秒&#39; 100000000&#39;龙的时间:468

     

循环的百万秒&#39; 100000000&#39;时间长: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。

答案 16 :(得分:2)

  1. 你需要原语进行数学运算
  2. 如上所述,原语占用的内存较少,性能更佳
  3. 您应该问为什么需要Class / Object类型

    拥有对象类型的原因是为了让我们在处理集合时更轻松。无法将基元直接添加到List / Map,而是需要编写包装类。 Readymade Integer类可以帮助你,还有很多实用方法,比如Integer.pareseInt(str)

答案 17 :(得分:2)

我同意以前的答案,使用原语包装器对象可能很昂贵。 但是,如果性能在您的应用程序中并不重要,那么在使用对象时可以避免溢出。例如:

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

因此,使用基元包装器对象可以更轻松地阻止这类问题。

您的问题已经得到了回答,我回复的目的只是添加一些之前未提及的信息。

答案 18 :(得分:1)

原始类型 快得多,并且所需的存储空间更少。因此,我们可能更希望使用它们。

另一方面,当前的Java语言规范不允许在Java集合或Reflection API中使用参数化类型(泛型)中的基元类型。

当我们的应用程序需要具有大量元素的集合时,我们应考虑使用尽可能具有更多“经济”类型的数组,如上图所示。

*有关详细信息,请参见来源:https://www.baeldung.com/java-primitives-vs-objects

答案 19 :(得分:0)

因为JAVA以原始类型执行所有数学运算。考虑这个例子:

public static int sumEven(List<Integer> li) {
    int sum = 0;
    for (Integer i: li)
        if (i % 2 == 0)
            sum += i;
        return sum;
}

这里,提醒和一元加操作不能应用于整数(参考)类型,编译器执行拆箱并执行操作。

因此,请确保在java程序中发生了多少次自动装箱和拆箱操作。因为,执行此操作需要时间。

通常,最好保留类型为Reference的参数和基本类型的结果。

答案 20 :(得分:0)

简而言之:原始类型比盒装类型更快并且需要更少的内存