2011-07-05 19 views
44

Sé que cuando lea la respuesta a esto veré que he pasado por alto algo que estaba bajo mis ojos. Pero he pasado los últimos 30 minutos tratando de resolverlo yo mismo sin resultado.Conversión de null a int possible?

Entonces, estaba escribiendo un programa en Java 6 y descubrí algo (para mí) característica extraña. Para tratar de aislarlo, he hecho dos pequeños ejemplos. primera vez que trató el siguiente método:

private static int foo() 
{ 
    return null; 
} 

y el compilador lo rechazaron: tipo incorrecto: no se puede convertir de nula a int.

Esto está bien conmigo y respeta la semántica de Java con la que estoy familiarizado. Luego he intentado lo siguiente:

private static Integer foo(int x) 
{ 
    if (x < 0) 
    { 
     return null; 
    } 
    else 
    { 
     return new Integer(x); 
    } 
} 

private static int bar(int x) 
{ 
    Integer y = foo(x); 

    return y == null ? null : y.intValue(); 
} 

private static void runTest() 
{ 
    for (int index = 2; index > -2; index--) 
    { 
     System.out.println("bar(" + index + ") = " + bar(index)); 
    } 
} 

Este compila sin errores! Pero, en mi opinión, debería haber un error de conversión de tipo en la línea

return y == null ? null : y.intValue(); 

Si ejecuto el programa me da el siguiente resultado:

bar(2) = 2 
bar(1) = 1 
bar(0) = 0 
Exception in thread "main" java.lang.NullPointerException 
    at Test.bar(Test.java:23) 
    at Test.runTest(Test.java:30) 
    at Test.main(Test.java:36) 

Puede explicar este comportamiento?

actualización

Muchas gracias por las muchas respuestas que aclaran. Estaba un poco preocupado porque este ejemplo no correspondía a mi intuición. Una cosa que me molestó fue que un nulo se estaba convirtiendo en un int y me preguntaba cuál sería el resultado : 0 como en C++? Eso hubiera sido muy extraño. Está bien que la conversión no sea posible en el tiempo de ejecución (excepción del puntero nulo).

Respuesta

54

Vamos a la línea:

return y == null ? null : y.intValue(); 

En un comunicado ? :, ambos lados de la : debe tener el mismo tipo. En este caso, Java hará que tenga el tipo Integer. Un Integer puede ser null, por lo que el lado izquierdo está bien. La expresión y.intValue() es del tipo int, pero Java lo va a auto-boxear en Integer (nota, también podría haber escrito y que le habría guardado este autobox).

Ahora, el resultado debe ser desempaquetado nuevamente a int, porque el tipo de devolución del método es int.Si desempaqueta un Integer que es null, obtiene un NullPointerException.

Nota: Paragraph 15.25 de la Especificación del lenguaje Java explica las reglas exactas para las conversiones de tipo con respecto al operador condicional ? :.

+0

¿Cuál es la regla general de cómo el compilador intenta resolver tipos en una expresión ternaria? –

+2

+1, esto tiene sentido ahora, para mí. ¿Por qué el compilador no permite la primera prueba, 'int foo() {return null; } ', como en este caso, ¿debería autobox' null' a 'Integer'? o, supongo que mi pregunta en otras palabras es, ¿por qué elige autobox los campos en la operación ternaria a 'Integer' y no los deja como primitivos? ¿Dónde se basa esa decisión? – c00kiemon5ter

+0

@Oli No estoy seguro, pero puede tener un comportamiento sorprendente, de lo cual este es un ejemplo. Ver [párrafo 15.25] (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25) del JLS. – Jesper

1

El problema con el autoenvasado de valores null puede ser realmente molesto. En su ejemplo, es una combinación del tipo de resultado de operador ternario inferir y autoencapar (se debe consultar el JLS por qué se comporta así)

Pero, en general, debe intentar evitar el uso de tipos de envoltura. Use int en lugar de Integer. Si necesita un valor especial que signifique "sin resultado", entonces puede usar Integer.MAX_VALUE por ejemplo. mirada

+0

Es por eso que tengo un tipo de retorno int. El nulo viene de copiar y pegar, y cuando vi que el compilador no se quejaba, quedé bastante sorprendido. – Giorgio

6

