¿Por qué javac permite algunos moldes imposibles y no otros?

52

Si intento convertir a Stringa a java.util.Date, el compilador de Java detecta el error. Entonces, ¿por qué el compilador no marca lo siguiente como un error?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

Por supuesto, la JVM lanza un ClassCastExceptionen tiempo de ejecución, pero el compilador no lo marca.

El comportamiento es el mismo con javac 1.8.0_212 y 11.0.2.

Mike Woinoski
fuente
2
No tiene nada de especial Listaquí. Date d = (Date) new Object();
Elliott Frisch
1
He estado jugando con un arduino últimamente. Me encantaría un compilador que no aceptara felizmente ningún elenco y luego lo hiciera con resultados totalmente impredecibles. Cadena a entero? ¡Cosa segura! Doble a entero? ¡Sí señor! Cadena a booleano? Al menos eso se vuelve falso ...
Stian Yttervik
@ElliottFrisch: Hay una relación de herencia obvia entre Fecha y Objeto, pero no hay relación entre Fecha y Lista. Así que esperaba que el compilador marcara este elenco, de la misma manera que marcaría un elenco de String to Date. Pero como explica Zabuza en su excelente respuesta, List es una interfaz, por lo que el elenco sería legal si strListfuera una instancia de una clase que implementa List.
Mike Woinoski
Esta es una pregunta frecuente, y estoy seguro de que he visto múltiples duplicados. Básicamente es la versión inversa de la fuertemente relacionada: stackoverflow.com/questions/21812289/…
Hulk
1
@StianYttervik -permisivo es lo que está haciendo eso. Activa las advertencias del compilador.
Bobsburner

Respuestas:

86

El reparto es técnicamente posible. Javac no puede demostrar fácilmente que no es así en su caso y el JLS en realidad define esto como un programa Java válido, por lo que marcar un error sería incorrecto.

Esto se debe a que Listes una interfaz. Por lo tanto, podría tener una subclase de una Dateque realmente implemente Listdisfrazada como Listaquí, y luego lanzarla Dateestaría perfectamente bien. Por ejemplo:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

Y entonces:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine

La detección de este escenario podría no ser siempre posible, ya que requeriría información de tiempo de ejecución si la instancia proviene, por ejemplo, de un método. E incluso si, requeriría mucho más esfuerzo para el compilador. El compilador solo evita los lanzamientos que son absolutamente imposibles debido a que no hay forma de que el árbol de clases coincida en absoluto. Que no es el caso aquí, como se ve.

Tenga en cuenta que JLS requiere que su código sea un programa Java válido. En 5.1.6.1. Conversión de referencia de estrechamiento permitida dice:

Existe una conversión de referencia de reducción de tipo Sde referencia a tipo de referencia Tsi se cumple todo lo siguiente :

  • [...]
  • Se aplica uno de los siguientes casos :
    • [...]
    • Ses un tipo de interfaz, Tes un tipo de clase y Tno nombra una finalclase.

Entonces, incluso si el compilador pudiera descubrir que su caso es realmente imposible, no está permitido marcar un error porque el JLS lo define como un programa Java válido.

Solo se le permitiría mostrar una advertencia.

Zabuzard
fuente
16
Y vale la pena notar que la razón por la que atrapa el caso con String es que String es final, por lo que el compilador sabe que ninguna clase puede extenderlo.
MTilsted
55
En realidad, no creo que sea la "finalidad" de String lo que hace myDate = (Date) myStringfallar. Usando la terminología JLS, la declaración intenta convertir de S(the String) a T(the Date). Aquí, Sno es un tipo de interfaz, por lo que no se aplica la condición JLS citada anteriormente. Como ejemplo, intente enviar un Calendario a una Fecha y obtendrá un error de compilación aunque ninguna de las clases sea final.
Mike Woinoski
1
No sé si estar decepcionado o no, el compilador no puede hacer suficiente análisis estático para demostrar que strList solo puede ser de tipo ArrayList.
Joshua
3
El compilador no tiene prohibido verificar. Pero está prohibido llamarlo un error. Eso haría que el compilador no sea compatible. (Vea mi respuesta ...)
Stephen C
3
Para agregar un poco de jerga, el compilador necesitaría demostrar que el tipo Date & Listes inhabitable , no es suficiente para demostrar que está deshabitado actualmente (podría estarlo en el futuro).
Polygnome
15

