为什么Java集合不能直接存储原始类型?


123

Java集合仅存储对象,而不存储原始类型。但是我们可以存储包装器类。

为什么会有这种约束?


2
当您处理基元并且想要使用队列发送并且您的发送速率非常快时,此约束确实很糟糕。我现在正在处理自动装箱花费太多时间的问题。
JPM

从技术上讲,原始类型是对象(确切地说是此类的单个实例),它们不是由class而是由JVM 定义的。该语句int i = 1定义了一个指向intJVM 中定义的对象的单例实例的指针,该实例设置为1JVM中某个位置定义的值。是的,Java语言中的指针-语言实现只是从您那里抽象出来。原始语言不能用作泛型语言,因为该语言断言所有泛型类型都必须是超类型Object-因此为什么A<?>A<Object>在运行时编译为。
罗伯特·E·弗莱

1
@RobertEFry基本类型不是 Java中的对象,因此您编写的有关单例实例的所有内容从根本上都是错误和令人困惑的。我建议阅读Java语言规范的“类型,值和变量”一章,该章定义了对象是什么:“对象(第4.3.1节)是动态创建的类类型实例或动态创建的数组。 ”
typeracer

Answers:


99

这是一个Java设计决策,有人认为这是一个错误。容器需要对象,而基元不是从对象派生的。

这是.NET设计人员从JVM中学到的地方,并实现了值类型和泛型,因此在许多情况下都无需装箱。在CLR中,通用容器可以将值类型存储为基础容器结构的一部分。

Java选择在编译器中添加100%的通用支持,而没有JVM的支持。JVM本身就是这样,它不支持“非对象”对象。Java泛型允许您假装没有包装器,但是您仍然要付出拳击的性能代价。这对于某些类的程序很重要。

装箱是一种技术折衷,我认为这是泄漏到语言中的实现细节。自动装箱是不错的语法糖,但仍然会降低性能。如果有的话,我希望编译器在自动装箱时发出警告。(据我所知,现在也许是在2010年,我写了这个答案)。

关于拳击的一个很好的解释:为什么有些语言需要装箱和拆箱?

以及对Java泛型的批评: 为什么有人声称Java的泛型实现不好?

在Java的辩护中,很容易向后看和批评。JVM经受了时间的考验,并且在许多方面都是不错的设计。


6
没错,我认为精心选择的折衷确实确实为Java提供了很好的帮助。
DJClayworth 2011年

13
.NET从中吸取教训并从一开始就实现了自动装箱,而在VM级别上实现了通用,却没有装箱开销,这真是一个错误。Java自己进行的校正只是语法级别的解决方案,仍然受到自动装箱与根本没有装箱的性能影响。Java的实现对大型数据结构显示出较差的性能。
codenheim 2011年

2
@mrjoltcola:恕我直言,默认自动装箱是一个错误,但是应该有一种标记变量和参数的方法,可以自动装箱给它们的值。即使是现在,我觉得应该有添加到指定某些变量或参数应该的手段接受新的自动装箱值[例如,它应该是合法的传递Object.ReferenceEquals型的引用Object,其识别盒装整数,但它不应该是合法的传递整数值]。Java的自动拆箱很麻烦,恕我直言。
2014年

18

使实施更容易。由于Java原语不被视为对象,因此您需要为每个这些原语创建一个单独的集合类(无需共享模板代码)。

当然,您可以这样做,只需查看GNU TroveApache Commons PrimitivesHPPC

除非您有非常大的集合,否则包装程序的开销不足以让人们关心(并且,当您确实有非常大的原始集合时,您可能需要花一些精力来研究使用/构建专门的数据结构) )。


11

这是两个事实的结合:

  • Java基本类型不是引用类型(例如an int不是Object
  • Java使用引用类型的类型擦除来进行泛型(例如,在运行时a List<?>实际上是a List<Object>

由于这两个都是正确的,因此通用Java集合不能直接存储原始类型。为方便起见,引入了自动装箱以允许将原始类型自动装箱为参考类型。毫无疑问,尽管如此,集合仍在存储对象引用。

可以避免吗?也许。

  • 如果一个intObject,则根本不需要框类型。
  • 如果没有使用类型擦除来完成泛型,则可以将原语用于类型参数。

8

自动装箱和自动拆箱的概念。如果您尝试将储存在int中,List<Integer>则Java编译器会自动将其转换为Integer


1
自动装箱与泛型一起在Java 1.5中引入。
杰里米

1
但这是编译时的事情;没有性能优势的语法糖。Java编译器会自动装箱,因此与.NET之类的VM实现(与泛型不涉及装箱)相比,性能下降。
codenheim 2011年

1
@mrjoltcola:你有什么意思?我只是在分享事实,而不是在争论。
杰里米

3
我的观点是指出语法和性能之间的区别很重要。我也认为我的评论是事实分享,而不是论点。谢谢。
codenheim 2011年

2

它不是真正的约束吗?

考虑是否要创建一个存储原始值的集合。您将如何编写一个可以存储int,float或char的集合?最有可能您将获得多个集合,因此您将需要一个intlist和charlist等。

编写收集类时,可以利用Java的面向对象特性,它可以存储任何对象,因此只需要一个收集类。多态这一思想非常有力,并大大简化了库的设计。


7
“您将如何编写一个可以存储int,float或char的集合?” -与其他语言一样,正确实现的泛型/模板不会冒充所有东西的代价,因此是一个对象。
codenheim

Java六年来,我几乎没有想要存储原始类型的集合。即使在少数情况下,我可能想要使用参考对象的额外时间和空间成本也可以忽略不计。特别是,我发现许多人认为他们想要Map <int,T>,却忘记了数组会很好地完成这一技巧。
DJClayworth 2011年

2
@DJClayworth仅在键值是非稀疏的情况下才有效。当然,您可以使用辅助数组来跟踪键,但这有其自身的问题:相对有效的访问将需要根据键顺序对两个数组进行排序以允许二进制搜索,从而进行插入和删除除非插入/删除被模式化,否则插入的项目很可能会终止于先前删除的项目所在的位置和/或一些缓冲区散布在数组中等,否则效率不高。有可用的资源,但是最好有Java本身。
2014年

@JAB实际上,如果键是稀疏的,它可以正常工作,它比不稀疏的键只需要更多的内存。如果它们的键稀疏,则意味着它们的数量并不多,并且使用Integer作为键可以很好地工作。无论使用哪种方法,都需要最少的内存。或无论您不在乎的感觉。
DJClayworth

0

我认为我们可能会在基于JEP的Java 10中看到JDK在此领域的进展-http: //openjdk.java.net/jeps/218

如果要今天避免在集合中使用装箱原语,则有几种第三方替代方法。除了前面提到的第三方选项之外,还有Eclipse CollectionsFastUtilKoloboke

较早前还发布了原始图的比较,标题为:Large HashMap概述:JDK,FastUtil,Goldman Sachs,HPPC,Koloboke,Trove。GS Collections(Goldman Sachs)库已迁移到Eclipse Foundation,现在是Eclipse Collections。


0

主要原因是java的设计策略。++ 1)集合需要对象进行操作,而基元不是从对象派生的,因此这可能是另一个原因。2)Java原始数据类型不是ex的引用类型。int不是对象。

克服:-

我们有自动装箱和自动拆箱的概念。因此,如果您尝试存储原始数据类型,则编译器会自动将其转换为该原始数据类的对象。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.