Operador ternario de Java vs if / else en <compatibilidad con JDK8

113

Recientemente estoy leyendo el código fuente de Spring Framework. Algo que no puedo entender va aquí:

public Member getMember() {
    // NOTE: no ternary expression to retain JDK <8 compatibility even when using
    // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
    // as common type, with that new base class not available on older JDKs)
    if (this.method != null) {
        return this.method;
    }
    else {
        return this.constructor;
    }
}

Este método es miembro de la clase org.springframework.core.MethodParameter. El código es fácil de entender mientras que los comentarios son difíciles.

NOTA: no hay expresión ternaria para retener la compatibilidad JDK <8 incluso cuando se usa el compilador JDK 8 (potencialmente seleccionando java.lang.reflect.Executablecomo tipo común, con esa nueva clase base no disponible en JDK más antiguos)

¿Cuál es la diferencia entre usar una expresión ternaria y usar una if...else...construcción en este contexto?

jddxf
fuente

Respuestas:

103

Cuando piensa en el tipo de operandos, el problema se vuelve más evidente:

this.method != null ? this.method : this.constructor

tiene como tipo el tipo común más especializado de ambos operandos, es decir, el tipo más especializado común a ambos this.methody this.constructor.

En Java 7 esto es java.lang.reflect.Member, sin embargo, la biblioteca de clases de Java 8 introduce un nuevo tipo java.lang.reflect.Executableque es más especializado que el genérico Member. Por lo tanto, con una biblioteca de clases de Java 8, el tipo de resultado de la expresión ternaria es en Executablelugar de Member.

Algunas versiones (preliminares) del compilador de Java 8 parecen haber producido una referencia explícita al Executablecódigo generado en el interior al compilar el operador ternario. Esto desencadenaría una carga de clase y, por lo tanto, a su vez ClassNotFoundExceptionen tiempo de ejecución cuando se ejecuta con una biblioteca de clases <JDK 8, porque Executablesolo existe para JDK ≥ 8.

Como señaló Tagir Valeev en esta respuesta , esto es en realidad un error en las versiones previas al lanzamiento de JDK 8 y desde entonces se ha corregido, por lo que tanto la if-elsesolución alternativa como el comentario explicativo ahora son obsoletos.

Nota adicional: Se podría llegar a la conclusión de que este error del compilador estaba presente antes de Java 8. Sin embargo, el código de bytes generado para el ternario por OpenJDK 7 es el mismo que el código de bytes generado por OpenJDK 8. De hecho, el tipo de expresión no se menciona por completo en tiempo de ejecución, el código es realmente solo prueba, ramificación, carga, retorno sin ninguna verificación adicional. Así que tenga la seguridad de que esto ya no es un problema y, de hecho, parece haber sido un problema temporal durante el desarrollo de Java 8.

dhke
fuente
1
Entonces, ¿cómo se puede ejecutar el código compilado con JDK 1.8 en JDK 1.7? Sé que el código compilado con una versión inferior de JDK puede ejecutarse en una versión superior de JDK sin problemas. ¿Viceversa?
jddxf
1
@jddxf Todo está bien siempre que especifique la versión de archivo de clase adecuada y no use ninguna funcionalidad que no esté disponible en versiones posteriores. Sin embargo, es probable que ocurran problemas si tal uso ocurre implícitamente como en este caso.
Dhke
13
@jddxf, use -source / -target javac options
Tagir Valeev
1
Gracias a todos, especialmente a dhke y Tagir Valeev, que han dado una explicación detallada
jddxf
30

Esto se introdujo en un compromiso bastante antiguo el 3 de mayo de 2013, casi un año antes del lanzamiento oficial de JDK-8. El compilador estaba en un gran desarrollo en esos momentos, por lo que podían ocurrir problemas de compatibilidad. Supongo que el equipo de Spring acaba de probar la compilación JDK-8 e intentó solucionar problemas, aunque en realidad son problemas del compilador. Para el lanzamiento oficial de JDK-8, esto se volvió irrelevante. Ahora, el operador ternario en este código funciona bien como se esperaba (no hay ninguna referencia a la Executableclase en el archivo .class compilado).

Actualmente aparecen cosas similares en JDK-9: algún código que se puede compilar muy bien en JDK-8 falla con JDK-9 javac. Supongo que la mayoría de estos problemas se solucionarán hasta el lanzamiento.

Tagir Valeev
fuente
2
+1. Entonces, ¿fue esto un error en el compilador inicial? ¿Fue ese comportamiento, donde se menciona Executable, en violación de algún aspecto de la especificación? ¿O es solo que Oracle se dio cuenta de que podían cambiar este comportamiento de una manera que aún se ajustara a las especificaciones y sin romper la compatibilidad con versiones anteriores?
ruakh
2
@ruakh, supongo que fue el error. En el código de bytes (ya sea en Java-8 o en versiones anteriores) es completamente innecesario convertir explícitamente a Executabletipos intermedios. En Java-8, el concepto de inferencia de tipos de expresión cambió drásticamente y esta parte se reescribió por completo, por lo que no es tan sorprendente que las primeras implementaciones tuvieran errores.
Tagir Valeev
7

La principal diferencia es que un if elsebloque es una declaración, mientras que el ternario (más conocido como operador condicional en Java) es una expresión .

Una declaración puede hacer cosas similares returna la persona que llama en algunas de las rutas de control. Se puede utilizar una expresión en una tarea:

int n = condition ? 3 : 2;

Entonces, las dos expresiones en el ternario después de la condición deben ser coercibles al mismo tipo. Esto puede causar algunos efectos extraños en Java, particularmente con el auto-boxing y la conversión automática de referencias; esto es a lo que se refiere el comentario en su código publicado. La coerción de las expresiones en su caso sería a un java.lang.reflect.Executabletipo (ya que ese es el tipo más especializado ) y eso no existe en versiones anteriores de Java.

Estilísticamente, debería usar un if elsebloque si el código es similar a una declaración y un ternario si es similar a una expresión.

Por supuesto, puede hacer que un if elsebloque se comporte como una expresión si usa una función lambda.

Betsabé
fuente
6

El tipo de valor de retorno en una expresión ternaria se ve afectado por las clases principales, que cambiaron como se describe en Java 8.

Es difícil ver por qué no se pudo haber escrito un elenco.

Marqués de Lorne
fuente