Devolver nulo como int permitido con operador ternario pero no si declaración

186

Veamos el código Java simple en el siguiente fragmento:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

En este código Java más simple, el temp()método no emite ningún error del compilador a pesar de que el tipo de retorno de la función es int, y estamos tratando de devolver el valor null(a través de la declaración return true ? null : 0;). Cuando se compila, esto obviamente causa la excepción de tiempo de ejecución NullPointerException.

Sin embargo, parece que lo mismo está mal si representamos el operador ternario con una ifdeclaración (como en el same()método), que hace emitir un error en tiempo de compilación! ¿Por qué?

León
fuente
66
Además, int foo = (true ? null : 0)y new Integer(null)ambos compilan bien, el segundo es la forma explícita de autoboxing.
Izkata
2
@Izkata El problema aquí es para mí entender por qué el compilador está tratando de Autobox nulla Integer... Eso mismo aspecto que "adivinar" a mí o "hacer las cosas de trabajo" ...
Marsellus Wallace
1
... Huhm, pensé que tenía una respuesta allí, ya que el constructor Integer (lo que los documentos que encontré dicen que se usa para autoboxing) puede tomar un String como argumento (que puede ser nulo). Sin embargo, también dicen que el constructor actúa de forma idéntica al método parseInt (), que arrojaría una NumberFormatException al pasar un nulo ...
Izkata
3
@Izkata: el argumento del argumento de cadena para Integer no es una operación de autoboxing. Una cadena no se puede convertir automáticamente en un entero. (La función Integer foo() { return "1"; }no se compilará).
Ted Hopp
55
¡Genial, aprendí algo nuevo sobre el operador ternario!
oksayt

Respuestas:

118

El compilador interpreta nullcomo una referencia nula a un Integer, aplica las reglas de autoboxing / unboxing para el operador condicional (como se describe en la Especificación del lenguaje Java, 15.25 ), y continúa felizmente. Esto generará un NullPointerExceptiontiempo de ejecución, que puede confirmar probándolo.

Ted Hopp
fuente
Dado el enlace a la Especificación del lenguaje Java que publicó, ¿qué punto cree que se ejecuta en el caso de la pregunta anterior? El último (ya que todavía estoy tratando de entender capture conversiony lub(T1,T2)) ?? Además, ¿es realmente posible aplicar el boxeo a un valor nulo? ¿No sería esto como "adivinar"?
Marsellus Wallace
´ @ Gevorg Un puntero nulo es un puntero válido para cada objeto posible, por lo que no puede pasar nada malo allí. El compilador simplemente asume que null es un número entero que luego puede autobox a int.
Voo
1
@Gevorg: vea el comentario de nowaq y mi respuesta a su publicación. Creo que eligió la cláusula correcta. lub(T1,T2)es el tipo de referencia más específico en común en la jerarquía de tipos de T1 y T2. (Ambos comparten al menos un Objeto, por lo que siempre hay un tipo de referencia más específico).
Ted Hopp
8
@Gevorg - nullno está encajonado en un entero, se interpreta como una referencia a un entero (una referencia nula, pero eso no es un problema). No se construye ningún objeto entero a partir del nulo, por lo que no hay razón para una NumberFormatException.
Ted Hopp
1
@Gevorg: si observa las reglas para la conversión de boxeo y las aplica null(que no es un tipo numérico primitivo), la cláusula aplicable es "Si p es un valor de cualquier otro tipo, la conversión de boxeo es equivalente a una conversión de identidad ". Entonces, la conversión del boxeo nulla Integerrendimientos null, sin invocar a ningún Integerconstructor.
Ted Hopp
40

Creo que el compilador de Java interpreta true ? null : 0como una Integerexpresión, que se puede convertir implícitamente int, posiblemente dando NullPointerException.

Para el segundo caso, la expresión nulles del tipo nulo especial ver , por lo que el código return nullhace que el tipo no coincida.

Vlad
fuente
2
Supongo que esto está relacionado con el auto-boxeo? Presumiblemente, el primer retorno no se compilaría antes de Java 5, ¿verdad?
Michael McGowan
@Michael que parece ser el caso si configura el nivel de cumplimiento de Eclipse en pre-5.
Jonathon Faust
@ Michael: esto definitivamente parece un auto-boxeo (soy bastante nuevo en Java y no puedo hacer una declaración más definida, lo siento).
Vlad
1
@Vlad cómo sería el final del compilador hasta interpretar true ? null : 0como Integer? Por autoboxing 0primero ??
Marsellus Wallace
1
@Gevorg: Mire aquí : de lo contrario, el segundo y tercer operandos son de los tipos S1 y S2 respectivamente. Deje que T1 sea el tipo que resulta de aplicar la conversión de boxeo a S1, y que T2 sea el tipo que resulta de aplicar la conversión de boxeo a S2. y el siguiente texto.
Vlad
32