Aquí se deduce el tipo del tipo de retorno. Esa es la cuestión ..

http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25

Aquí es el problema real -

Si uno de los segundo y tercer operandos es de tipo nulo y el tipo del otro es un tipo de referencia , entonces el tipo de expresión condicional es ese tipo de referencia.

Así que, básicamente, el compilador infiere que el tipo de devolución de la expresión condicional es Integer y por eso le permite compilar correctamente.

EDIT: Ver las reglas en los comentarios

+1

@Kai: Ahora empiezo a entender: ¿el compilador determina el tipo de resultado del? operador primero y encuentra Entero. Solo entonces, intenta hacer coincidir Integer con el tipo de retorno int. Son compatibles siempre que se realice un desempaquetado. Nunca se ve que null no se puede unboxed a int. En el primer ejemplo, solo hay un paso de conversión, por eso el compilador dice que los tipos son incompatibles. – Giorgio

+0

@Giorgio - sí. Si miras el enlace, hay más aclaraciones --- Encapsula ambos operandos y luego aplica la misma lógica (en este caso, int se coloca en cuadros en Integer). El tipo de retorno se supone Entero. ** De lo contrario, el segundo y tercer operandos son de los tipos S1 y S2, respectivamente. Deje que T1 sea del tipo que resulta de aplicar la conversión de boxeo a S1, y deje que T2 sea del tipo que resulta de aplicar la conversión de boxeo a S2. El tipo de expresión condicional es el resultado de aplicar la conversión de captura (§5.1.10) a lub (T1, T2) (§15.12.2.7). – Kal

+0

"* y el tipo del otro es un tipo de referencia *" <--- an int NO es una referencia, por lo que no cumple este párrafo. – edutesoy

3

Esto ilustra la diferencia problemática entre la forma de un ser humano lee el código y un compilador lee el código.

Cuando que ve una expresión ternaria, es muy posible que usted pueda dividir mentalmente en dos partes, en el estilo de una declaración if/else:

if (y == null) 
    return null; 
else 
    return y.intValue(); 

Usted puede ver que esta no es válido, ya que da como resultado una posible bifurcación en la que un método definido para devolver un int devuelve realmente null (¡ilegal!).

Lo que el compilador ve es una expresión , que debe contener un tipo. Observa que la operación ternaria incluye un null en un lado y un int en el otro; debido al comportamiento de autoboxing de Java, aparece una "mejor conjetura" (mi término, no Java) sobre cuál es el tipo de expresión: Integer (esto es justo: es el único tipo que legalmente podría ser null o int en caja))

Dado que se supone que el método devuelve un int, está bien desde la perspectiva del compilador: la expresión que se devuelve se evalúa como Integer, que se puede unboxed automáticamente.

1

esta compila

private static int foo() 
{ 
    return (Integer)null; 
} 
+2

Claro que se compila, pero obtendrás un Error de tiempo de ejecución (NullPointer) porque no se puede llamar a Integer.intValue(). – Orri

3

Sólo en caso de que usted no tiene la guayaba en su proyecto, pero que ya utilizan Apache Commons, puede utilizar Apache Lang3 con su clase ObjectUtils.

El uso es básicamente la misma que la guayaba:

Integer number = null; 
int notNull = ObjectUtils.firstNonNull(number, 0); 

Tenga en cuenta, que este método en la biblioteca de guayaba funciona más rápido, que en Apache. He aquí una breve comparación que acabo de hacer en mi portátil (Core i7-7500U 2,7 GHz), Oracle Java 8, múltiples ejecuciones, JVM precalentado, los resultados se promedian:

╔══════════════╦══════╦══════╦════════╦══════╗ 
║ Library/Runs ║ 1000 ║ 1mln ║ 100mln ║ 1bln ║ 
╠══════════════╬══════╬══════╬════════╬══════╣ 
║ Apache  ║ 1 ║ 30 ║ 782 ║ 9981 ║ 
║ Guava  ║ 1 ║ 22 ║ 120 ║ 828 ║ 
╚══════════════╩══════╩══════╩════════╩══════╝ 

Los resultados están en milisegundos. No creo que a menudo necesite ejecutar este método por miles de millones de veces, pero aun así, siempre es bueno tener una comparación de rendimiento