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
object-oriented
type-inference
language-design
Sam Washburn
fuente
fuente
Respuestas:
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 devolverT
y generar un error de lo contrario.En su ejemplo específico, el compilador podría haber sido diseñado para deducir que en
parent
realidad es aApple
travé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.fuente
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
Apple
y luego ese conocimiento fue desechado almacenando la referencia en una variable de tipoFruit
. Entonces desea utilizar la misma referencia como unaApple
vez más.Dado que la información solo se descartó "localmente", seguro, el compilador podría mantener el conocimiento que
parent
es realmenteApple
, a pesar de que su tipo declarado lo esFruit
.Pero generalmente nadie hace eso. Si desea crear un
Apple
y usarlo comoApple
, lo almacena en unaApple
variable, no enFruit
una.Cuando tienes un
Fruit
y quieres usarlo como unApple
, generalmente significa que obtuviste aFruit
través de algún medio que generalmente podría devolver cualquier tipo deFruit
, pero en este caso sabes que era unApple
. Casi siempre no lo ha construido, sino que le ha pasado algún otro código.Un ejemplo obvio es si tengo una
parseFruit
funció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 deFruit
, pero si llamoparseFruit("apple")
a continuación, que sabemos que va a llamar a unaApple
y puede ser que desee utilizarApple
mé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.
fuente
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:
Ahora, extraigamos un método:
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:
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 sumaeat(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 ++ .fuente