Automatisches Downcasting durch Ableiten des Typs

11

In Java müssen Sie explizit umwandeln, um eine Variable herunterzuspielen

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

Gibt es einen Grund für diese Anforderung, abgesehen von der Tatsache, dass Java keine Typinferenz macht?

Gibt es irgendwelche "Fallstricke" bei der Implementierung von automatischem Downcasting in einer neuen Sprache?

Zum Beispiel:

Apple child = parent; // no cast required 
Sam Washburn
quelle
Sie sagen, wenn der Compiler daraus schließen kann, dass das heruntergesendete Objekt immer vom richtigen Typ ist, sollten Sie in der Lage sein, den Downcast nicht explizit auszudrücken? Abhängig von der Compilerversion und der möglichen Typinferenz können einige Programme jedoch nicht gültig sein. Fehler im Typinferencer können verhindern, dass gültige Programme geschrieben werden. Es ist nicht sinnvoll, spezielle Programme einzuführen Wenn dies von vielen Faktoren abhängt, wäre es für die Benutzer nicht intuitiv, wenn sie bei jeder Veröffentlichung ohne Grund Casts hinzufügen oder entfernen müssten.
Bakuriu

Antworten:

24

Upcasts sind immer erfolgreich.

Downcasts können zu einem Laufzeitfehler führen, wenn der Objektlaufzeittyp kein Untertyp des in der Umwandlung verwendeten Typs ist.

Da die zweite Operation gefährlich ist, muss der Programmierer bei den meisten typisierten Programmiersprachen explizit danach fragen. Im Wesentlichen sagt der Programmierer dem Compiler: "Vertrau mir, ich weiß es besser - das wird zur Laufzeit in Ordnung sein."

Wenn es um Typsysteme geht, belasten Upcasts den Compiler mit der Beweislast (der ihn statisch überprüfen muss), Downcasts den Programmierer mit der Beweislast (der darüber nachdenken muss).

Man könnte argumentieren, dass eine richtig gestaltete Programmiersprache Downcasts vollständig verbietet oder sichere Casts-Alternativen bietet, z. B. die Rückgabe eines optionalen Typs Option<T>. Viele weit verbreitete Sprachen entschieden sich jedoch für den einfacheren und pragmatischeren Ansatz, einfach zurückzukehren Tund ansonsten einen Fehler zu melden.

In Ihrem speziellen Beispiel könnte der Compiler so konzipiert sein, dass parenter Appledurch eine einfache statische Analyse ableitet, dass dies tatsächlich eine ist, und die implizite Umwandlung zulässt. Im Allgemeinen ist das Problem jedoch unentscheidbar, sodass wir nicht erwarten können, dass der Compiler zu viel Magie ausführt.

Chi
quelle
1
Ein Beispiel für eine Sprache, die auf Optionen heruntergestuft wird, ist Rust. Dies liegt jedoch daran, dass sie keine echte Vererbung hat, sondern nur einen "beliebigen Typ" .
Kroltan
3

Normalerweise ist Downcasting das, was Sie tun, wenn das statisch bekannte Wissen des Compilers über die Art von etwas weniger spezifisch ist als das, was Sie wissen (oder zumindest hoffen).

In Situationen wie Ihrem Beispiel wurde das Objekt als erstellt, Appleund dann wurde dieses Wissen durch Speichern der Referenz in einer Variablen vom Typ weggeworfen Fruit. Dann möchten Sie dieselbe Referenz wie Applewieder verwenden.

Da die Informationen natürlich nur "lokal" weggeworfen wurden, konnte der Compiler das Wissen beibehalten, das parentwirklich ein ist Apple, obwohl sein deklarierter Typ ist Fruit.

Aber normalerweise macht das niemand. Wenn Sie eine erstellen Appleund als verwenden möchten Apple, speichern Sie sie in einer AppleVariablen, nicht in Fruiteiner.

Wenn Sie ein haben Fruitund es als verwenden möchten, Applebedeutet dies normalerweise, dass Sie das Fruitdurch einige Mittel erhalten haben, die im Allgemeinen jede Art von zurückgeben können Fruit, aber in diesem Fall wissen Sie, dass es ein war Apple. Fast immer haben Sie es nicht nur erstellt, sondern wurden von einem anderen Code übergeben.

Ein naheliegendes Beispiel ist, wenn ich eine parseFruitFunktion habe, mit der Zeichenfolgen wie "Apfel", "Orange", "Zitrone" usw. in die entsprechende Unterklasse umgewandelt werden können. generell alle können wir (und der Compiler) wissen über diese Funktion ist , dass es eine Art gibt Fruit, aber wenn ich rufe parseFruit("apple")dann ich weiß , dass das eine geht zu nennen Appleund vielleicht verwenden möchten AppleMethoden, so dass ich niedergeschlagenen konnte.

Wiederum könnte ein ausreichend intelligenter Compiler dies hier herausfinden, indem er den Quellcode für einfügt parseFruit, da ich ihn mit einer Konstanten aufrufe (es sei denn, er befindet sich in einem anderen Modul und wir haben eine separate Kompilierung, wie in Java). Sie sollten jedoch leicht erkennen können, wie schwierig (oder sogar unmöglich!) Die Überprüfung komplizierterer Beispiele mit dynamischen Informationen für den Compiler sein kann.

In realistischem Code treten normalerweise Downcasts auf, bei denen der Compiler mit generischen Methoden nicht überprüfen konnte, ob der Downcast sicher ist, und nicht in so einfachen Fällen wie unmittelbar nach einem Upcast, der dieselben Typinformationen wegwirft, die wir durch Downcasting zurückerhalten möchten.

Ben
quelle
3

Es ist eine Frage, wo Sie die Grenze ziehen möchten. Sie können eine Sprache entwerfen, die die Gültigkeit des impliziten Downcasts erkennt:

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

Lassen Sie uns nun eine Methode extrahieren:

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

Wir sind immer noch gut. Statische Analysen sind viel schwieriger, aber immer noch möglich.

Aber das Problem taucht in der Sekunde auf, in der jemand hinzufügt:

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

Wo möchten Sie den Fehler auslösen? Dies schafft ein Dilemma, man kann sagen, dass das Problem besteht Apple child = parent;, aber dies kann durch "Aber es hat vorher funktioniert" widerlegt werden. Auf der anderen Seite eat(trouble);verursachte das Hinzufügen das Problem ", aber der springende Punkt beim Polymorphismus ist, genau das zuzulassen.

In diesem Fall können Sie einen Teil der Arbeit des Programmierers erledigen, aber nicht vollständig. Je weiter Sie vor dem Aufgeben gehen, desto schwieriger wird es, zu erklären, was schief gelaufen ist. Es ist daher besser, so schnell wie möglich anzuhalten, um Fehler frühzeitig zu melden.

Übrigens ist in Java der von Ihnen beschriebene Downcast eigentlich kein Downcast. Dies ist eine allgemeine Besetzung, die Äpfel genauso gut zu Orangen gießen kann. Technisch gesehen ist @ chis Idee also schon da, es gibt keine Downcasts in Java, nur "Everycasts". Es wäre sinnvoll, einen speziellen Downcast-Operator zu entwerfen, der einen Kompilierungsfehler auslöst, wenn der Ergebnistyp nicht nach dem Argumenttyp gefunden werden kann. Es wäre eine gute Idee, die Verwendung von "Everycast" erheblich zu erschweren, um Programmierer davon abzuhalten, sie ohne guten Grund zu verwenden. Die XXXXX_cast<type>(argument)Syntax von C ++ fällt mir ein.

Agent_L
quelle