¿Por qué la gente todavía usa tipos primitivos en Java?

163

Desde Java 5, hemos tenido boxing / unboxing de tipos primitivos para que intesté envuelto para ser java.lang.Integer, y así sucesivamente.

Veo muchos nuevos proyectos Java últimamente (que definitivamente requieren un JRE de al menos la versión 5, si no la 6) que se usan en intlugar de java.lang.Integer, aunque es mucho más conveniente usar este último, ya que tiene algunos métodos auxiliares para convertir a longvalores et al.

¿Por qué algunos todavía usan tipos primitivos en Java? ¿Hay algún beneficio tangible?

Naftuli Kay
fuente
49
¿Alguna vez pensó en el consumo de memoria y el rendimiento?
Tedil
76
Agregué la etiqueta de autoboxing ... y descubrí que tres personas realmente la siguen. De Verdad? ¿La gente sigue la etiqueta AUTOBOXING?
corsiKa
44
@glowcoder No son personas reales, son solo conceptos abstractos que asumen una forma humana para responder en SO. :)
biziclop
9
@TK Kocheran Principalmente porque new IntegeR(5) == new Integer(5), según las reglas, evaluar como falso.
biziclop
10
Vea GNU Trove o Mahout Collections o HPPC o ... para soluciones a colecciones de tipos primitivos. Aquellos de nosotros que nos preocupamos por la velocidad pasamos nuestro tiempo usando tipos más primitivos, no menos.
bmargulies

Respuestas:

395

En Java eficaz de Joshua Bloch , Elemento 5: "Evite crear objetos innecesarios", publica el siguiente ejemplo de código:

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

y tarda 43 segundos en ejecutarse. Tomar el Long en la primitiva lo reduce a 6.8 segundos ... Si eso es una indicación de por qué usamos primitivas.

La falta de igualdad de valor nativo también es una preocupación ( .equals()es bastante detallada en comparación con ==)

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

Resultados en:

false
false
true
false

EDITAR ¿Por qué (3) regresa truey (4) regresa false?

Porque son dos objetos diferentes. Los 256 enteros más cercanos a cero [-128; 127] son ​​almacenados en caché por la JVM, por lo que devuelven el mismo objeto para ellos. Sin embargo, más allá de ese rango, no se almacenan en caché, por lo que se crea un nuevo objeto. Para hacer las cosas más complicadas, el JLS exige que se almacenen en caché al menos 256 pesos mosca. Los implementadores de JVM pueden agregar más si lo desean, lo que significa que esto podría ejecutarse en un sistema donde los 1024 más cercanos se almacenan en caché y todos devuelven verdadero ... #awkward

corsiKa
fuente
54
¡Ahora imagine si también ise declararan Long!
ColinD
14
@TREE: la especificación realmente requiere máquinas virtuales para crear pesos moscas dentro de un cierto rango. Pero lamentablemente les permite ampliar ese rango, lo que significa que los programas pueden comportarse de manera diferente en diferentes máquinas virtuales. Esto en cuanto a plataformas ...
Daniel Earwicker
12
Java se ha ido por el desagüe, con más y más malas elecciones de diseño. El autoboxing es un completo fracaso, no es robusto, predecible ni portátil. Realmente me pregunto qué estaban pensando ... en lugar de arreglar la temida dualidad del objeto primitivo, lograron empeorarlo en primer lugar.
Pop Catalin
34
@Catalin No estoy de acuerdo con usted en que el autoboxing es un completo fracaso. Tiene algunas fallas, que no es diferente a cualquier otro diseño que podría haberse utilizado (sin incluir nada). Dejan muy claro lo que puede y no puede esperar, y como cualquier otro diseño, esperan que los desarrolladores conozcan y cumplan los contratos de esos diseños.
corsiKa
9
@NaftuliTzviKay Eso no es un "fracaso". Dejan MUY CLARO que el ==operador realiza comparaciones de identidad de referencia en Integerexpresiones y comparaciones de igualdad de valores en intexpresiones. Integer.equals()existe por esta misma razón. Usted debe nunca se use ==para comparar los valores de cualquier tipo no primitivo. Esto es Java 101.
NullUserException
86

El autounboxing puede conducir a NPE difíciles de detectar

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

En la mayoría de las situaciones, la asignación nula a ines mucho menos obvia que la anterior.

Heiko Rupp
fuente
43

Los tipos en caja tienen peor rendimiento y requieren más memoria.

Orbita
fuente
40

Tipos primitivos:

int x = 1000;
int y = 1000;

Ahora evalúe:

