2009-10-03 62 views
149

Yo sé que si se compara un número entero primitiva en caja con una constante tales como:¿Cómo comparar correctamente dos enteros en Java?

Integer a = 4; 
if (a < 5) 

a será automáticamente sacó de la caja y la comparación va a funcionar.

Sin embargo, ¿qué sucede cuando se comparan dos cajas Integers y desea comparar la igualdad o menos de/mayor que?

Integer a = 4; 
Integer b = 5; 

if (a == b) 

¿El resultado del código anterior será verificado para ver si son el mismo objeto, o será unbox automático en ese caso?

¿Qué hay de:

Integer a = 4; 
Integer b = 5; 

if (a < b) 

?

+13

Bueno, lo que pasó cuando se trató? ¿Qué observaste? –

+17

@Bart Kiers: un experimento explícito solo podría desmentir, no demostrar que se produce unboxing. Si se usa '==' en lugar de 'igual', se obtiene el resultado correcto, puede deberse a que los números encapsulados están siendo internados o reutilizados (como una optimización del compilador, presumiblemente). La razón para hacer esta pregunta es descubrir qué está sucediendo internamente, no lo que parece estar sucediendo. (Al menos, es por eso que estoy aquí.) –

Respuesta

30

== seguirá probando la igualdad de objetos. Es fácil ser engañado, sin embargo:

Integer a = 10; 
Integer b = 10; 

System.out.println(a == b); //prints true 

Integer c = new Integer(10); 
Integer d = new Integer(10); 

System.out.println(c == d); //prints false 

Sus ejemplos con las desigualdades van a trabajar, ya que no se definen en los objetos. Sin embargo, con la comparación ==, aún se verificará la igualdad de objetos. En este caso, cuando inicializa los objetos de una primitiva en caja, se usa el mismo objeto (para a y b). Esta es una buena optimización ya que las clases primitivas de caja son inmutables.

+0

Me imaginé que era la igualdad de los objetos probados. Tuve algunos resultados extraños. ¿Debo reemplazarlo con .equals()? Además, ¿cree que debería dejar las desigualdades tal como están o hacerlo de otra manera también? –

+0

Hay algunos casos extremos no obvios con autoboxing. Tengo mi IDE (Eclipse) configurado para colorear cualquier cosa que no esté en rojo, esto me ha salvado de errores en algunas ocasiones. Si está comparando dos enteros, use .equals, si desea aclarar sus desigualdades, escriba el reparto explícitamente: if ((int) c <(int) d) ...; También puede hacer: c.compareTo (d) <0 // === c

+11

Y si cambia el número de literales a '200', ambas pruebas imprimirán' false'. –

210

No, == entre Entero, Largo, etc comprobará igualdad referencia - es decir,

Integer x = ...; 
Integer y = ...; 

System.out.println(x == y); 

esta comprobará si x y y se refieren al mismo objeto en lugar de iguales objetos.

Así

Integer x = new Integer(10); 
Integer y = new Integer(10); 

System.out.println(x == y); 

está garantizado para imprimir false. Internar de "pequeños" valores autoboxed puede conducir a resultados engañosos:

Integer x = 10; 
Integer y = 10; 

System.out.println(x == y); 

Esto imprimirá true, debido a las reglas del boxeo (JLS section 5.1.7). Todavía se usa la igualdad de referencia, pero las referencias genuinamente son iguales.

Personalmente me gustaría usar:

if (x.intValue() == y.intValue()) 

o

if (x.equals(y)) 

Este último es un poco menos eficiente - no hay una sobrecarga para Integer.equals(Integer) por lo que tendrá que hacer la comprobación de tipos en tiempo de ejecución , mientras que el primero usa el hecho de que ya sabemos que ambos objetos son Integer s.

Afortunadamente, compareTo sabe acerca de los tipos, por lo que:

if (x.compareTo(y) < 0) 

todavía debe ser eficiente. Por supuesto, este es un territorio micro-optimización y se debe utilizar el código que encuentre más claro - después de asegurarse de que es :) correcta

Como usted dice, para cualquier comparación entre un tipo de envoltura (Integer, Long etc) y un valor numérico tipo (int, long etc.) el valor del tipo de envoltura es unboxed y la prueba se aplica a los valores primitivos involucrados.

Esto ocurre como parte de la promoción numérica binaria (JLS section 5.6.2). Mire la documentación de cada operador individual para ver si se aplica. ! Por ejemplo, a partir de la documentación para == y = (JLS 15.21.1):

Si los operandos de un operador de igualdad son tanto de tipo numérico, o uno es de tipo numérico y el otro es convertible (§5.1.8) al tipo numérico , la promoción numérica binaria es realizada en los operandos (§5.6.2).

y para <, < =,> y> = (JLS 15.20.1)