En realidad, todo se explica en la Especificación del lenguaje Java .

El tipo de una expresión condicional se determina de la siguiente manera:

  • Si el segundo y tercer operandos tienen el mismo tipo (que puede ser el tipo nulo), entonces ese es el tipo de la expresión condicional.

Por lo tanto, el "nulo" en su (true ? null : 0)obtiene un tipo int y luego se autoboxing a Integer.

Pruebe algo como esto para verificar esto (true ? null : null)y obtendrá el error del compilador.

nowaq
fuente
3
Pero esa cláusula de las reglas no se aplica: el segundo y tercer operandos no tienen el mismo tipo.
Ted Hopp
1
Entonces la respuesta parece estar en la siguiente declaración:> De lo contrario, el segundo y tercer operandos son de los tipos S1 y S2 respectivamente. Deje que T1 sea el tipo que resulta de aplicar la conversión de boxeo a S1, y que T2 sea el 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).
nowaq
Creo que esa es la cláusula aplicable. Luego trata de aplicar el desempaquetado automático para devolver un intvalor de la función, lo que provoca un NPE.
Ted Hopp
@nowaq Yo también pensé esto. Sin embargo, si se intenta a la caja de forma explícita nulla Integerla new Integer(null);"Let T1 sea del tipo que resulta de aplicar la conversión de boxeo a S1 ..." que se obtendría una NumberFormatExceptiony esto no es el caso ...
Marsellus Wallace
@Gevorg Creo que, dado que ocurre una excepción al hacer el boxeo, no obtenemos NINGÚN resultado aquí. El compilador solo está obligado a generar código que siga la definición que hace, solo obtenemos la excepción antes de terminar.
Voo
25

En el caso de la ifdeclaración, la nullreferencia no se trata como una Integerreferencia porque no participa en una expresión que obliga a interpretarla como tal. Por lo tanto, el error puede detectarse fácilmente en tiempo de compilación porque es más claramente un error de tipo .

En cuanto al operador condicional, la Especificación del lenguaje Java §15.25 "Operador condicional ? :" responde esto muy bien en las reglas sobre cómo se aplica la conversión de tipo:

  • Si el segundo y tercer operandos tienen el mismo tipo (que puede ser el tipo nulo), entonces ese es el tipo de la expresión condicional.

    No aplica porque nullno lo es int.

  • Si uno de los operandos segundo y tercero es de tipo booleano y el tipo del otro es de tipo booleano, entonces el tipo de la expresión condicional es booleano.

    No aplica porque ni nulltampoco intes booleano Boolean.

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

    No se aplica porque nulles del tipo nulo, pero intno es un tipo de referencia.

  • De lo contrario, si los operandos segundo y tercero tienen tipos que son convertibles (§5.1.8) a tipos numéricos, entonces hay varios casos: […]

    Aplica: nullse trata como convertible a un tipo numérico y se define en §5.1. 8 "Conversión de Unboxing" para lanzar a NullPointerException.
Jon Purdy
fuente
Si 0está en caja automática Integer, el compilador está ejecutando el último caso de las "reglas de operador ternario" como se describe en la Especificación del lenguaje Java. Si eso es cierto, es difícil para mí creer que saltaría al caso 3 de las mismas reglas que tienen un tipo nulo y un tipo de referencia que hace que el valor de retorno del operador ternario sea el tipo de referencia (Entero). .
Marsellus Wallace
@Gevorg - ¿Por qué es difícil creer que el operador ternario está devolviendo un Integer? Eso es exactamente lo que está sucediendo; el NPE se genera al tratar de desempaquetar el valor de la expresión para devolver un valor intde la función. Cambie la función para devolver un Integery volverá nullsin problema.
Ted Hopp
2
@TedHopp: Gevorg estaba respondiendo a una revisión anterior de mi respuesta, que era incorrecta. Debes ignorar la discrepancia.
Jon Purdy
@JonPurdy "Se dice que un tipo es convertible a un tipo numérico si es un tipo numérico, o es un tipo de referencia que se puede convertir a un tipo numérico al desempaquetar la conversión" y no creo que nullse encuentre en esta categoría . Además, pasaríamos al paso "De lo contrario, se aplica la promoción numérica binaria (§5.6.2) ... Tenga en cuenta que la promoción numérica binaria realiza la conversión de desempaquetado (§5.1.8) ..." para determinar el tipo de retorno. Pero la conversión de unboxing generaría un NPE y esto ocurre solo en tiempo de ejecución y no mientras se intenta determinar el tipo de operador ternario. Todavía estoy confundido ..
Marsellus Wallace
@Gevorg: Unboxing ocurre en tiempo de ejecución. El nullse trata como si tuviera tipo int, pero en realidad es equivalente a throw new NullPointerException(), eso es todo.
Jon Purdy
11