x == y

Es true. Difícil de sorprender. Ahora prueba los tipos en caja:

Integer x = 1000;
Integer y = 1000;

Ahora evalúe:

x == y

Es false. Probablemente. Depende del tiempo de ejecución. ¿Es esa razón suficiente?

Daniel Earwicker
fuente
36

Además de los problemas de rendimiento y memoria, me gustaría plantear otro problema: la Listinterfaz se rompería sin ella int.
El problema es el remove()método sobrecargado ( remove(int)vs. remove(Object)). remove(Integer)siempre resolvería llamar a este último, por lo que no podría eliminar un elemento por índice.

Por otro lado, hay un error al intentar agregar y eliminar un int:

final int i = 42;
final List<Integer> list = new ArrayList<Integer>();
list.add(i); // add(Object)
list.remove(i); // remove(int) - Ouch!
xehpuk
fuente
77
Estaría roto, sí. Sin embargo, remove (int) es un defecto de diseño de la OMI. Los nombres de los métodos nunca deben sobrecargarse si existe la menor posibilidad de una confusión.
MrBackend
44
@MrBackend bastante justo Curiosamente, Vectortenía removeElementAt(int)desde el principio. remove(int)se introdujo con el marco de colecciones en Java 1.2.
xehpuk
66
@MrBackend: cuando Listse diseñó la API, no existían ni Generics ni Autoboxing, por lo que no había posibilidad de mezclarse remove(int)y remove(Object)...
Holger
@Franklin Yu: claro, pero al diseñar un nuevo idioma / versión sin restricciones de compatibilidad, no se detendría en cambiar esa desafortunada sobrecarga. Simplemente deshacerse de la distinción de primitivos y valores encuadrados por completo, de modo que la pregunta que utilizar nunca aparecerá.
Holger
27

¿Realmente puedes imaginar un

  for (int i=0; i<10000; i++) {
      do something
  }

bucle con java.lang.Integer en su lugar? Un java.lang.Integer es inmutable, por lo que cada incremento alrededor del ciclo crearía un nuevo objeto java en el montón, en lugar de simplemente incrementar el int en la pila con una sola instrucción JVM. El rendimiento sería diabólico.

Realmente no estoy de acuerdo con que es mucho más conveniente usar java.lang.Integer que int. De lo contrario. Autoboxing significa que puede usar int donde de otro modo se vería obligado a usar Integer, y el compilador de Java se encarga de insertar el código para crear el nuevo objeto Integer por usted. Autoboxing se trata de permitirle usar un int donde se espera un Integer, con el compilador insertando la construcción del objeto relevante. De ninguna manera elimina o reduce la necesidad de int en primer lugar. Con autoboxing obtienes lo mejor de ambos mundos. Obtiene un Entero creado automáticamente cuando necesita un objeto Java basado en el montón, y obtiene la velocidad y la eficiencia de un int cuando solo está haciendo cálculos aritméticos y locales.

Tom Quarendon
fuente
19

Los tipos primitivos son mucho más rápidos:

int i;
i++;

El entero (todos los números y también una cadena) es un tipo inmutable : una vez creado, no se puede cambiar. Si ifuera Integer, i++entonces crearía un nuevo objeto Integer, mucho más costoso en términos de memoria y procesador.