El tipo de cada uno de los operandos de un operador de comparación numérica debe ser un tipo que es convertible (§5.1 .8) a se produce un tipo numérico primitivo o un error de tiempo de compilación . La promoción numérica binaria se realiza en los operandos (§5.6.2). Si el tipo promocionado de los operandos es int o largo, , entonces la comparación de entero con signo es realizada; si este tipo promocionado es flotante o doble, entonces se realiza la comparación de punto flotante .

Nota cómo nada de esto se considera como parte de la situación en la que ni tipo es un tipo numérico.

+0

¿Hay alguna razón por la que uno quiera escribir 'x.compareTo (y) <0' en lugar de' x

+0

@MaxNanasy: No es que yo pueda pensar de inmediato. –

+1

A partir de Java 1.6.27+ existe una sobrecarga en equals en la clase Integer, por lo que debe ser tan eficiente como llamar a .intValue(). Compara los valores como primitivo int. – otterslide

4

tl; dr mi opinión es usar un unario + para disparar el unboxing en uno de los operandos en la comprobación de la igualdad de valor, y simplemente utilizar los operadores matemáticos lo contrario. Rationale follows:

Ya se ha mencionado que la comparación == para Integer es una comparación de identidad, que generalmente no es lo que un programador quiere, y que el objetivo es hacer una comparación de valores; Aún así, he hecho un poco de science sobre cómo hacer esa comparación más eficientemente, tanto en términos de compacidad, corrección y velocidad del código.

He utilizado el grupo habitual de métodos:

public boolean method1() { 
    Integer i1 = 7, i2 = 5; 
    return i1.equals(i2); 
} 

public boolean method2() { 
    Integer i1 = 7, i2 = 5; 
    return i1.intValue() == i2.intValue(); 
} 

public boolean method3() { 
    Integer i1 = 7, i2 = 5; 
    return i1.intValue() == i2; 
} 

public boolean method4() { 
    Integer i1 = 7, i2 = 5; 
    return i1 == +i2; 
} 

public boolean method5() { // obviously not what we want.. 
    Integer i1 = 7, i2 = 5; 
    return i1 == i2; 
} 

y tiene el código después de la compilación y descompilación:

public boolean method1() { 
    Integer var1 = Integer.valueOf(7); 
    Integer var2 = Integer.valueOf(5); 

    return var1.equals(var2); 
} 

public boolean method2() { 
    Integer var1 = Integer.valueOf(7); 
    Integer var2 = Integer.valueOf(5); 

    if (var2.intValue() == var1.intValue()) { 
     return true; 
    } else { 
     return false; 
    } 
} 

public boolean method3() { 
    Integer var1 = Integer.valueOf(7); 
    Integer var2 = Integer.valueOf(5); 

    if (var2.intValue() == var1.intValue()) { 
     return true; 
    } else { 
     return false; 
    } 
} 

public boolean method4() { 
    Integer var1 = Integer.valueOf(7); 
    Integer var2 = Integer.valueOf(5); 

    if (var2.intValue() == var1.intValue()) { 
     return true; 
    } else { 
     return false; 
    } 
} 

public boolean method5() { 
    Integer var1 = Integer.valueOf(7); 
    Integer var2 = Integer.valueOf(5); 

    if (var2 == var1) { 
     return true; 
    } else { 
     return false; 
    } 
} 

Como se puede ver fácilmente, el método 1 llamadas Integer.equals() (obviamente), métodos 2-4 da como resultado exactamente el mismo código, desenvuelve los valores por medio de .intValue() y luego los compara directamente, y el método 5 simplemente desencadena una comparación de identidad, siendo la forma incorrecta de comparar valores.

Puesto que (como ya se ha mencionado por ejemplo, JS) equals() incurre en una sobrecarga (que tiene que ver instanceof y un reparto sin marcar), los métodos de 2-4 trabajarán con exactamente la misma velocidad, noticingly mejor que el método 1 cuando se utiliza en apretado bucles, ya que HotSpot no es probable que optimice los moldes & instanceof.

es bastante similar con otros operadores de comparación (por ejemplo </>) - van a desencadenar unboxing, durante el uso de compareTo() no - pero esta vez, la operación es altamente optimizable por HS desde intValue() es sólo un método getter (primer candidato a ser optimizado).

En mi opinión, la versión rara vez se utilizan 4 es la forma más concisa - cada C sazonada/desarrollador de Java sabe que más unario es en la mayoría de los casos igual a emitir a int/.intValue() - si bien puede ser un poco WTF momento para algunos (principalmente aquellos que no usaron el plus único durante su vida útil), se puede decir que muestra el intento más claro y más lacónico - muestra que queremos un valor int de uno de los operandos, forzando el otro valor a unbox como bien. También es indiscutiblemente más similar a la comparación i1 == i2 normal utilizada para los valores primitivos int.

