Objekttyp-Casting in Java

Objekttyp-Casting in Java

1. Überblick

Das Java-Typensystem besteht aus zwei Arten von Typen: Primitiven und Referenzen.

Wir haben primitive Konvertierungen inthis article behandelt und konzentrieren uns hier auf das Casting von Referenzen, um ein gutes Verständnis dafür zu erhalten, wie Java mit Typen umgeht.

Weitere Lektüre:

Die Grundlagen von Java Generics

Eine kurze Einführung in die Grundlagen von Java Generics.

Read more

Java-Instanz des Operators

Erfahren Sie mehr über den instanceof-Operator in Java

Read more

2. Primitive vs. Referenz

Obwohl primitive Konvertierungen und Referenzvariablen-Casting ähnlich aussehen können, sind sie ziemlichdifferent concepts.

In beiden Fällen "verwandeln" wir einen Typ in einen anderen. Vereinfacht ausgedrückt enthält eine primitive Variable ihren Wert, und die Konvertierung einer primitiven Variablen bedeutet irreversible Änderungen ihres Werts:

double myDouble = 1.1;
int myInt = (int) myDouble;

assertNotEquals(myDouble, myInt);

Nach der Konvertierung im obigen Beispiel ist die VariablemyInt1, und der vorherige Wert1.1 kann nicht wiederhergestellt werden.

Reference variables are different; Die Referenzvariable bezieht sich nur auf ein Objekt, enthält jedoch nicht das Objekt selbst.

Das Umwandeln einer Referenzvariablen berührt nicht das Objekt, auf das sie verweist, sondern kennzeichnet dieses Objekt nur auf eine andere Art und Weise und erweitert oder schränkt die Möglichkeiten, damit zu arbeiten. Upcasting narrows the list of methods and properties available to this object, and downcasting can extend it.

Eine Referenz ist wie eine Fernbedienung für ein Objekt. Die Fernbedienung verfügt je nach Typ über mehr oder weniger Tasten, und das Objekt selbst wird auf einem Heap gespeichert. Beim Casting ändern wir den Typ der Fernbedienung, aber nicht das Objekt selbst.

3. Upcasting

Casting from a subclass to a superclass is called upcasting. Normalerweise wird das Upcasting implizit vom Compiler ausgeführt.

Upcasting ist eng mit Vererbung verbunden - ein weiteres Kernkonzept in Java. Es ist üblich, Referenzvariablen zu verwenden, um auf einen spezifischeren Typ zu verweisen. Und jedes Mal, wenn wir dies tun, findet implizites Upcasting statt.

Um das Upcasting zu demonstrieren, definieren wir eineAnimal-Klasse:

public class Animal {

    public void eat() {
        // ...
    }
}

Erweitern wir nunAnimal:

public class Cat extends Animal {

    public void eat() {
         // ...
    }

    public void meow() {
         // ...
    }
}

Jetzt können wir ein Objekt der KlasseCaterstellen und es der Referenzvariablen vom TypCat zuweisen:

Cat cat = new Cat();

Und wir können es auch der Referenzvariablen vom TypAnimal zuordnen:

Animal animal = cat;

In der obigen Zuordnung findet implizites Upcasting statt. Wir könnten es explizit tun:

animal = (Animal) cat;

Es ist jedoch nicht erforderlich, den Vererbungsbaum explizit zu erstellen. Der Compiler weiß, dasscat einAnimal ist und zeigt keine Fehler an.

Beachten Sie, dass sich diese Referenz auf jeden Subtyp des deklarierten Typs beziehen kann.

Mit Upcasting haben wir die Anzahl der verfügbaren Methoden auf die Instanz vonCatbeschränkt, die Instanz selbst jedoch nicht geändert. Jetzt können wir nichts tun, was spezifisch fürCat – ist. Wir könnenmeow() nicht für die Variableanimal aufrufen.

Obwohl das Objekt vonCatdas Objekt vonCatbleibt, würde der Aufruf vonmeow() den Compilerfehler verursachen:

// animal.meow(); The method meow() is undefined for the type Animal

Ummeow() aufzurufen, müssen wiranimal herunterwerfen, und wir werden dies später tun.

Aber jetzt werden wir beschreiben, was uns das Upcasting gibt. Dank des Umformens können wir den Polymorphismus ausnutzen.

3.1. Polymorphismus

Definieren wir eine weitere Unterklasse vonAnimal, eineDog-Klasse:

public class Dog extends Animal {

    public void eat() {
         // ...
    }
}

Jetzt können wir diefeed()-Methode definieren, die alle Katzen und Hunde wieanimals behandelt:

public class AnimalFeeder {

    public void feed(List animals) {
        animals.forEach(animal -> {
            animal.eat();
        });
    }
}