Peter Knego
fuente
No desea que cambie una variable si lo hace i++en otra variable, por lo que Integer necesita ser inmutable para poder hacer esto (o al menos esto i++tendría que crear un nuevo objeto Integer de todos modos). (Y los valores primitivos son inmutables, también - sólo no comenta este ya que hay objetos.)
Paulo Ebermann
44
@ Paŭlo: Decir que los valores primitivos son inmutables no tiene sentido. Cuando reasigna una variable primitiva a un nuevo valor, no está creando nada nuevo. No hay asignación de memoria involucrada. El punto de Peter es: i ++ para un primitivo no asigna asignación de memoria, pero para un objeto necesariamente lo hace.
Eddie
@Eddie: (No necesariamente necesita asignación de memoria, también podría devolver un valor almacenado en caché. Creo que para algunos valores pequeños lo hace). Mi punto era que la inmutabilidad de los enteros aquí no es el punto decisivo, de todos modos querrías tener otro objeto, independientemente de la inmutabilidad.
Paŭlo Ebermann
@ Paŭlo: mi único punto fue que Integer es un orden de magnitud más lento que los primitivos. Y esto se debe al hecho de que los tipos en caja son inmutables y cada vez que se cambia un valor, se crea un nuevo objeto. No afirmé que hay algo mal con ellos o el hecho de que son inmutables. Solo que son más lentos y que un codificador debe saber eso. Eche un vistazo a cómo le va a Groovy sin tipos primitivos jroller.com/rants/entry/why_is_groovy_so_slow
Peter Knego
1
Inmutabilidad y ++es un arenque rojo aquí. Imagínese Java se ha mejorado para operador de soporte de la sobrecarga de una manera muy sencilla, de manera que si una clase (por ejemplo, Integertiene un método plus, entonces usted podría escribir i + 1en lugar de i.plus(1). Y suponer también que el compilador es suficientemente inteligente como para ampliar i++en i = i + 1. Ahora se podría decir i++y efectivamente "incrementar la variable i" sin Integerser mutable. "
Daniel Earwicker
16

Ante todo, hábito. Si ha codificado en Java durante ocho años, acumula una cantidad considerable de inercia. ¿Por qué cambiar si no hay una razón convincente para hacerlo? No es que usar primitivas en caja tenga ventajas adicionales.

La otra razón es afirmar que nullno es una opción válida. Sería inútil y engañoso declarar la suma de dos números o una variable de bucle como Integer.

También está el aspecto del rendimiento, aunque la diferencia de rendimiento no es crítica en muchos casos (aunque cuando lo es, es bastante malo), a nadie le gusta escribir código que podría escribirse con la misma facilidad de una manera más rápida. Acostumbrado a.

biziclop
fuente
15
Estoy en desacuerdo. El aspecto del rendimiento puede ser crítico. Muy poco de esto es probablemente inercial o fuerza de hábito.
Eddie
77
@ Eddie Puede ser, pero rara vez lo es. Confía en mí, para la mayoría de las personas los argumentos de rendimiento son solo una excusa.
biziclop
3
A mí también me gustaría proteger el argumento del rendimiento. En Android con Dalvik, cada objeto que cree aumentará el "riesgo" de que se llame a GC y cuantos más objetos tenga, las pausas serán más largas. Por lo tanto, crear números enteros en lugar de int en un bucle probablemente le costará algunos fotogramas descartados.
Igor Čordaš
1
@PSIXO Es un punto justo, lo escribí pensando exclusivamente en Java del lado del servidor. Los dispositivos móviles son un animal completamente diferente. Pero mi punto fue que incluso los desarrolladores que de otra manera escriben un código terrible sin tener en cuenta el rendimiento citarán esto como una razón, de ellos esto suena como una excusa.
biziclop
12

Por cierto, Smalltalk solo tiene objetos (sin primitivas) y, sin embargo, han optimizado sus enteros pequeños (utilizando no todos los 32 bits, solo 27 o similares) para no asignar ningún espacio de almacenamiento dinámico, sino simplemente usar un patrón de bits especial. También otros objetos comunes (verdadero, falso, nulo) tenían patrones de bits especiales aquí.

Por lo tanto, al menos en JVM de 64 bits (con un espacio de nombres de puntero de 64 bits) debería ser posible no tener ningún objeto de entero, carácter, byte, corto, booleano, flotante (y pequeño largo) (aparte de estos creados por explícito new ...()), solo patrones de bits especiales, que podrían ser manipulados por los operadores normales con bastante eficiencia.

Paŭlo Ebermann
fuente
Creo que debería haber dicho "algunas implementaciones", ya que esto no se rige por las especificaciones del lenguaje. (Y lamentablemente no puedo citar ninguna fuente aquí, es solo de lo que escuché en alguna parte.)
Paŭlo Ebermann
Entonces, JIT ya mantiene meta en el puntero; incl., el puntero puede guardar la información del GC o el Klass (optimizar Class es una idea mucho mejor que optimizar Integers, por lo que no me importa). Cambiar el puntero requeriría un código shift / cmp / jnz (o algo así) antes de cada carga de puntero. La rama probablemente no será muy bien predicha por el hardware (ya que puede ser tanto el Tipo de valor como el Objeto normal) y conduciría a un impacto en el rendimiento.
Bestsss
3
Hice Smalltalk por algunos años. La optimización aún era bastante costosa, ya que para cada operación en un int tenían que desenmascararlas y volver a aplicarlas. Actualmente, Java está a la par con C al manipular números primitivos. Con desenmascarar + máscara es probable que sea> 30% más lento.
R.Moeller
9

No puedo creer que nadie haya mencionado lo que creo que es la razón más importante: "int" es mucho más fácil de escribir que "Integer". Creo que la gente subestima la importancia de una sintaxis concisa. El rendimiento no es realmente una razón para evitarlos porque la mayoría de las veces cuando uno usa números está en índices de bucle, e incrementar y comparar esos no cuesta nada en ningún bucle no trivial (ya sea que esté usando int o Integer).

La otra razón dada fue que puede obtener NPE, pero eso es extremadamente fácil de evitar con los tipos en caja (y se garantiza que se evitará siempre que los inicialice siempre a valores no nulos).

La otra razón fue que (new Long (1000)) == (new Long (1000)) es falso, pero esa es solo otra forma de decir que ".equals" no tiene soporte sintáctico para los tipos en caja (a diferencia de los operadores <,> , =, etc.), así que volvemos a la razón de "sintaxis más simple".

Creo que el ejemplo de bucle no primitivo de Steve Yegge ilustra muy bien mi punto: http://sites.google.com/site/steveyegge2/language-trickery-and-ejb

Piense en esto: con qué frecuencia utiliza tipos de funciones en lenguajes que tienen una buena sintaxis para ellos (como cualquier lenguaje funcional, python, ruby ​​e incluso C) en comparación con Java, donde debe simularlos usando interfaces como Runnable y Callable y clases sin nombre.

CromTheDestroyer
fuente
8

Un par de razones para no deshacerse de los primitivos:

  • Compatibilidad con versiones anteriores.

Si se elimina, los programas antiguos ni siquiera se ejecutarían.

  • JVM reescribir.

Toda la JVM tendría que reescribirse para admitir esta nueva cosa.

  • Mayor huella de memoria.

Necesitaría almacenar el valor y la referencia, que usa más memoria. Si tiene una gran variedad de bytes, usar byte's es significativamente más pequeño que usar Byte' s.

  • Problemas de puntero nulo.

Declarar int iluego hacer cosas con no igeneraría problemas, pero declarar Integer iy hacer lo mismo generaría un NPE.

  • Cuestiones de igualdad.

Considera este código:

Integer i1 = 5;
Integer i2 = 5;

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

Sería falso Los operadores tendrían que estar sobrecargados, y eso resultaría en una gran reescritura de cosas.

  • Lento

Los envoltorios de objetos son significativamente más lentos que sus homólogos primitivos.

Anubian Noob
fuente
i1 == i2; sería falso solo si i1> = 128. Entonces, el ejemplo actual es incorrecto
Geniy
7

Los objetos son mucho más pesados ​​que los tipos primitivos, por lo que los tipos primitivos son mucho más eficientes que las instancias de clases de contenedor.

Los tipos primitivos son muy simples: por ejemplo, un int tiene 32 bits y ocupa exactamente 32 bits en la memoria, y puede manipularse directamente. Un objeto entero es un objeto completo, que (como cualquier objeto) debe almacenarse en el montón y solo se puede acceder a él a través de una referencia (puntero). Lo más probable es que también ocupe más de 32 bits (4 bytes) de memoria.

Dicho esto, el hecho de que Java tenga una distinción entre tipos primitivos y no primitivos también es un signo de la antigüedad del lenguaje de programación Java. Los lenguajes de programación más nuevos no tienen esta distinción; El compilador de dicho lenguaje es lo suficientemente inteligente como para descubrir por sí mismo si está utilizando valores simples u objetos más complejos.

Por ejemplo, en Scala no hay tipos primitivos; hay una clase Int para enteros, y un Int es un objeto real (que puedes usar métodos, etc.). Cuando el compilador compila su código, usa entradas primitivas detrás de escena, por lo que usar un Int es tan eficiente como usar un int primitivo en Java.

Umesh K
fuente
1
Supuse que el JRE sería lo suficientemente "inteligente" como para hacer esto también con primitivas envueltas en Java. Fallar.
Naftuli Kay
7

Además de lo que otros han dicho, las variables locales primitivas no se asignan desde el montón, sino en la pila. Pero los objetos se asignan desde el montón y, por lo tanto, deben ser recolectados.

Eddie
fuente
3
Lo siento, esto está mal. Una JVM inteligente puede hacer análisis de escape en cualquier asignación de objetos y, si no puede escapar, asignarlos en la pila.
rlibby
2
Sí, esto está comenzando a ser una característica de las JVM modernas. En cinco años, lo que diga será cierto para la mayoría de las JVM que estén en uso. Hoy no lo es. Casi comenté sobre esto, pero decidí no comentarlo. Quizás debería haber dicho algo.
Eddie
6

Los tipos primitivos tienen muchas ventajas:

  • Código más simple para escribir
  • El rendimiento es mejor ya que no está creando instancias de un objeto para la variable
  • Como no representan una referencia a un objeto, no es necesario verificar si hay valores nulos
  • Utilice tipos primitivos a menos que necesite aprovechar las funciones de boxeo.
Adriana
fuente
5

Es difícil saber qué tipo de optimizaciones están sucediendo debajo de las cubiertas.

Para uso local, cuando el compilador tiene suficiente información para realizar optimizaciones excluyendo la posibilidad del valor nulo, espero que el rendimiento sea el mismo o similar .

Sin embargo, las matrices de primitivas son aparentemente muy diferentes de las colecciones de primitivas en caja. Esto tiene sentido dado que muy pocas optimizaciones son posibles dentro de una colección.

Además, Integertiene una sobrecarga lógica mucho mayor en comparación con int: ahora debe preocuparse por si int a = b + c;arroja una excepción o no .

Usaría las primitivas tanto como fuera posible y confiaría en los métodos de fábrica y el autoboxing para darme los tipos en caja semánticamente más potentes cuando sean necesarios.

Matthew Willis
fuente
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));

