web-dev-qa-db-ger.com

beispiel für explizites Typgießen in Java

Ich bin auf dieses Beispiel auf http://www.javabeginner.com/learn-Java/java-object-typecasting gestoßen - und in dem Teil, in dem es um explizites Typ-Casting geht, gibt es ein Beispiel, das mich verwirrt.

Das Beispiel:

class Vehicle {

    String name;
    Vehicle() {
        name = "Vehicle";
    }
}

class HeavyVehicle extends Vehicle {

    HeavyVehicle() {
        name = "HeavyVehicle";
    }
}

class Truck extends HeavyVehicle {

    Truck() {
        name = "Truck";
    }
}

class LightVehicle extends Vehicle {

    LightVehicle() {
        name = "LightVehicle";
    }
}

public class InstanceOfExample {

    static boolean result;
    static HeavyVehicle hV = new HeavyVehicle();
    static Truck T = new Truck();
    static HeavyVehicle hv2 = null;
    public static void main(String[] args) {
        result = hV instanceof HeavyVehicle;
        System.out.print("hV is an HeavyVehicle: " + result + "\n");
        result = T instanceof HeavyVehicle;
        System.out.print("T is an HeavyVehicle: " + result + "\n");
        result = hV instanceof Truck;
        System.out.print("hV is a Truck: " + result + "\n");
        result = hv2 instanceof HeavyVehicle;
        System.out.print("hv2 is an HeavyVehicle: " + result + "\n");
        hV = T; //Sucessful Cast form child to parent
        T = (Truck) hV; //Sucessful Explicit Cast form parent to child
    }
}

Warum wird in der letzten Zeile, in der T die Referenz hV und die Typumwandlung als (LKW) zugewiesen wird, in dem Kommentar besagt, dass dies eine erfolgreiche explizite Umwandlung von einem Elternteil an ein Kind ist? Ich verstehe, dass das Casting (implizit oder explizit) nur den deklarierten Objekttyp ändert, nicht den tatsächlichen Typ (der sich niemals ändern sollte, es sei denn, Sie ordnen der Feldreferenz dieses Objekts tatsächlich eine neue Klasseninstanz zu). Wenn hv bereits eine Instanz einer HeavyVehicle-Klasse zugewiesen wurde, die eine Superklasse der Truck-Klasse ist, wie kann dann dieses Feld in eine spezifischere Unterklasse namens Truck umgewandelt werden, die sich aus der HeavyVehicle-Klasse ergibt?

Ich verstehe es so, dass das Casting dazu dient, den Zugriff auf bestimmte Methoden eines Objekts (Klasseninstanz) zu beschränken. Daher können Sie ein Objekt nicht als spezifischere Klasse umwandeln, die über mehr Methoden als die tatsächlich zugewiesene Klasse des Objekts verfügt. Das bedeutet, dass das Objekt nur in eine Oberklasse oder in dieselbe Klasse wie die Klasse umgewandelt werden kann, von der es tatsächlich instanziiert wurde. Ist das richtig oder irre ich mich hier? Ich lerne immer noch, daher bin ich mir nicht sicher, ob dies die richtige Sichtweise ist.

Ich verstehe auch, dass dies ein Beispiel für das Downcasting sein sollte, aber ich bin nicht sicher, wie dies tatsächlich funktioniert, wenn der tatsächliche Typ nicht über die Methoden der Klasse verfügt, auf die dieses Objekt downcasted wird. Ändert das explizite Casting irgendwie den tatsächlichen Objekttyp (nicht nur den deklarierten Typ), so dass dieses Objekt keine Instanz der HeavyVehicle-Klasse mehr ist, sondern jetzt eine Instanz der Truck-Klasse wird?

31
SineLaboreNihil

Referenz vs Objekt vs Typen

Der Schlüssel besteht für mich darin, den Unterschied zwischen einem Objekt und seinen Referenzen zu verstehen oder mit anderen Worten den Unterschied zwischen einem Objekt und seinen Typen auszudrücken.

Wenn wir ein Objekt in Java erstellen, deklarieren wir dessen wahre Natur, die sich nie ändern wird. Aber jedes gegebene Objekt in Java hat wahrscheinlich mehrere Typen . Einige dieser Typen sind offensichtlich dank der Klassenhierarchie gegeben, andere sind nicht so offensichtlich ( dh Generika, Arrays).