Wir möchten nicht, dassAnimalFeeder sich darum kümmern, welcheanimal auf der Liste stehen - aCat oderDog. Bei derfeed()-Methode sind sie alleanimals.

Implizites Upcasting tritt auf, wenn wir der ListeanimalsObjekte eines bestimmten Typs hinzufügen:

List animals = new ArrayList<>();
animals.add(new Cat());
animals.add(new Dog());
new AnimalFeeder().feed(animals);

Wir fügen Katzen und Hunde hinzu und sie sind implizit auf den TypAnimaleingestellt. JedesCat ist einAnimal und jedesDog ist einAnimal. Sie sind polymorph.

Übrigens sind alle Java-Objekte polymorph, da jedes Objekt mindestensObject beträgt. Wir können der Referenzvariablen vom TypObject eine Instanz vonAnimal zuweisen, und der Compiler wird sich nicht beschweren:

Object object = new Animal();

Aus diesem Grund verfügen alle von uns erstellten Java-Objekte bereits überObject-spezifische Methoden, z. B.toString().

Das Upcasting auf eine Schnittstelle ist ebenfalls üblich.

Wir können die Schnittstelle vonMewerstellen undCatimplementieren lassen:

public interface Mew {
    public void meow();
}

public class Cat extends Animal implements Mew {

    public void eat() {
         // ...
    }

    public void meow() {
         // ...
    }
}

Jetzt kann jedesCat-Objekt auch aufMew hochgeladen werden:

Mew mew = new Cat();

Cat ist einMew, Upcasting ist legal und wird implizit durchgeführt.

Somit istCat einMew,Animal,Object undCat. Sie kann in unserem Beispiel Referenzvariablen aller vier Typen zugewiesen werden.

3.2. Überschreiben

Im obigen Beispiel wird die Methodeeat() überschrieben. Dies bedeutet, dass obwohleat() für die Variable vom TypAnimal aufgerufen wird, die Arbeit mit Methoden ausgeführt wird, die für reale Objekte aufgerufen werden - Katzen und Hunde:

public void feed(List animals) {
    animals.forEach(animal -> {
        animal.eat();
    });
}

Wenn wir unseren Klassen eine Protokollierung hinzufügen, werden wir sehen, dass die Methoden vonCat undDog aufgerufen werden:

web - 2018-02-15 22:48:49,354 [main] INFO com.example.casting.Cat - cat is eating
web - 2018-02-15 22:48:49,363 [main] INFO com.example.casting.Dog - dog is eating

Um zusammenzufassen:

  • Eine Referenzvariable kann auf ein Objekt verweisen, wenn das Objekt vom selben Typ wie eine Variable ist oder wenn es ein Untertyp ist

  • Upcasting geschieht implizit

  • Alle Java-Objekte sind polymorph und können aufgrund von Upcasting als Supertype-Objekte behandelt werden

4. Downcasting

Was ist, wenn wir die Variable vom TypAnimal verwenden möchten, um eine Methode aufzurufen, die nur für die KlasseCatverfügbar ist? Hier kommt der Wermutstropfen. It’s the casting from a superclass to a subclass.

Nehmen wir ein Beispiel:

Animal animal = new Cat();

Wir wissen, dass sich die Variableanimal auf die Instanz vonCat bezieht. Und wir möchten dieCatmeow()-Methode füranimal aufrufen. Der Compiler beschwert sich jedoch, dass die Methodemeow()für den TypAnimal nicht existiert.

Ummeow() aufzurufen, sollten wiranimal aufCat herabsetzen:

((Cat) animal).meow();

Die inneren Klammern und der darin enthaltene Typ werden manchmal als Umwandlungsoperator bezeichnet. Beachten Sie, dass zum Kompilieren des Codes auch externe Klammern erforderlich sind.

Schreiben wir das vorherige Beispiel vonAnimalFeedermit der Methode vonmeow()neu:

public class AnimalFeeder {

    public void feed(List animals) {
        animals.forEach(animal -> {
            animal.eat();
            if (animal instanceof Cat) {
                ((Cat) animal).meow();
            }
        });
    }
}

Jetzt erhalten wir Zugriff auf alle Methoden, die für die KlasseCatverfügbar sind. Sehen Sie sich das Protokoll an, um sicherzustellen, dassmeow() tatsächlich aufgerufen wird:

web - 2018-02-16 18:13:45,445 [main] INFO com.example.casting.Cat - cat is eating
web - 2018-02-16 18:13:45,454 [main] INFO com.example.casting.Cat - meow
web - 2018-02-16 18:13:45,455 [main] INFO com.example.casting.Dog - dog is eating

