¿Por qué las colecciones Java no pueden almacenar directamente tipos primitivos?

123

Las colecciones Java solo almacenan objetos, no tipos primitivos; Sin embargo, podemos almacenar las clases de contenedor.

¿Por qué esta restricción?

JavaUser
fuente
2
Esta restricción apesta cuando manejas primitivas y quieres usar Colas para enviar y tus tasas de envío son muy rápidas. Estoy lidiando con ese problema en este momento de que el autoboxing toma demasiado tiempo.
JPM
Técnicamente, los tipos primitivos son objetos (instancias únicas de tales para ser exactos), simplemente no están definidos por a class, sino por la JVM. La instrucción int i = 1define un puntero a la instancia singleton del objeto que se define inten la JVM, establecido en el valor 1definido en algún lugar de la JVM. Sí, punteros en Java: la implementación del lenguaje simplemente lo abstrae de ti. Las primitivas no se pueden usar como genéricas porque el lenguaje predica todos los tipos genéricos deben ser del supertipo Object, de ahí por qué se A<?>compila A<Object>en tiempo de ejecución.
Robert E Fry
1
Los tipos primitivos @RobertEFry no son objetos en Java, por lo que todo lo que escribió sobre instancias de singleton y demás es fundamentalmente incorrecto y confuso. Sugiero leer el capítulo "Tipos, valores y variables" de la Especificación del lenguaje Java, que define qué es un objeto: "Un objeto (§4.3.1) es una instancia creada dinámicamente de un tipo de clase o una matriz creada dinámicamente. "
typeracer

Respuestas:

99

Fue una decisión de diseño de Java, y una que algunos consideran un error. Los contenedores quieren objetos y las primitivas no derivan de objetos.

Este es un lugar que los diseñadores de .NET aprendieron de la JVM e implementaron tipos de valores y genéricos, de modo que el boxeo se elimina en muchos casos. En CLR, los contenedores genéricos pueden almacenar tipos de valores como parte de la estructura del contenedor subyacente.

Java optó por agregar soporte genérico al 100% en el compilador sin soporte de la JVM. Siendo JVM lo que es, no es compatible con un objeto "no objeto". Los genéricos de Java le permiten pretender que no hay envoltorio, pero aún así paga el precio de rendimiento del boxeo. Esto es IMPORTANTE para ciertas clases de programas.

El boxeo es un compromiso técnico, y creo que se trata de detalles de implementación que se filtran al lenguaje. El autoboxing es un buen azúcar sintáctico, pero sigue siendo una penalización de rendimiento. En todo caso, me gustaría que el compilador me avise cuando se autoboxes. (Por lo que sé, puede que ahora, escribí esta respuesta en 2010).

Una buena explicación sobre SO sobre el boxeo: ¿Por qué algunos idiomas necesitan Boxing y Unboxing?

Y críticas a los genéricos de Java: ¿por qué algunos afirman que la implementación de genéricos de Java es mala?

En defensa de Java, es fácil mirar hacia atrás y criticar. La JVM ha resistido la prueba del tiempo y es un buen diseño en muchos aspectos.

Codenheim
fuente
66
No es un error, una compensación cuidadosamente elegida que creo que ha servido muy bien a Java.
DJClayworth
13
Fue un error suficiente que .NET aprendió de él e implementó el autoboxing desde el principio, y los genéricos a nivel de VM sin sobrecarga del boxeo. El intento de corrección de Java fue solo una solución de nivel de sintaxis que todavía sufre el impacto de rendimiento del autoboxing frente a ningún boxeo. La implementación de Java ha mostrado un bajo rendimiento con grandes estructuras de datos.
Codenheim
2
@mrjoltcola: En mi humilde opinión, el autoboxing predeterminado es un error, pero debería haber una forma de etiquetar variables y parámetros que deberían encuadrar automáticamente los valores que se les asignaron. Incluso ahora, creo que debería haber un medio agregado para especificar que ciertas variables o parámetros no deberían aceptar valores recién encuadrados automáticamente [por ejemplo, debería ser legal pasar Object.ReferenceEqualsreferencias de tipo Objectque identifiquen enteros en caja, pero no debería ser legal para pasar un valor entero]. El auto-desempaquetado de Java es, en mi humilde opinión, desagradable.
supercat
18

Facilita la implementación. Dado que las primitivas de Java no se consideran objetos, necesitaría crear una clase de colección separada para cada una de estas primitivas (sin código de plantilla para compartir).

Puede hacerlo, por supuesto, solo vea GNU Trove , Apache Commons Primitives o HPPC .

A menos que tenga colecciones realmente grandes, los gastos generales para los envoltorios no son lo suficientemente importantes para que las personas se preocupen (y cuando tiene colecciones primitivas realmente grandes, es posible que desee dedicar el esfuerzo de considerar el uso / construcción de una estructura de datos especializada para ellos )

Thilo
fuente
11

Es una combinación de dos hechos:

  • Los tipos primitivos de Java no son tipos de referencia (por ejemplo, un intno es un Object)
  • Java hace utilizando los genéricos tipo de borrado de los tipos de referencia (por ejemplo, una List<?>es realmente una List<Object>en tiempo de ejecución)

