Objekttyp Casting in Java

1. Übersicht

Das Java-Typsystem besteht aus zwei Arten von Typen: Grundelementen und Referenzen.

In diesem Artikel haben wir uns mit primitiven Konvertierungen befasst. Wir konzentrieren uns hier auf das Casting von Referenzen, um ein gutes Verständnis dafür zu erhalten, wie Java mit Typen umgeht.

2. Primitiv vs. Referenz

Obwohl primitive Konvertierungen und das Umwandeln von Referenzvariablen ähnlich aussehen, handelt es sich um ganz andere Konzepte.

In beiden Fällen „verwandeln“ wir einen Typ in einen anderen. Auf vereinfachte Weise enthält eine primitive Variable jedoch 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 Variable myInt 1 , und der vorherige Wert 1.1 kann nicht wiederhergestellt werden .

Referenzvariablen sind unterschiedlich ; 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 beschriftet dieses Objekt nur auf andere Weise, wodurch die Möglichkeiten zur Arbeit damit erweitert oder eingeschränkt werden. Upcasting schränkt die Liste der Methoden und Eigenschaften ein, die für dieses Objekt verfügbar sind, und Downcasting kann sie erweitern.

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 Haufen gespeichert. Beim Casting ändern wir den Typ der Fernbedienung, aber nicht das Objekt selbst.

3. Upcasting

Das Umwandeln von einer Unterklasse in eine Oberklasse wird als Upcasting bezeichnet . In der Regel wird das Upcasting implizit vom Compiler ausgeführt.

Upcasting ist eng mit der 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 zu demonstrieren , Upcasting sich eine Definition Tierklasse:

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

Lassen Sie uns nun Animal erweitern :

public class Cat extends Animal { public void eat() { // ... } public void meow() { // ... } }

Jetzt können wir ein Objekt der Cat- Klasse erstellen und es der Referenzvariablen vom Typ Cat zuweisen :

Cat cat = new Cat();

Und wir können es auch der Referenzvariablen vom Typ Animal 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 aufzurufen. Der Compiler weiß, dass cat ein Tier 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 für die Cat- Instanz verfügbaren Methoden beschränkt, die Instanz selbst jedoch nicht geändert. Jetzt können wir nichts tun , was spezifisch ist Katze - wir können nicht invoke miauen () auf der Tier variabel.

Obwohl Cat Objekt bleibt Cat Objekt, ruft Meow () würde den Compiler - Fehler verursachen:

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

Um meow () aufzurufen, müssen wir das Tier niederwerfen , und das werden wir später tun.

Aber jetzt werden wir beschreiben, was uns die Ausstrahlung gibt. Dank Upcasting können wir den Polymorphismus nutzen.

3.1. Polymorphismus

Definieren wir eine andere Unterklasse von Animal , eine Hundeklasse :

public class Dog extends Animal { public void eat() { // ... } }

Jetzt können wir die feed () -Methode definieren, die alle Katzen und Hunde wie Tiere behandelt :

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

Wir möchten nicht, dass AnimalFeeder sich darum kümmert, welches Tier auf der Liste steht - eine Katze oder ein Hund . Bei der feed () -Methode sind sie alle Tiere .

Implizite Upcasting tritt auf, wenn wir Objekte eines bestimmten Typs der hinzufügen Tiere Liste:

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 Tierarten eingestellt . Jede Katze ist ein Tier und jeder Hund ist ein Tier . Sie sind polymorph.

Übrigens sind alle Java-Objekte polymorph, da jedes Objekt mindestens ein Objekt ist . Wir können der Referenzvariablen des Objekttyps eine Instanz von Animal zuweisen, und der Compiler wird sich nicht beschweren:

Object object = new Animal();

Deshalb werden alle Java - Objekte schaffen wir bereits haben Object spezifische Methoden, beispielsweise toString () .

Upcasting auf eine Schnittstelle ist ebenfalls üblich.

Wir können eine Mew- Schnittstelle erstellen und Cat dazu bringen , sie zu implementieren:

public interface Mew { public void meow(); } public class Cat extends Animal implements Mew { public void eat() { // ... } public void meow() { // ... } }

Jetzt kann jedes Cat- Objekt auch auf Mew übertragen werden :

Mew mew = new Cat();

Cat ist ein Mew , Upcasting ist legal und implizit getan.

Somit ist Katze ein Miau , Tier , Objekt und Katze . In unserem Beispiel kann es Referenzvariablen aller vier Typen zugewiesen werden.

3.2. Überschreiben

Im obigen Beispiel wird die eat () -Methode überschrieben. Dies bedeutet, dass, obwohl eat () für die Variable des Tiertyps 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(); }); }

If we add some logging to our classes, we'll see that Cat’s and Dog’s methods are called:

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

To sum up:

  • A reference variable can refer to an object if the object is of the same type as a variable or if it is a subtype
  • Upcasting happens implicitly
  • All Java objects are polymorphic and can be treated as objects of supertype due to upcasting

4. Downcasting

What if we want to use the variable of type Animal to invoke a method available only to Cat class? Here comes the downcasting. It’s the casting from a superclass to a subclass.

Let’s take an example:

Animal animal = new Cat();

We know that animal variable refers to the instance of Cat. And we want to invoke Cat’s meow() method on the animal. But the compiler complains that meow() method doesn’t exist for the type Animal.

To call meow() we should downcast animal to Cat:

((Cat) animal).meow();

The inner parentheses and the type they contain are sometimes called the cast operator. Note that external parentheses are also needed to compile the code.

Let’s rewrite the previous AnimalFeeder example with meow() method:

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

Now we gain access to all methods available to Cat class. Look at the log to make sure that meow() is actually called:

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

Note that in the above example we're trying to downcast only those objects which are really instances of Cat. To do this, we use the operator instanceof.

4.1. instanceof Operator

We often use instanceof operator before downcasting to check if the object belongs to the specific type:

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

4.2. ClassCastException

If we hadn't checked the type with the instanceof operator, the compiler wouldn't have complained. But at runtime, there would be an exception.

To demonstrate this let’s remove the instanceof operator from the above code:

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

This code compiles without issues. But if we try to run it we’ll see an exception:

java.lang.ClassCastException: com.baeldung.casting.Dog cannot be cast to com.baeldung.casting.Cat

This means that we are trying to convert an object which is an instance of Dog into a Cat instance.

ClassCastException's always thrown at runtime if the type we downcast to doesn't match the type of the real object.

Note, that if we try to downcast to an unrelated type, the compiler won't allow this:

Animal animal; String s = (String) animal;

The compiler says “Cannot cast from Animal to String”.

For the code to compile, both types should be in the same inheritance tree.

Let's sum up:

  • Downcasting is necessary to gain access to members specific to subclass
  • Downcasting is done using cast operator
  • To downcast an object safely, we need instanceof operator
  • If the real object doesn't match the type we downcast to, then ClassCastException will be thrown at runtime

5. cast() Method

There's another way to cast objects using the methods of Class:

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

In the above example, cast() and isInstance() methods are used instead of cast and instanceof operators correspondingly.

It's common to use cast() and isInstance() methods with generic types.

Let's create AnimalFeederGeneric class with feed() method which “feeds” only one type of animals – cats or dogs, depending on the value of the type parameter:

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

The feed() method checks each animal and returns only those which are instances of T.

Note, that the Class instance should also be passed to the generic class as we can't get it from the type parameter T. In our example, we pass it in the constructor.

Let's make T equal to Cat and make sure that the method returns only cats:

@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. Conclusion

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 Artikel auf GitHub verfügbar.