Beachten Sie, dass wir im obigen Beispiel versuchen, nur die Objekte herunterzuspielen, die tatsächlich Instanzen vonCat sind. Dazu verwenden wir den Operatorinstanceof.

4.1. instanceof Operator

Wir verwenden häufig den Operatorinstanceofvor dem Downcasting, um zu überprüfen, ob das Objekt zu dem bestimmten Typ gehört:

if (animal instanceof Cat) {
    ((Cat) animal).meow();
}

4.2. ClassCastException

Wenn wir den Typ nicht mit dem Operatorinstanceof überprüft hätten, hätte sich der Compiler nicht beschwert. Aber zur Laufzeit würde es eine Ausnahme geben.

Um dies zu demonstrieren, entfernen wir den Operatorinstanceofaus dem obigen Code:

public void uncheckedFeed(List animals) {
    animals.forEach(animal -> {
        animal.eat();
        ((Cat) animal).meow();
    });
}

Dieser Code wird ohne Probleme kompiliert. Wenn wir jedoch versuchen, es auszuführen, sehen wir eine Ausnahme:

java.lang.ClassCastException: com.example.casting.Dog kann nicht incom.example.casting.Cat umgewandelt werden

Dies bedeutet, dass wir versuchen, ein Objekt, das eine Instanz vonDog ist, in eine Instanz vonCat zu konvertieren.

ClassCastException's wird immer zur Laufzeit ausgelöst, wenn der Typ, auf den wir downcasten, nicht mit dem Typ des realen Objekts übereinstimmt.

Beachten Sie, dass der Compiler dies nicht zulässt, wenn wir versuchen, auf einen nicht verwandten Typ herunterzuspielen:

Animal animal;
String s = (String) animal;

Der Compiler sagt "Kann nicht von Animal zu String umgewandelt werden".

Damit der Code kompiliert werden kann, müssen sich beide Typen in derselben Vererbungsstruktur befinden.

Fassen wir zusammen:

  • Downcasting ist erforderlich, um Zugang zu Mitgliedern zu erhalten, die für die Unterklasse spezifisch sind

  • Das Downcasting erfolgt mit dem Cast-Operator

  • Um ein Objekt sicher herunterzuschieben, benötigen wir den Operatorinstanceof

  • Wenn das reale Objekt nicht mit dem Typ übereinstimmt, auf den wir heruntergestuft haben, wirdClassCastException zur Laufzeit ausgelöst

5. Cast() Methode

Es gibt eine andere Möglichkeit, Objekte mit den Methoden vonClass umzuwandeln:

public void whenDowncastToCatWithCastMethod_thenMeowIsCalled() {
    Animal animal = new Cat();
    if (Cat.class.isInstance(animal)) {
        Cat cat = Cat.class.cast(animal);
        cat.meow();
    }
}

Im obigen Beispiel werden die Methodencast() undisInstance() anstelle der Operatoren cast undinstanceof entsprechend verwendet.

Es ist üblich,cast()- undisInstance()-Methoden mit generischen Typen zu verwenden.

Erstellen wir die KlasseAnimalFeederGeneric<T>mit der Methodefeed(), die nur einen Tiertyp "füttert" - Katzen oder Hunde, abhängig vom Wert des Typparameters:

public class AnimalFeederGeneric {
    private Class type;

    public AnimalFeederGeneric(Class type) {
        this.type = type;
    }

    public List feed(List animals) {
        List list = new ArrayList();
        animals.forEach(animal -> {
            if (type.isInstance(animal)) {
                T objAsType = type.cast(animal);
                list.add(objAsType);
            }
        });
        return list;
    }

}

Diefeed()-Methode überprüft jedes Tier und gibt nur diejenigen zurück, bei denen es sich um Instanzen vonT handelt.

Beachten Sie, dass die Instanz vonClassauch an die generische Klasse übergeben werden sollte, da sie nicht vom TypparameterT abgerufen werden kann. In unserem Beispiel übergeben wir es im Konstruktor.

Machen wirT gleichCat und stellen Sie sicher, dass die Methode nur Katzen zurückgibt:

@Test
public void whenParameterCat_thenOnlyCatsFed() {
    List animals = new ArrayList<>();
    animals.add(new Cat());
    animals.add(new Dog());
    AnimalFeederGeneric catFeeder
      = new AnimalFeederGeneric(Cat.class);
    List fedAnimals = catFeeder.feed(animals);

    assertTrue(fedAnimals.size() == 1);
    assertTrue(fedAnimals.get(0) instanceof Cat);
}

6. Fazit

In diesem grundlegenden Tutorial haben wir untersucht, was Upcasting, Downcasting, wie man sie verwendet und wie diese Konzepte Ihnen helfen können, den Polymorphismus zu nutzen.

Wie immer ist der Code für diesen Artikelover on GitHub verfügbar.