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

Naf*_*Kay 157 java primitive autoboxing jdk1.5 primitive-types

从Java 5开始,我们已经对原始类型进行了装箱/拆箱,因此它int被包装成java.lang.Integer等等.

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

为什么有些人仍然在Java中使用原始类型?有什么实际好处吗?

cor*_*iKa 380

在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);
}
Run Code Online (Sandbox Code Playgroud)

运行需要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));
    }
}
Run Code Online (Sandbox Code Playgroud)

结果是:

false
false
true
false
Run Code Online (Sandbox Code Playgroud)

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

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

  • 现在想象一下`i`也被宣布为'Long`! (53认同)
  • @Catalin我不同意你的观点,即自动装箱是完全​​失败的.它有一些缺陷,与任何其他可以使用的设计没有什么不同(包括什么都没有.)它们非常清楚你能做什么和不能指望什么,以及他们希望开发人员知道并遵守合同的任何其他设计那些设计. (32认同)
  • @TREE - 规范实际上*要求*VM在一定范围内创建flyweights.但遗憾的是它*允许*他们扩展该范围,这意味着程序在不同的VM上可能表现不同.对于跨平台而言...... (14认同)
  • 随着越来越多糟糕的设计选择,Java已经走下坡路.自动装箱是完全​​失败的,它既不健壮,也不可预测或便携.我真的很想知道他们在想什么...而不是修复可怕的原始对象二元性,他们设法让它比起初更糟. (12认同)
  • @NaftuliTzviKay那不是"失败".他们[**非常清楚**](http://docs.oracle.com/javase/7/docs/technotes/guides/language/autoboxing.html)使`==`运算符执行引用标识比较`int`表达式的`Integer`表达式和值相等比较.出于这个原因,存在`Integer.equals()`.您应该**永远不要使用`=='来比较任何非基本类型的值.这是Java 101. (8认同)
  • @John:不.他们是对的.;)它可能取决于特定的JVM,但是一些JVM通过为零自动编码的原始数字创建flyweights来优化. (5认同)
  • @John是的,这很疯狂.不幸的是,由于人们经常对1,5或其他小值进行快速测试,他们错误地认为任何valueOf都等于true.我已经看到它导致了一些缺乏经验的程序员的错误. (2认同)

Hei*_*upp 82

自动装箱可能导致难以发现NPE

Integer in = null;
...
...
int i = in; // NPE at runtime
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,null赋值in比上面的要少得多.


Orb*_*bit 40

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


Dan*_*ker 40

原始类型:

int x = 1000;
int y = 1000;
Run Code Online (Sandbox Code Playgroud)

现在评估:

x == y
Run Code Online (Sandbox Code Playgroud)

是的true.不足为奇.现在尝试盒装类型:

Integer x = 1000;
Integer y = 1000;
Run Code Online (Sandbox Code Playgroud)

现在评估:

x == y
Run Code Online (Sandbox Code Playgroud)

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


xeh*_*puk 35

除了性能和内存问题,我还想提出另一个问题: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!
Run Code Online (Sandbox Code Playgroud)

  • 它会被打破,是的.但是,remove(int)是一个设计缺陷IMO.如果混合的可能性最小,方法名称永远不应该超载. (7认同)
  • @MrBackend:当设计`List` API时,Generics和Autoboxing都不存在,所以没有机会混合`remove(int)`和`remove(Object)`... (6认同)
  • @MrBackend足够公平.有趣的是,`Vector`从一开始就有`removeElementAt(int)`.`remove(int)`是在Java 1.2中使用collections框架引入的. (4认同)

Tom*_*don 27

你能想象一下吗?

  for (int i=0; i<10000; i++) {
      do something
  }
Run Code Online (Sandbox Code Playgroud)

用java.lang.Integer循环?java.lang.Integer是不可变的,因此循环中的每个增量都会在堆上创建一个新的java对象,而不是仅使用单个JVM指令递增堆栈上的int.表演将是恶魔般的.

我真的不同意使用java.lang.Integer比使用int更方便.反之.自动装箱意味着你可以使用int,否则你将被迫使用Integer,java编译器会负责插入代码来为你创建新的Integer对象.Autoboxing就是允许你在编译器中插入相关的对象结构时使用一个期望Integer的int.它绝不会首先消除或减少对int的需求.通过自动装箱,您可以获得两全其美的效果.当您需要基于堆的Java对象时,会自动为您创建一个Integer,并且当您进行算术和本地计算时,您可以获得int的速度和效率.