Lo primero a tener en cuenta es que los operadores ternarios de Java tienen un "tipo", y que esto es lo que el compilador determinará y considerará sin importar cuáles sean los tipos reales / reales del segundo o tercer parámetro. Dependiendo de varios factores, el tipo de operador ternario se determina de diferentes maneras, como se ilustra en la Especificación del lenguaje Java 15.26

En la pregunta anterior deberíamos considerar el último caso:

De lo contrario, el segundo y tercer operandos son de los tipos S1 y S2 respectivamente. Deje que T1 sea ​​el tipo que resulta de aplicar la conversión de boxeo a S1 , y que T2 sea ​​el 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).

Este es, con mucho, el caso más complejo una vez que echa un vistazo a la aplicación de la conversión de captura (§5.1.10) y, sobre todo, en lub (T1, T2) .

En inglés simple y después de una simplificación extrema, podemos describir el proceso como el cálculo de la "Superclase menos común" (sí, piense en el MCM) del segundo y tercer parámetro. Esto nos dará el operador "tipo" ternario. Nuevamente, lo que acabo de decir es una simplificación extrema (considere las clases que implementan múltiples interfaces comunes).

Por ejemplo, si intenta lo siguiente:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

Notarás que el tipo resultante de la expresión condicional se debe a java.util.Dateque es la "Superclase menos común" para el Timestamp/ Timepar.

Dado que nullse puede autoboxear a cualquier cosa, la "Superclase menos común" es la Integerclase y este será el tipo de retorno de la expresión condicional (operador ternario) anterior. El valor de retorno será entonces un puntero nulo de tipo Integery eso es lo que devolverá el operador ternario.

En tiempo de ejecución, cuando la máquina virtual de Java unboxes la Integeruna NullPointerExceptionse lanza. Esto sucede porque la JVM intenta invocar la función null.intValue(), donde nulles el resultado del autoboxing.

En mi opinión (y dado que mi opinión no está en la Especificación del lenguaje Java, muchas personas lo encontrarán mal de todos modos) el compilador hace un mal trabajo al evaluar la expresión en su pregunta. Dado que usted escribió, true ? param1 : param2el compilador debe determinar de inmediato que nullse devolverá el primer parámetro - y generará un error de compilación. Esto es algo similar a cuando escribe while(true){} etc...y el compilador se queja del código debajo del bucle y lo marca Unreachable Statements.

Su segundo caso es bastante sencillo y esta respuesta ya es demasiado larga ...;)

CORRECCIÓN:

Después de otro análisis, creo que me equivoqué al decir que un nullvalor se puede encuadrar / encuadrar automáticamente en cualquier cosa. Hablando de la clase Integer, el boxeo explícito consiste en invocar al new Integer(...)constructor o tal vez el Integer.valueOf(int i);(encontré esta versión en alguna parte). El primero arrojaría un NumberFormatException(y esto no sucede) mientras que el segundo simplemente no tendría sentido ya que un intno puede ser null...

Marsellus Wallace
fuente
1
El nullcódigo original en OP no está encuadrado. La forma en que funciona es: el compilador supone que nulles una referencia a un número entero. Usando las reglas para los tipos de expresiones ternarias, decide que la expresión completa es una expresión entera. Luego genera código para autobox el 1(en caso de que la condición evalúe false). Durante la ejecución, la condición se evalúa como, por truelo que la expresión se evalúa como null. Al intentar devolver un intde la función, el nullestá sin caja. Eso luego arroja un NPE. (El compilador podría optimizar la mayor parte de esto.)
Ted Hopp
4

En realidad, en el primer caso, la expresión se puede evaluar, ya que el compilador sabe que debe evaluarse como un Integer; sin embargo, en el segundo caso, el tipo del valor de retorno ( null) no se puede determinar, por lo que no se puede compilar. Si lo envía Integer, el código se compilará.

Obtener
fuente
2
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}
Youans
fuente
0

Qué tal esto:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = "";
        s += (true ? 1 : "") instanceof Integer;
        System.out.println(s);

        String t = "";
        t += (!true ? 1 : "") instanceof String;
        System.out.println(t);

    }

}

La salida es verdadera, verdadera.

El color del eclipse codifica el 1 en la expresión condicional como autoboxed.

Supongo que el compilador está viendo el tipo de retorno de la expresión como Object.

Jon
fuente