Speziell für Referenztypen bestimmt die Klassenhierarchie die Untertypisierungsregeln. Zum Beispiel sind in Ihrem Beispiel alle LKWs schwere Fahrzeuge und alle schweren Fahrzeuge sind Fahrzeuge . Daher schreibt diese Hierarchie von is-a-Beziehungen vor, dass ein LKW mehrere kompatible Typen hat .

Wenn wir ein Truck erstellen, definieren wir eine "Referenz", um Zugriff darauf zu erhalten. Diese Referenz muss einen dieser kompatiblen Typen haben.

Truck t = new Truck(); //or
HeavyVehicle hv = new Truck(); //or
Vehicle h = new Truck() //or
Object o = new Truck();

Der entscheidende Punkt hier ist also die Erkenntnis, dass der Verweis auf das Objekt nicht das Objekt selbst ist . Die Art des Objekts, das erstellt wird, wird sich niemals ändern. Wir können jedoch verschiedene Arten kompatibler Referenzen verwenden, um Zugriff auf das Objekt zu erhalten. Dies ist eines der Merkmale des Polymorphismus hier. Auf dasselbe Objekt kann über Referenzen verschiedener "kompatibler" Typen zugegriffen werden.

Wenn wir irgendeine Art von Casting durchführen, nehmen wir einfach die Art dieser Kompatibilität zwischen verschiedenen Arten von Referenzen an.

Upcasting oder Erweitern der Referenzkonvertierung

Wenn wir nun eine Referenz vom Typ Truck haben, können wir leicht schließen, dass sie immer mit einer Referenz vom Typ Vehicle kompatibel ist, da alle LKWs Fahrzeuge sind . Daher können wir die Referenz ohne explizite Besetzung upcasten.

Truck t = new Truck();
Vehicle v = t;

Dies wird auch als Verbreiterung der Referenzkonvertierung bezeichnet, da der Typ mit zunehmender Typhierarchie allgemeiner wird.

Wenn Sie möchten, können Sie hier eine explizite Besetzung verwenden, die jedoch nicht erforderlich ist. Wir können sehen, dass das tatsächliche Objekt, auf das t und v verweisen, dasselbe ist. Es ist und bleibt ein Truck.

Downcasting oder Verengung der Referenzkonvertierung

Wenn wir nun eine Referenz vom Typ Vechicle haben, können wir nicht "sicher" schließen, dass sie tatsächlich auf eine Truck verweist. Immerhin kann es sich auch um eine andere Form von Fahrzeug handeln. Zum Beispiel

Vehicle v = new Sedan(); //a light vehicle

Wenn Sie die Referenz v irgendwo in Ihrem Code finden, ohne zu wissen, auf welches Objekt sie verweist, können Sie nicht "sicher" argumentieren, ob sie auf Truck oder auf Sedan oder auf ein anderes verweist andere Art von Fahrzeug.

Der Compiler weiß genau, dass er keine Garantie für die wahre Natur des Objekts geben kann, auf das verwiesen wird. Aber der Programmierer kann durch Lesen des Codes sicher sein, was er tut. Wie im obigen Fall können Sie deutlich erkennen, dass Vehicle v auf ein Sedan verweist.

In diesen Fällen können wir einen Downcast machen. Wir nennen es so, weil wir die Typenhierarchie durchgehen. Wir bezeichnen dies auch als Verengungsreferenzkonvertierung . Wir könnten sagen

Sedan s = (Sedan) v;

Dies erfordert immer eine explizite Besetzung, da der Compiler nicht sicher sein kann, dass dies sicher ist, und deshalb fragt er den Programmierer: "Sind Sie sicher, was Sie tun?". Wenn Sie den Compiler anlügen, erhalten Sie zur Laufzeit ein ClassCastException, wenn dieser Code ausgeführt wird.

Andere Arten von Subtyping-Regeln

Es gibt andere Regeln für die Untertypisierung in Java. Zum Beispiel gibt es auch ein Konzept namens numerische Heraufstufung, die Zahlen in Ausdrücken automatisch zwingt. Wie in

double d = 5 + 6.0;

In diesem Fall wandelt ein Ausdruck, der aus zwei verschiedenen Typen besteht, nämlich Integer und Double, die Ganzzahl vor der Auswertung des Ausdrucks in ein Double um/zwingt sie zu einem Double, was zu einem Double-Wert führt.

Sie können auch primitives Upcasting und Downcasting durchführen. Wie in