Dado que ambos son ciertos, las colecciones genéricas de Java no pueden almacenar tipos primitivos directamente. Para mayor comodidad, se introduce el autoboxing para permitir que los tipos primitivos se encuadren automáticamente como tipos de referencia. Sin embargo, no se equivoque, las colecciones aún almacenan referencias de objetos independientemente.

¿Podría esto haberse evitado? Quizás.

  • Si an intes an Object, entonces no hay necesidad de ningún tipo de cuadro.
  • Si los genéricos no se hacen usando el borrado de tipo, entonces las primitivas podrían haberse usado para los parámetros de tipo.
poligenelubricantes
fuente
8

Existe el concepto de auto-boxing y auto-unboxing. Si intenta almacenar un inten un List<Integer>compilador de Java lo convertirá automáticamente en un Integer.

Jeremy
fuente
1
Autoboxing se introdujo en Java 1.5 junto con Generics.
Jeremy
1
Pero es una cosa de tiempo de compilación; sintaxis de azúcar sin beneficio de rendimiento. Las compilaciones automáticas del compilador Java, de ahí la penalización del rendimiento en comparación con las implementaciones de máquinas virtuales como .NET, cuyos genéricos no incluyen el boxeo.
Codenheim
1
@mrjoltcola: ¿Cuál es tu punto? Solo estaba compartiendo hechos, no discutiendo.
Jeremy
3
Mi punto es importante señalar la diferencia entre sintaxis y rendimiento. También considero que mis comentarios comparten hechos, no argumentos. Gracias.
Codenheim
2

No es realmente una restricción, ¿verdad?

Considere si desea crear una colección que almacena valores primitivos. ¿Cómo escribirías una colección que pueda almacenar int, float o char? Lo más probable es que termines con múltiples colecciones, por lo que necesitarás una lista de intlistas y una lista de charlas, etc.

Aprovechando la naturaleza orientada a objetos de Java cuando escribe una clase de colección, puede almacenar cualquier objeto, por lo que solo necesita una clase de colección. Esta idea, el polimorfismo, es muy poderosa y simplifica enormemente el diseño de bibliotecas.

Vincent Ramdhanie
fuente
77
"¿Cómo escribirías una colección que pueda almacenar int, o float o char?" - Con genéricos / plantillas correctamente implementados como otros idiomas que no pagan la pena de pretender que todo es un objeto.
codenheim
Casi nunca en seis años de Java quería almacenar una colección de primitivas. Incluso en los pocos casos en los que podría haber querido el tiempo extra y los costos de espacio para usar los objetos de referencia ha sido insignificante. En particular, encuentro que muchas personas piensan que quieren Map <int, T>, olvidando que una matriz hará ese truco muy bien.
DJClayworth
2
@DJClayworth Eso solo funciona bien si los valores clave no son escasos. Por supuesto, podría usar una matriz auxiliar para realizar un seguimiento de las claves, pero eso tiene sus propios problemas: el acceso relativamente eficiente requeriría mantener ambas matrices ordenadas según el orden de las claves para permitir la búsqueda binaria, lo que a su vez haría la inserción y eliminación ineficiente a menos que la inserción / eliminación tenga un patrón tal que los elementos insertados puedan terminar donde estaba un elemento previamente eliminado y / o algunos búferes estén intercalados en las matrices, etc. Hay recursos disponibles, pero sería bueno tenerlos en Java en sí.
JAB
@JAB En realidad funciona bien si las teclas son escasas, solo necesita más memoria que si no son escasas. Y si las teclas son escasas, eso significa que no hay muchas y usar Integer como clave funciona bien. Use el enfoque que necesite menos memoria. O lo que quieras si no te importa.
DJClayworth
0

Creo que podríamos ver progreso en este espacio en el JDK posiblemente en Java 10 basado en este JEP: http://openjdk.java.net/jeps/218 .

Si desea evitar las primitivas de boxeo en las colecciones de hoy, existen varias alternativas de terceros. Además de las opciones de terceros mencionadas anteriormente, también hay Eclipse Collections , FastUtil y Koloboke .

Hace un tiempo también se publicó una comparación de mapas primitivos con el título: Descripción general de HashMap grande: JDK, FastUtil, Goldman Sachs, HPPC, Koloboke, Trove . La biblioteca GS Collections (Goldman Sachs) se migró a la Fundación Eclipse y ahora es Eclipse Collections.

Donald Raab
fuente
0

La razón principal es la estrategia de diseño de Java. ++ 1) las colecciones requieren objetos para su manipulación y las primitivas no se derivan del objeto, por lo que esta puede ser la otra razón. 2) Los tipos de datos primitivos de Java no son tipos de referencia para ej. int no es un objeto.

Para superar:-

Tenemos el concepto de auto-boxing y auto-unboxing. así que si está tratando de almacenar tipos de datos primitivos, el compilador lo convertirá automáticamente en objeto de esa clase de datos primitivos.

usuario5693566
fuente