Downcasting automático al inferir el tipo

11

En Java, debe emitir explícitamente para rechazar una variable

public class Fruit{}  // parent class
public class Apple extends Fruit{}  // child class

public static void main(String args[]) {
    // An implicit upcast
    Fruit parent = new Apple();
    // An explicit downcast to Apple
    Apple child = (Apple)parent;
}

¿Hay alguna razón para este requisito, aparte del hecho de que Java no hace ninguna inferencia de tipo?

¿Hay alguna "trampa" con la implementación del downcasting automático en un nuevo idioma?

Por ejemplo:

Apple child = parent; // no cast required 
Sam Washburn
fuente
¿Está diciendo que, si el compilador puede inferir que el objeto que se está descartando siempre es del tipo correcto, no debería poder expresar explícitamente el rechazo? Pero luego, dependiendo de la versión del compilador y de la cantidad de inferencia de tipos que puede hacer, algunos programas pueden no ser válidos ... los errores en el inferenciador de tipos podrían evitar que se escriban programas válidos, etc. ... no tiene sentido introducir algunos programas especiales en caso de que dependa de muchos factores, no sería intuitivo para los usuarios si tuvieran que seguir agregando o quitando moldes sin ningún motivo en cada lanzamiento.
Bakuriu

Respuestas:

24

Upcasts siempre tienen éxito.

Las descargas pueden producir un error de tiempo de ejecución, cuando el tipo de tiempo de ejecución del objeto no es un subtipo del tipo utilizado en el reparto.

Como la segunda es una operación peligrosa, la mayoría de los lenguajes de programación mecanografiados requieren que el programador la solicite explícitamente. Esencialmente, el programador le dice al compilador "confía en mí, lo sé mejor, esto estará bien en tiempo de ejecución".

Cuando se trata de sistemas tipográficos, los upcasts ponen la carga de la prueba en el compilador (que tiene que verificarlo estáticamente), los downcasts ponen la carga de la prueba en el programador (que tiene que pensarlo detenidamente).

Se podría argumentar que un lenguaje de programación correctamente diseñado prohibiría completamente las descargas, o proporcionaría alternativas de conversión seguras, por ejemplo, devolver un tipo opcional Option<T>. Sin embargo, muchos lenguajes generalizados eligieron el enfoque más simple y más pragmático de simplemente devolver Ty generar un error de lo contrario.

En su ejemplo específico, el compilador podría haber sido diseñado para deducir que en parentrealidad es a Appletravés de un análisis estático simple y permitir la conversión implícita. Sin embargo, en general, el problema es indecidible, por lo que no podemos esperar que el compilador realice demasiada magia.

chi
fuente
1
Solo como referencia, un ejemplo de un lenguaje que se convierte en opcionales es Rust, pero eso se debe a que no tiene una herencia verdadera, solo un "cualquier tipo" .
Kroltan
3

Por lo general, el downcasting es lo que haces cuando el conocimiento estáticamente conocido que el compilador tiene sobre el tipo de algo es menos específico de lo que sabes (o al menos esperas).

En situaciones como su ejemplo, el objeto fue creado como un Appley luego ese conocimiento fue desechado almacenando la referencia en una variable de tipo Fruit. Entonces desea utilizar la misma referencia como una Applevez más.

Dado que la información solo se descartó "localmente", seguro, el compilador podría mantener el conocimiento que parentes realmente Apple, a pesar de que su tipo declarado lo es Fruit.

Pero generalmente nadie hace eso. Si desea crear un Appley usarlo como Apple, lo almacena en una Applevariable, no en Fruituna.

Cuando tienes un Fruity quieres usarlo como un Apple, generalmente significa que obtuviste a Fruittravés de algún medio que generalmente podría devolver cualquier tipo de Fruit, pero en este caso sabes que era un Apple. Casi siempre no lo ha construido, sino que le ha pasado algún otro código.

Un ejemplo obvio es si tengo una parseFruitfunción que puede convertir cadenas como "manzana", "naranja", "limón", etc., en la subclase apropiada; en general, todo lo que (y el compilador) podemos saber acerca de esta función es que se vuelve una especie de Fruit, pero si llamo parseFruit("apple")a continuación, que sabemos que va a llamar a una Appley puede ser que desee utilizar Applemétodos, por lo que podría abatido.

Una vez más, un compilador suficientemente inteligente podría resolverlo aquí al incluir el código fuente parseFruit, ya que lo llamo con una constante (a menos que esté en otro módulo y tengamos una compilación separada, como en Java). Pero debería poder ver fácilmente cómo los ejemplos más complicados que involucran información dinámica podrían ser más difíciles (¡o incluso imposibles!) Para que el compilador los verifique.

En el código realista, las descargas se producen cuando el compilador no puede verificar que la descarga sea segura utilizando métodos genéricos, y no en casos tan simples como inmediatamente después de una descarga que arroja la misma información de tipo que estamos tratando de recuperar mediante la descarga.

Ben
fuente
3

Es una cuestión de dónde quieres trazar la línea. Puede diseñar un lenguaje que detecte la validez del downcast implícito:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Ahora, extraigamos un método:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Aún estamos bien. El análisis estático es mucho más difícil pero aún posible.

Pero el problema aparece en el momento en que alguien agrega:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

Ahora, ¿dónde quieres generar el error? Esto crea un dilema, se puede decir que el problema está en Apple child = parent;, pero esto puede ser refutado por "Pero funcionó antes". Por otro lado, la suma eat(trouble);causó el problema ", pero el objetivo del polimorfismo es permitir exactamente eso.

En este caso, puede hacer parte del trabajo del programador, pero no puede hacerlo del todo. Cuanto más lo lleves antes de rendirte, más difícil será explicar qué salió mal. Por lo tanto, es mejor detenerse lo antes posible, de acuerdo con el principio de informar los errores temprano.

Por cierto, en Java, el downcast que ha descrito no es en realidad un downcast. Este es un molde general, que también puede arrojar manzanas a las naranjas. Entonces, técnicamente hablando, la idea de @ chi ya está aquí, no hay downcasts en Java, solo "everycasts". Tendría sentido diseñar un operador abatido especializado que arroje un error de compilación cuando su tipo de resultado no se pueda encontrar más abajo de su tipo de argumento. Sería una buena idea hacer que "cada difusión" sea mucho más difícil de usar para disuadir a los programadores de usarlo sin una muy buena razón. XXXXX_cast<type>(argument)Me viene a la mente la sintaxis de C ++ .

Agent_L
fuente