int a = 10;
double b = a; //upcasting
int c = (int) b; //downcasting

In diesen Fällen ist eine explizite Besetzung erforderlich, wenn Informationen verloren gehen können.

Einige Subtypisierungsregeln sind möglicherweise nicht so offensichtlich wie bei Arrays. Beispielsweise sind alle Referenz-Arrays Untertypen von Object[], nicht jedoch primitive Arrays.

Und bei Generika wird es noch komplizierter, besonders wenn Wildcards wie super und extends verwendet werden. Wie in

List<Integer> a = new ArrayList<>();
List<? extends Number> b = a;

List<Object> c = new ArrayList<>(); 
List<? super Number> d = c;

Wobei der Typ von b ein Subtyp des Typs von a ist. Und der Typ von d ist ein Subtyp des Typs von c.

Und auch Boxen und Unboxen unterliegen einigen Besetzungsregeln (auch dies ist meiner Meinung nach wieder eine Form von Zwang).

60
Edwin Dalorzo

Du hast es richtig. Sie können ein Objekt nur in seine Klasse, in einige übergeordnete Klassen oder in eine Schnittstelle umwandeln, die es oder seine Eltern implementieren. Wenn Sie es in einige der übergeordneten Klassen oder Schnittstellen konvertiert haben, können Sie es wieder in den ursprünglichen Typ umwandeln.

Andernfalls (während Sie es im Quellcode haben können), führt dies zu einer Laufzeit-ClassCastException.

Casting wird normalerweise verwendet, um das Speichern verschiedener Elemente (derselben Schnittstelle oder Elternklasse, z. B. aller Autos) in demselben Feld oder einer Sammlung desselben Typs (z. B. Fahrzeug) zu ermöglichen, sodass Sie damit arbeiten können sie auf die gleiche Weise.

Wenn Sie dann den vollständigen Zugriff erhalten möchten, können Sie sie zurückwerfen (z. B. Vehicle to Truck). 


Im Beispiel bin ich ziemlich sicher, dass die letzte Aussage ungültig ist und der Kommentar einfach falsch ist.

5
MightyPork

Die letzte Codezeile wird kompiliert und ohne Ausnahmen erfolgreich ausgeführt. Was es tut, ist absolut legal.

  1. hV bezieht sich anfänglich auf ein Objekt vom Typ HeavyVehicle (nennen wir dieses Objekt h1):

    static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1.
    
  2. Später machen wir hV auf ein anderes Objekt vom Typ Truck (nennen wir dieses Objekt t1):

    hV = T; // hV now refers to t1.
    
  3. Zum Schluss machen wir T auf t1.

    T = (Truck) hV; // T now refers to t1.
    

T hat sich bereits auf t1 bezogen, also hat diese Aussage nichts geändert.

Wenn hv bereits eine Instanz einer HeavyVehicle-Klasse zugewiesen wurde, die eine Superklasse der Truck-Klasse ist, wie kann dann dieses Feld in eine spezifischere Unterklasse namens Truck umgewandelt werden, die sich aus der HeavyVehicle-Klasse ergibt?

Bei Erreichen der letzten Zeile bezieht sich hV nicht mehr auf eine Instanz von HeavyVehicle. Es bezieht sich auf eine Instanz von Truck. Das Umwandeln einer Instanz von Truck in Type Truck ist kein Problem.

Das bedeutet, dass das Objekt nur in eine Oberklasse oder in dieselbe Klasse wie die Klasse umgewandelt werden kann, von der es tatsächlich instanziiert wurde. Ist das richtig oder irre ich mich hier?

Grundsätzlich ja, aber verwechseln Sie das Objekt selbst nicht mit einer Variablen, die auf das Objekt verweist. Siehe unten.

Ändert das explizite Casting irgendwie den tatsächlichen Objekttyp (nicht nur den deklarierten Typ), so dass dieses Objekt keine Instanz der HeavyVehicle-Klasse mehr ist, sondern jetzt eine Instanz der Truck-Klasse wird?

Ein einmal erstelltes Objekt kann seinen Typ niemals ändern. Es kann keine Instanz einer anderen Klasse werden.

In der letzten Zeile hat sich nichts geändert. T bezog sich vor dieser Zeile auf t1 und danach auf t1.

Warum ist also der explizite Cast (Truck) in der letzten Zeile notwendig? Wir helfen im Grunde nur beim Compiler.

