Downcasting automatico inferendo il tipo


11

In Java, è necessario eseguire il cast esplicito per eseguire il downcast di una variabile

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;
}

C'è qualche motivo per questo requisito, a parte il fatto che Java non fa alcuna deduzione di tipo?

Ci sono dei "trucchi" con l'implementazione del downcasting automatico in una nuova lingua?

Per esempio:

Apple child = parent; // no cast required 

Stai dicendo che, se il compilatore può dedurre che l'oggetto in fase di downcast è sempre del tipo corretto, dovresti essere in grado di non esprimere esplicitamente il downcast? Ma a seconda della versione del compilatore e di quanto inferenza di tipo può fare alcuni programmi potrebbero non essere validi ... i bug nell'inferencer di tipo potrebbero impedire la scrittura di programmi validi ecc ... non ha senso introdurre alcuni speciali caso che dipende da molti fattori, non sarebbe intuitivo per gli utenti se dovessero continuare ad aggiungere o rimuovere i cast senza motivo per ogni rilascio.
Bakuriu,

Risposte:


24

Gli aumenti hanno sempre successo.

I downcast possono causare un errore di runtime, quando il tipo di runtime dell'oggetto non è un sottotipo del tipo utilizzato nel cast.

Poiché la seconda è un'operazione pericolosa, la maggior parte dei linguaggi di programmazione tipizzati richiedono al programmatore di richiederlo esplicitamente. In sostanza, il programmatore sta dicendo al compilatore "fidati di me, lo so meglio - questo andrà bene in fase di esecuzione".

Quando si tratta di sistemi di tipo, gli upcasts pongono l'onere della dimostrazione sul compilatore (che deve controllarlo staticamente), gli downcasts mettono l'onere della dimostrazione sul programmatore (che deve pensarci bene).

Si potrebbe sostenere che un linguaggio di programmazione ben progettato proibirebbe completamente i downcast o fornire alternative ai cast sicuri, ad esempio la restituzione di un tipo opzionale Option<T>. Molte lingue diffuse, tuttavia, hanno scelto l'approccio più semplice e più pragmatico di semplicemente restituire Te sollevare un errore altrimenti.

Nel tuo esempio specifico, il compilatore avrebbe potuto essere progettato per dedurre che in parentrealtà è Appleattraverso una semplice analisi statica e consentire il cast implicito. Tuttavia, in generale il problema è indecidibile, quindi non possiamo aspettarci che il compilatore esegua troppa magia.


1
Solo per riferimento, un esempio di un linguaggio che passa agli opzionali è Rust, ma è perché non ha una vera eredità, solo un "qualsiasi tipo" .
Kroltan,

3

Di solito il downcasting è ciò che fai quando la conoscenza staticamente nota del compilatore sul tipo di qualcosa è meno specifica di ciò che conosci (o almeno speri).

In situazioni come il tuo esempio, l'oggetto è stato creato come un Applee quindi quella conoscenza è stata gettata via memorizzando il riferimento in una variabile di tipo Fruit. Quindi si desidera utilizzare Applenuovamente lo stesso riferimento di un .

Dato che l'informazione è stata eliminata solo "localmente", il compilatore potrebbe mantenere la conoscenza che parentè realmente un Apple, anche se il suo tipo dichiarato è Fruit.

Ma di solito nessuno lo fa. Se vuoi creare un Applee usarlo come un Apple, lo memorizzi in una Applevariabile, non in una Fruit.

Quando hai un Fruite vuoi usarlo come un di Applesolito significa che hai ottenuto Fruitattraverso alcuni mezzi che generalmente potrebbero restituire qualsiasi tipo di Fruit, ma in questo caso sai che era un Apple. Quasi sempre non l'hai appena costruito, lo hai passato con qualche altro codice.

Un esempio ovvio è se ho una parseFruitfunzione che può trasformare stringhe come "mela", "arancia", "limone", ecc. Nella sottoclasse appropriata; in generale tutto ciò che (e il compilatore) è dato sapere su questa funzione è che restituisce una sorta di Fruit, ma se chiamo parseFruit("apple")allora mi sa che sta per chiamare un Applee potrebbero voler utilizzare Applei metodi, così ho potuto abbattuto.

Ancora una volta un compilatore sufficientemente intelligente potrebbe capirlo qui integrando il codice sorgente per parseFruit, dal momento che lo sto chiamando con una costante (a meno che non sia in un altro modulo e abbiamo una compilazione separata, come in Java). Ma dovresti essere facilmente in grado di vedere come esempi più complicati che coinvolgono informazioni dinamiche potrebbero diventare più difficili (o addirittura impossibili!) Per il compilatore da verificare.

In downcasts di codice realistici di solito si verificano dove il compilatore non è in grado di verificare che il downcast sia sicuro utilizzando metodi generici e non in casi così semplici come immediatamente dopo un upcast che getta via le stesse informazioni di tipo che stiamo cercando di ottenere tramite downcast.


3

È una questione di dove vuoi tracciare la linea. È possibile progettare una lingua che rilevi la validità del downcast implicito:

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

Ora estraiamo un metodo:

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; 
}

Siamo ancora bravi. L'analisi statica è molto più difficile ma ancora possibile.

Ma il problema si presenta nel momento in cui qualcuno aggiunge:

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

Ora, dove vuoi generare l'errore? Questo crea un dilemma, si può dire che il problema è presente Apple child = parent;, ma questo può essere confutato da "Ma ha funzionato prima". D'altra parte, l'aggiunta ha eat(trouble);causato il problema ", ma il punto centrale del polimorfismo è consentire esattamente questo.

In questo caso, puoi svolgere un po 'del lavoro del programmatore, ma non puoi farlo fino in fondo. Più lo prendi prima di arrenderti, più difficile sarà spiegare cosa è andato storto. Quindi, è meglio fermarsi il prima possibile, secondo il principio di segnalare gli errori in anticipo.

A proposito, in Java il downcast che hai descritto non è in realtà un downcast. Questo è un cast generale, che può anche lanciare mele sulle arance. Quindi, tecnicamente parlando, l'idea di @ chi è già qui, non ci sono downcasts in Java, solo "everycasts". Avrebbe senso progettare un operatore downcast specializzato che genererà un errore di compilazione quando il suo tipo di risultato non può essere trovato a valle del suo tipo di argomento. Sarebbe una buona idea rendere "everycast" molto più difficile da usare per scoraggiare i programmatori dall'usarlo senza un motivo molto valido. XXXXX_cast<type>(argument)Viene in mente la sintassi di C ++ .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.