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
object-oriented
type-inference
language-design
Sam Washburn
quelle
quelle
Antworten:
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ückzukehrenT
und ansonsten einen Fehler zu melden.In Ihrem speziellen Beispiel könnte der Compiler so konzipiert sein, dass
parent
erApple
durch 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.quelle
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,
Apple
und dann wurde dieses Wissen durch Speichern der Referenz in einer Variablen vom Typ weggeworfenFruit
. Dann möchten Sie dieselbe Referenz wieApple
wieder verwenden.Da die Informationen natürlich nur "lokal" weggeworfen wurden, konnte der Compiler das Wissen beibehalten, das
parent
wirklich ein istApple
, obwohl sein deklarierter Typ istFruit
.Aber normalerweise macht das niemand. Wenn Sie eine erstellen
Apple
und als verwenden möchtenApple
, speichern Sie sie in einerApple
Variablen, nicht inFruit
einer.Wenn Sie ein haben
Fruit
und es als verwenden möchten,Apple
bedeutet dies normalerweise, dass Sie dasFruit
durch einige Mittel erhalten haben, die im Allgemeinen jede Art von zurückgeben könnenFruit
, aber in diesem Fall wissen Sie, dass es ein warApple
. Fast immer haben Sie es nicht nur erstellt, sondern wurden von einem anderen Code übergeben.Ein naheliegendes Beispiel ist, wenn ich eine
parseFruit
Funktion 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 gibtFruit
, aber wenn ich rufeparseFruit("apple")
dann ich weiß , dass das eine geht zu nennenApple
und vielleicht verwenden möchtenApple
Methoden, 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.
quelle
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:
Lassen Sie uns nun eine Methode extrahieren:
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:
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 Seiteeat(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.quelle