Mi voto va para i1 == +i2 estilo & i1 > i2 para Integer objetos, tanto por razones de rendimiento & consistencia. También hace que el código sea portátil para las primitivas sin cambiar nada más que la declaración de tipo. El uso de métodos con nombre me parece una introducción de ruido semántico, similar al muy criticado bigInt.add(10).multiply(-3) estilo.

8

== cheques para la igualdad de referencia, sin embargo, al escribir un código como:

Integer a = 1; 
Integer b = 1; 

Java es lo suficientemente inteligente como para reutilizar el mismo inmutable para a y b, así que esto es cierto: a == b. Curioso, escribí un pequeño ejemplo para mostrar dónde java detiene optimizando de esta manera:

public class BoxingLol { 
    public static void main(String[] args) { 
     for (int i = 0; i < Integer.MAX_VALUE; i++) { 
      Integer a = i; 
      Integer b = i; 
      if (a != b) { 
       System.out.println("Done: " + i); 
       System.exit(0); 
      } 
     } 
     System.out.println("Done, all values equal"); 
    } 
} 

Cuando compilar y ejecutar este (en mi máquina), me sale:

Done: 128 
+1

tl; dr -1 para ondulado manual; http://stackoverflow.com/questions/15052216/how-large-is-the-integer-cache http: // stackoverflow.com/questions/20897020/why-integer-class-caching-values-in-the-range-128-to-127 http://stackoverflow.com/questions/3131136/integers-caching-in-java, etc. explican detallar el asunto que mencionaste; es mejor leer los documentos (o la fuente de la fuente) que crear pseudo-pruebas con el riesgo de una alta localidad de los resultados; no solo se ha olvidado por completo del límite inferior de la memoria caché (es decir, -128 por defecto), no solo tiene uno por uno (el máximo es 127, no 128), – vaxquis

+0

, pero * no tiene ninguna garantía * de recibir el mismo resultado en cualquier máquina, ya que puede * aumentar * fácilmente el tamaño de la memoria caché usted mismo, YMMV. Además, la pregunta de OP era * cómo comparar correctamente dos enteros *: * usted no lo ha respondido en absoluto *. – vaxquis

+0

Respeto su opinión y percepción aquí. Creo que solo tenemos enfoques fundamentalmente diferentes para CS. –

3

Calling

if (a == b) 

Funciona la mayor parte del tiempo, pero no se garantiza que siempre funcione, por lo que no lo use.

La forma más adecuada para comparar dos clases enteras para la igualdad, suponiendo que se denominan 'a' y 'b' es llamar:

if(a != null && a.equals(b)) { 
    System.out.println("They are equal"); 
} 

También puede utilizar de esta manera que es un poco más rápido.

if(a != null && b != null && (a.intValue() == b.intValue())) { 
     System.out.println("They are equal"); 
    } 

En mi máquina, 99 mil millones de operaciones tomaron 47 segundos con el primer método y 46 segundos con el segundo. Debería estar comparando miles de millones de valores para ver cualquier diferencia.

Tenga en cuenta que 'a' puede ser nulo ya que es un Objeto. Comparando de esta manera no causará una excepción de puntero nulo.

Para la comparación de mayor y menor que, utilice

if (a != null && b!=null) { 
    int compareValue = a.compareTo(b); 
    if (compareValue > 0) { 
     System.out.println("a is greater than b"); 
    } else if (compareValue < 0) { 
     System.out.println("b is greater than a"); 
    } else { 
      System.out.println("a and b are equal"); 
    } 
} else { 
    System.out.println("a or b is null, cannot compare"); 
} 
+0

'if (a == b)' funciona solo para valores pequeños y no funcionará la mayoría de las veces. – Tony

+0

Funciona hasta 127 ya que es el caché de enteros predeterminado de Java, lo que asegura que todos los números hasta 127 tengan el mismo valor de referencia. Puede configurar el caché para ir más allá de 127 si lo desea, pero simplemente no use == para estar seguro. – otterslide

-1

este método compara dos enteros con cheque nulo, ver pruebas

public static boolean compare(Integer int1, Integer int2) { 
    if(int1!=null) { 
     return int1.equals(int2); 
    } else { 
     return int2==null; 
    } 
    //inline version: 
    //return (int1!=null) ? int1.equals(int2) : int2==null; 
} 

//results: 
System.out.println(compare(1,1));   //true 
System.out.println(compare(0,1));   //false 
System.out.println(compare(1,0));   //false 
System.out.println(compare(null,0));  //false 
System.out.println(compare(0,null));  //false 
System.out.println(compare(null,null));  //true 
+0

agregue alguna explicación a su respuesta. – RamPrakash

+2

Para esto, creo que sería mejor usar el método 'Objects.equals (x, y)' en lugar de hacer el suyo propio. – ryvantage