Pet*_*ego 19

基本类型有很多速度快:

int i;
i++;
Run Code Online (Sandbox Code Playgroud)

整数(所有数字和字符串)都是不可变类型:一旦创建,就无法更改.如果i是Integer,i++则会创建一个新的Integer对象 - 在内存和处理器方面要贵得多.

  • @Paŭlo:说原始价值是不可改变的,是没有意义的.将原始变量重新分配给新值时,不会创建任何新值.不涉及内存分配.彼得的观点是:i ++对于一个原语没有内存分配,但对于它必然会有一个对象. (4认同)

biz*_*lop 16

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

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

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

  • 我不同意.性能方面可能至关重要.很少这可能是惯性或习惯的力量. (15认同)
  • @Eddie它可以,但它很少.相信我,对于大多数人来说,表现论点只是一个借口. (7认同)
  • 我也想保护性能论点.在使用Dalvik的Android上,您创建的每个对象都会增加调用GC的"风险",并且暂停的对象越长.因此,在循环中创建Integers而不是int可能会花费一些丢帧. (3认同)
  • @PSIXO 这是一个公平的观点,我在编写它时考虑到了纯粹的服务器端 Java。移动设备是完全不同的动物。但我的观点是,即使是那些在不考虑性能的情况下编写糟糕代码的开发人员也会以此为理由,在他们看来,这听起来很像是一个借口。 (2认同)

Paŭ*_*ann 12

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

因此,至少在64位JVM(具有64位指针命名空间)上应该可以没有任何Integer,Character,Byte,Short,Boolean,Float(和小Long)的对象(除了这些创建之外)通过显式new ...()),只有特殊的位模式,可以非常有效地由普通操作员操纵.

  • 我做了Smalltalk多年.优化仍然非常昂贵,因为对于int上的每个操作,他们必须取消屏蔽并重新应用它们.当前java在处理原始数字时与C相当.使用unmask + mask,它可能会慢30%. (3认同)

Cro*_*yer 9

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

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

另一个原因是(new Long(1000))==(new Long(1000))是假的,但这只是说".equals"对盒装类型没有语法支持的另一种方式(不像运算符<,> ,=等等,所以我们回到"更简单的语法"的原因.

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

想想这个:你经常使用具有良好语法的语言中的函数类型(比如任何函数语言,python,ruby甚至是C),而java必须使用Runnable和Callable等接口来模拟它们.无名的课程.


Anu*_*oob 8

几个理由不摆脱原始:

  • 向后兼容性.

如果它被淘汰,任何旧程序都不会运行.

  • JVM重写.

必须重写整个JVM才能支持这个新东西.

  • 更大的内存占用.

您需要存储使用更多内存的值和引用.如果你有一个庞大的字节数组,使用byte's'远远小于使用Byte's'.

  • 空指针问题.

int i然后声明做什么i会导致没有问题,但是声明Integer i然后做同样会导致NPE.

  • 平等问题.

考虑以下代码:

Integer i1 = 5;
Integer i2 = 5;

i1 == i2; // Currently would be false.
Run Code Online (Sandbox Code Playgroud)

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

对象包装器比它们的原始对应物慢得多.


Ume*_*cha 7

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

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

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

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


Edd*_*die 7

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

  • 对不起,这是错的.智能JVM可以对任何对象分配进行转义分析,如果它们无法转义,则在堆栈上分配它们. (3认同)
  • 是的,这是*开始*成为现代JVM的一个特性.在五年内,您所说的对于当时使用的大多数JVM都是如此.今天不是.我几乎评论过这个,但决定不对它发表评论.也许我应该说些什么. (2认同)

小智 6

原始类型有许多优点:

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


Mat*_*lis 5

很难知道幕后正在进行哪种优化.

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

但是,基元数组显然与盒装基元的集合非常不同.这是有道理的,因为在集合中很少有可能进行优化.

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

我尽可能地使用原语并依赖工厂方法和自动装箱,以便在需要时为我提供更具语义功能的盒装类型.


小智 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));
Run Code Online (Sandbox Code Playgroud)

在Long周围循环'100000000'的毫秒数:468

在长时间内循环"100000000"次的毫秒数:31

在旁注中,我不介意看到这样的东西发现它进入Java.

Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
   ...
}
Run Code Online (Sandbox Code Playgroud)

for循环自动将loop1从0增加到1000或

Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
   ...
}
Run Code Online (Sandbox Code Playgroud)

for循环自动将loop1 1000减少为0.