Milisegundos llevados al bucle '100000000' veces alrededor Largo: 468

Milisegundos llevados al ciclo '100000000' veces alrededor de largo: 31

En una nota al margen, no me importaría ver algo como esto encontrar su camino en Java.

Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
   ...
}

Donde el bucle for incrementa automáticamente el loop1 de 0 a 1000 o

Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
   ...
}

Donde el bucle for disminuye automáticamente loop1 1000 a 0.

Keith
fuente
2
  1. Necesitas primitivas para hacer operaciones matemáticas.
  2. Primitivos toma menos memoria como se respondió anteriormente y un mejor rendimiento

Debe preguntar por qué se requiere el tipo de clase / objeto

La razón para tener el tipo de objeto es hacernos la vida más fácil cuando tratamos con colecciones. Las primitivas no se pueden agregar directamente a Lista / Mapa, sino que debe escribir una clase de contenedor. El tipo de clases Readymade Integer lo ayuda aquí, además de que tiene muchos métodos de utilidad como Integer.pareseInt (str)

Prabakaran
fuente
2

Estoy de acuerdo con las respuestas anteriores, el uso de objetos envoltorios primitivos puede ser costoso. Pero, si el rendimiento no es crítico en su aplicación, evite desbordamientos al usar objetos. Por ejemplo:

