Sagen wir, ich habe eine Klasse Tier und die Klassen Katze und Hund, die Tier erweitern. Katze, Tier und Hund haben alle eine Methode speak(), die für Katze "miau" und für Hund "wuff" ausgibt und für Tier "kann nicht sprechen".
Nun gut, hier ist endlich meine Frage. Was genau passiert, wenn ich eine Katze ( c
) und führen Sie dann Animal a = c;
? Was passiert, wenn ich a.speak();
? Welche speak()
Methode aufgerufen wird? Was genau ist passiert, wenn ich die Typen auf diese Weise ändere? Werde ich jemals einen echten Grund haben, dies zu verwenden?
Objekte in Java wissen genau, als welcher Typ sie erstellt wurden; es ist quasi ein verstecktes Feld (das Sie mit der Funktion Object.getClass()
Methode). Darüber hinaus beginnt die Auflösung aller nichtstatischen Methoden mit den Methodendefinitionen der spezifischsten Klasse und geht dann zur generischsten Klasse ( Object
); da es in Java immer nur eine einzige Vererbung der Implementierung gibt, ist dies eine einfache Suche. Cat
weiß, dass es ein Subtyp von Animal
, die ein Subtyp ist von Object
und c
weiß, dass es sich um eine Cat
unabhängig vom Typ der Variablen.
Wenn Sie die Zuordnung vornehmen, wird die Compiler prüft, ob der bekannte Typ des zugewiesenen Wertes der Typ ist, der zugewiesen wird oder einer seiner Subtypen . Wenn ja, funktioniert die Zuweisung. Wenn nicht, brauchen Sie einen expliziten Cast (der zur Laufzeit eine korrekte Typüberprüfung vornimmt; Casts können das Typsystem von Java nicht zerstören, sie können es nur hässlich machen). Es ändert nichts an der Tatsache, dass die Methodensuche immer noch dynamisch erfolgt und das Objekt immer noch weiß, welcher Typ es wirklich ist; das Programm vernachlässigt lediglich einige Informationen. Wenn Sie C++ kennen, dann stellen Sie sich vor, dass Java nur virtuelle Methoden (und statische Methoden) hat, und es ist sehr einfach, die Lookups in den vtables zu handhaben, weil es kein Problem mit Vererbungsdiamanten oder anderen bösen Fällen gibt.
Bei der Arbeit mit Implementierungen einer Schnittstelle ist es fast dasselbe, außer dass ein komplexeres Lookup durchgeführt wird (d.h. zuerst wird der Index in der vtable nachgeschlagen, bevor man wie zuvor fortfährt). Ein Objekt zu haben, das die Schnittstelle implementiert, bedeutet jedoch, dass es eine Klasse geben muss, die die Schnittstelle vollständig implementiert, und dann ist alles wieder relativ einfach. Und denken Sie daran, dass alle wirklich komplizierten Dinge zur Kompilierungszeit erledigt werden; zur Laufzeit sind die Dinge in allen Fällen relativ einfach.
Werden Sie etwas davon in Anspruch nehmen? Nun, Sie sollte (und Sie werden feststellen, dass es sich in echtem Code nur schwer vermeiden lässt). Es ist ein guter Stil, sich einen Vertrag vorzustellen, der durch eine Schnittstelle oder Superklasse definiert ist, wobei die Subtypen den Vertrag befolgen, ohne dass der Aufrufer die Details kennen muss. Java-Bibliotheken verwenden dies sehr häufig, zumal die Sichtbarkeit der Typdetails des Vertrags und seine Erfüllung unterschiedlich sein können. Alles, was der Client-Code weiß, ist, dass das Objekt dem Vertrag gehorcht, dass es vom gegebenen Typ ist.