En la programación basada en clases , la conversión descendente , o refinamiento de tipo, es el acto de convertir una referencia de clase base o padre en una referencia de clase derivada más restringida . [1] Esto solo está permitido si el objeto ya es una instancia de la clase derivada, por lo que esta conversión es inherentemente falible.
En muchos entornos, la introspección de tipos se puede utilizar para obtener el tipo de una instancia de objeto en tiempo de ejecución y luego utilizar este resultado para evaluar explícitamente su compatibilidad de tipos con otro tipo. Los posibles resultados de comparar tipos polimórficos (además de que sean equivalentes (idénticos) o no relacionados (incompatibles)) incluyen dos casos adicionales: a saber, cuando el primer tipo se deriva del segundo y luego es lo mismo pero intercambiado al revés (consulte: Subtipificación § Subsunción ).
Con esta información, un programa puede probar, antes de realizar una operación como almacenar un objeto en una variable tipificada, si esa operación es segura en cuanto a tipos o si generaría un error. Si el tipo de la instancia de tiempo de ejecución se deriva del tipo de la variable de destino (es decir, la variable principal) (un hijo de este), es posible realizar una conversión descendente.
Algunos lenguajes, como OCaml , no permiten el downcasting. [2]
clase pública Fruta {} // clase padre clase pública Manzana extiende Fruta {} // clase hija public static void main ( String [] args ) { // Lo siguiente es una conversión ascendente implícita: Fruit parent = new Apple (); // Lo siguiente es una conversión descendente. Aquí, funciona ya que la variable `parent` contiene una instancia de Apple: Apple child = ( Apple ) parent ; }
// Clase padre: clase Fruit { public : // Debe ser polimórfico para usar la conversión dinámica verificada en tiempo de ejecución. virtual ~ Fruit () = predeterminado ; }; // Clase hija: clase Manzana : public Fruta {}; int main ( int argc , const char ** argv ) { // Lo siguiente es una conversión ascendente implícita: Fruit * parent = new Apple (); // Lo siguiente es una conversión descendente. Aquí, funciona ya que la variable `parent` contiene una instancia de Apple: Apple * child = dynamic_cast < Apple *> ( parent ); }
La conversión descendente es útil cuando se conoce el tipo del valor al que hace referencia la variable principal y, a menudo, se utiliza al pasar un valor como parámetro. En el siguiente ejemplo, el método objectToString toma un parámetro Object que se supone que es de tipo String.
public static String objectToString ( Object myObject ) { // Esto solo funcionará cuando el myObject que actualmente contiene el valor sea una cadena. return ( String ) myObject ; } public static void main ( String [] args ) { // Esto funcionará ya que pasamos String, por lo que myObject tiene un valor String. String result = objectToString ( "My String" ); Object iFail = new Object (); // Esto fallará ya que pasamos Object que no tiene un valor String. result = objectToString ( iFail ); }
En este enfoque, la conversión descendente evita que el compilador detecte un posible error y, en su lugar, provoca un error en tiempo de ejecución. La conversión descendente de myObject a String ('(String)myObject') no fue posible en tiempo de compilación porque hay ocasiones en las que myObject es de tipo String, por lo que solo en tiempo de ejecución podemos determinar si el parámetro pasado es lógico. Si bien también podríamos convertir myObject a un String en tiempo de compilación utilizando el java.lang.Object.toString() universal, esto correría el riesgo de llamar a la implementación predeterminada de toString() donde no era útil o inseguro, y el manejo de excepciones no podría evitarlo.
En C++, la comprobación de tipos en tiempo de ejecución se implementa mediante dynamic_cast . La conversión descendente en tiempo de compilación se implementa mediante static_cast , pero esta operación no realiza ninguna comprobación de tipos. Si se utiliza de forma incorrecta, podría producir un comportamiento indefinido.
Un ejemplo popular de un diseño mal considerado son los contenedores de tipos superiores , [ cita requerida ] como los contenedores de Java antes de que se introdujeran los genéricos de Java , que requieren la conversión a lenguaje descendente de los objetos contenidos para que puedan usarse nuevamente.