long bigNumber = Integer.MAX_VALUE + 2;

El valor de bigNumberes -2147483647, y es de esperar que sea 2147483649. Es un error en el código que se solucionaría haciendo:

long bigNumber = Integer.MAX_VALUE + 2l; // note that '2' is a long now (it is '2L').

Y bigNumbersería 2147483649. Este tipo de errores a veces son fáciles de pasar por alto y pueden conducir a comportamientos desconocidos o vulnerabilidades (ver CWE-190 ).

Si usa objetos de contenedor, el código equivalente no se compilará.

Long bigNumber = Integer.MAX_VALUE + 2; // Not compiling

Por lo tanto, es más fácil detener este tipo de problemas mediante el uso de objetos envoltorios primitivos.

Su pregunta ya está tan respondida, que respondo solo para agregar un poco más de información no mencionada anteriormente.

Fernando García
fuente
1

Porque JAVA realiza todas las operaciones matemáticas en tipos primitivos. Considere este ejemplo:

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

Aquí, las operaciones de recordatorio y unario plus no se pueden aplicar en el tipo Entero (Referencia), el compilador realiza el desempaquetado y realiza las operaciones.

Por lo tanto, asegúrese de cuántas operaciones de autoboxing y unboxing ocurren en el programa java. Desde entonces, lleva tiempo realizar estas operaciones.

En general, es mejor mantener argumentos de tipo Referencia y resultado de tipo primitivo.

usuario3462649
fuente
1

Los tipos primitivos son mucho más rápidos y requieren mucha menos memoria . Por lo tanto, podríamos preferir usarlos.

Por otro lado, la especificación actual del lenguaje Java no permite el uso de tipos primitivos en los tipos parametrizados (genéricos), en las colecciones de Java o la API Reflection.

Cuando nuestra aplicación necesita colecciones con una gran cantidad de elementos, deberíamos considerar el uso de matrices con el tipo más "económico" posible.

* Para obtener información detallada, consulte la fuente: https://www.baeldung.com/java-primitives-vs-objects

Arun Joshla
fuente
0

Para ser breve: los tipos primitivos son más rápidos y requieren menos memoria que los en caja

Enlace blanco
fuente