Consideremos una generalización de su ejemplo:

List<String> strList = someMethod();       
Date d = (Date) strList;

Estas son las razones principales por las Date d = (Date) strList;que no se trata de un error de compilación.

  • La razón intuitiva es que el compilador no (en general) conoce el tipo preciso del objeto devuelto por esa llamada al método. Es posible que además de ser una clase que implementa List, es también una subclase de Date.

  • La razón técnica es que la Especificación del lenguaje Java "permite" la conversión de referencia de estrechamiento que corresponde a este tipo de conversión . De acuerdo con JLS 5.1.6.1 :

    "Existe una conversión de referencia de reducción de tipo Sde referencia a tipo de referencia Tsi se cumple todo lo siguiente:"

    ...

    5) " Ses un tipo de interfaz, Tes un tipo de clase y Tno nombra una finalclase".

    ...

    En un lugar diferente, JLS también dice que se puede lanzar una excepción en tiempo de ejecución ...

    Tenga en cuenta que la determinación de JLS 5.1.6.1 se basa únicamente en los tipos declarados de las variables involucradas en lugar de los tipos de tiempo de ejecución reales. En el caso general, el compilador no conoce ni puede conocer los tipos de tiempo de ejecución reales.


Entonces, ¿por qué el compilador de Java no puede resolver que el elenco no funcionará?

  • En mi ejemplo, la someMethodllamada podría devolver objetos con una variedad de tipos. Incluso si el compilador pudo analizar el cuerpo del método y determinar el conjunto preciso de tipos que podrían devolverse, no hay nada que impida que alguien lo cambie para devolver diferentes tipos ... después de compilar el código que lo llama. Esta es la razón básica por la que JLS 5.1.6.1 dice lo que dice.

  • En su ejemplo, un compilador inteligente podría descubrir que el elenco nunca puede tener éxito. Y está permitido emitir una advertencia en tiempo de compilación para señalar el problema.

Entonces, ¿por qué no se le permite a un compilador inteligente decir que esto es un error de todos modos?

  • Porque el JLS dice que este es un programa válido. Período. Cualquier compilador que haya llamado a esto un error no sería compatible con Java.

  • Además, cualquier compilador que rechace los programas de Java que JLS y otros compiladores dicen que es válido, es un impedimento para la portabilidad del código fuente de Java.

Stephen C
fuente
44
Vota por el hecho de que después de compilar la clase que llama la implementación de la función llamada puede cambiar , por lo que incluso si es demostrable en el momento de la compilación, con la implementación actual de la persona que llama, el lanzamiento es imposible, esto puede no ser así en tiempos de ejecución posteriores cuando la persona que llama ha cambiado o ha sido reemplazada.
Peter - Restablece a Monica el
2
Vota por resaltar el problema de portabilidad que se introduciría si un compilador intenta ser demasiado inteligente.
Mike Woinoski
2

5.5.1. Tipo de referencia de fundición:

Dado un tipo de referencia en tiempo de compilación S(fuente) y un tipo de referencia en tiempo de compilación T(destino), existe una conversión de conversión de Sa T si no se producen errores en tiempo de compilación debido a las siguientes reglas.

[...]

Si Ses un tipo de interfaz:

  • [...]

  • Si Tes una clase o tipo de interfaz que no es final, entonces si existe un supertipo Xde T, y un supertipo Yde S, de modo que ambos Xy Ysean tipos parametrizados demostrablemente distintos, y que las borradas de Xy Ysean iguales, un error en tiempo de compilación ocurre.

    De lo contrario, el reparto siempre es legal en tiempo de compilación (porque incluso si Tno se implementa S, una subclase de Tpoder).

List<String>es Sy Datees Ten tu caso.

Oleksandr Pyrohov
fuente