Wir wissen, dass sich hV zu diesem Zeitpunkt auf ein Objekt vom Typ Truck bezieht, es ist also in Ordnung, dieses Objekt vom Typ Truck der Variablen T zuzuweisen. Aber der Compiler ist nicht klug genug, das zu wissen . Der Compiler möchte, dass er sicher ist, dass er eine Instanz von Truck findet, wenn er zu dieser Zeile kommt und versucht, die Zuweisung vorzunehmen.

2
dshiga

Wenn Sie einen Cast von einem Truck-Objekt zu einem HeavyVehicle wie folgt machen: 

Truck truck = new Truck()
HeavyVehicle hv = truck;

Das Objekt ist immer noch ein LKW, aber Sie haben nur Zugriff auf die Methoden und Felder von heavyVehicle, die die HeavyVehicle-Referenz verwenden. Wenn Sie erneut auf einen LKW herunterfahren, können Sie alle Methoden und Felder des LKW erneut verwenden.

Truck truck = new Truck()
HeavyVehicle hv = truck;
Truck anotherTruckReference = (Truck) hv; // Explicit Cast is needed here

Wenn das eigentliche Objekt, das Sie abwerfen, kein Truck ist, wird eine ClassCastException wie im folgenden Beispiel ausgelöst:

HeavyVehicle hv = new HeavyVehicle();
Truck tr = (Truck) hv;  // This code compiles but will throw a ClasscastException

Die Ausnahme wird ausgelöst, weil das tatsächliche Objekt nicht der richtigen Klasse angehört, sondern ein Objekt einer Superklasse ist (HeavyVehicle).

2
David SN

Der obige Code wird kompiliert und funktioniert einwandfrei. Ändern Sie nun den obigen Code und fügen Sie die folgende Zeile hinzu System.out.println (T.name);

Dadurch wird sichergestellt, dass Sie das Objekt T nach dem Downcasting des hV-Objekts nicht als Truck verwenden.

Derzeit verwenden Sie in Ihrem Code nach dem Downcast nicht T, sodass alles in Ordnung ist und funktioniert. 

Dies liegt daran, dass sich Complier durch die explizite Verwendung von hV als Truck beschwert, dass der Programmierer das Objekt als gegossenes Objekt betrachtet und sich dessen bewusst ist, welches Objekt in was gegossen wurde.

Zur Laufzeit kann JVM das Casting jedoch nicht rechtfertigen und wirft ClassCastException "HeavyVehicle kann nicht in Truck umgewandelt werden".

1

Um die oben angeführten Punkte besser veranschaulichen zu können, habe ich den betreffenden Code geändert und mit Inline-Kommentaren (einschließlich der tatsächlichen Ausgaben) weitere Codes hinzugefügt: 

class Vehicle {

        String name;
        Vehicle() {
                name = "Vehicle";
        }
}

class HeavyVehicle extends Vehicle {

        HeavyVehicle() {
                name = "HeavyVehicle";
        }
}

class Truck extends HeavyVehicle {

        Truck() {
                name = "Truck";
        }
}

class LightVehicle extends Vehicle {

        LightVehicle() {
                name = "LightVehicle";
        }
}

public class InstanceOfExample {

        static boolean result;
        static HeavyVehicle hV = new HeavyVehicle();
        static Truck T = new Truck();
        static HeavyVehicle hv2 = null;
        public static void main(String[] args) {

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true

                result = T instanceof HeavyVehicle;
                System.out.print("T is a HeavyVehicle: " + result + "\n"); // true
//      But the following is in error.              
//      T = hV; // error - HeavyVehicle cannot be converted to Truck because all hV's are not trucks.                               

                result = hV instanceof Truck;
                System.out.print("hV is a Truck: " + result + "\n"); // false               

                hV = T; // Sucessful Cast form child to parent.
                result = hV instanceof Truck; // This only means that hV now points to a Truck object.                            
                System.out.print("hV is a Truck: " + result + "\n");    // true         

                T = (Truck) hV; // Sucessful Explicit Cast form parent to child. Now T points to both HeavyVehicle and Truck. 
                                // And also hV points to both Truck and HeavyVehicle. Check the following codes and results.
                result = hV instanceof Truck;                             
                System.out.print("hV is a Truck: " + result + "\n");    // true 

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true             

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true 

                result = hv2 instanceof HeavyVehicle;               
                System.out.print("hv2 is a HeavyVehicle: " + result + "\n"); // false

        }

}
0
S. W. Chi