Casting del tipo di oggetto in Java

1. Panoramica

Il sistema di tipi Java è costituito da due tipi di tipi: primitivi e riferimenti.

Abbiamo trattato le conversioni primitive in questo articolo e ci concentreremo sul casting dei riferimenti qui, per ottenere una buona comprensione di come Java gestisce i tipi.

2. Primitivo vs. riferimento

Sebbene le conversioni primitive e il casting delle variabili di riferimento possano sembrare simili, sono concetti abbastanza diversi.

In entrambi i casi, stiamo "trasformando" un tipo in un altro. Ma, in modo semplificato, una variabile primitiva contiene il suo valore e la conversione di una variabile primitiva significa cambiamenti irreversibili nel suo valore:

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

Dopo la conversione nell'esempio precedente, la variabile myInt è 1 e non possiamo ripristinare il valore precedente 1.1 da essa.

Le variabili di riferimento sono diverse ; la variabile di riferimento si riferisce solo a un oggetto ma non contiene l'oggetto stesso.

E lanciare una variabile di riferimento non tocca l'oggetto a cui si riferisce, ma etichetta questo oggetto in un altro modo, espandendo o restringendo le opportunità di lavorare con esso. L'upcast restringe l'elenco di metodi e proprietà disponibili per questo oggetto e il downcast può estenderlo.

Un riferimento è come un telecomando a un oggetto. Il telecomando ha più o meno pulsanti a seconda del tipo e l'oggetto stesso è memorizzato in un mucchio. Quando eseguiamo il casting, cambiamo il tipo di telecomando ma non cambiamo l'oggetto stesso.

3. Upcasting

Il cast da una sottoclasse a una superclasse è chiamato upcasting . In genere, l'upcasting viene eseguito implicitamente dal compilatore.

L'upcasting è strettamente correlato all'ereditarietà, un altro concetto fondamentale in Java. È comune utilizzare variabili di riferimento per fare riferimento a un tipo più specifico. E ogni volta che lo facciamo, avviene l'upcasting implicito.

Per dimostrare l'upcasting definiamo una classe Animal :

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

Ora estendiamo Animal :

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

Ora possiamo creare un oggetto di classe Cat e assegnarlo alla variabile di riferimento di tipo Cat :

Cat cat = new Cat();

E possiamo anche assegnarlo alla variabile di riferimento di tipo Animal :

Animal animal = cat;

Nell'assegnazione di cui sopra, ha luogo l'upcasting implicito. Potremmo farlo esplicitamente:

animal = (Animal) cat;

Ma non è necessario eseguire il cast esplicito dell'albero dell'ereditarietà. Il compilatore sa che il gatto è un animale e non visualizza alcun errore.

Nota, quel riferimento può fare riferimento a qualsiasi sottotipo del tipo dichiarato.

Utilizzando l'upcasting, abbiamo limitato il numero di metodi disponibili per l' istanza Cat ma non abbiamo modificato l'istanza stessa. Ora non possiamo fare nulla di specifico per Cat - non possiamo invocare meow () sulla variabile animale .

Sebbene l' oggetto Cat rimanga oggetto Cat , la chiamata di meow () causerebbe l'errore del compilatore:

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

Per invocare meow () dobbiamo abbattere l' animale , e lo faremo più tardi.

Ma ora descriveremo cosa ci dà l'upcasting. Grazie all'upcasting, possiamo sfruttare il polimorfismo.

3.1. Polimorfismo

Definiamo un'altra sottoclasse di Animal , una classe Dog :

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

Ora possiamo definire il metodo feed () che tratta tutti i cani e gatti come animali :

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

Non vogliamo che AnimalFeeder si preoccupi di quale animale è sulla lista: un gatto o un cane . Nel metodo feed () sono tutti animali .

Implicit upcasting occurs when we add objects of a specific type to the animals list:

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

We add cats and dogs and they are upcast to Animal type implicitly. Each Cat is an Animal and each Dog is an Animal. They're polymorphic.

By the way, all Java objects are polymorphic because each object is an Object at least. We can assign an instance of Animal to the reference variable of Object type and the compiler won’t complain:

Object object = new Animal();

That’s why all Java objects we create already have Object specific methods, for example, toString().

Upcasting to an interface is also common.

Possiamo creare l' interfaccia Mew e farla implementare da Cat :

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

Ora qualsiasi oggetto Cat può anche essere trasmesso a Mew :

Mew mew = new Cat();

Cat è un Mew , l'upcasting è legale e viene fatto implicitamente.

Quindi, il gatto è un Mew , un animale , un oggetto e un gatto . Può essere assegnato a variabili di riferimento di tutti e quattro i tipi nel nostro esempio.

3.2. Overriding

Nell'esempio sopra, il metodo eat () è sovrascritto. Ciò significa che sebbene eat () venga chiamato sulla variabile del tipo Animal , il lavoro viene svolto mediante metodi invocati su oggetti reali - cani e gatti:

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 questo tutorial fondamentale, abbiamo esplorato cos'è l'upcasting, il downcasting, come usarli e come questi concetti possono aiutarti a trarre vantaggio dal polimorfismo.

Come sempre, il codice per questo articolo è disponibile su GitHub.