Tipul de obiect Casting în Java

1. Prezentare generală

Sistemul de tip Java este alcătuit din două tipuri de tipuri: primitive și referințe.

Am tratat conversiile primitive în acest articol și ne vom concentra pe distribuirea referințelor aici, pentru a înțelege bine cum tratează Java tipurile.

2. Primitiv vs. Referință

Deși conversiile primitive și distribuirea variabilelor de referință pot arăta similare, acestea sunt concepte destul de diferite.

În ambele cazuri, „transformăm” un tip în altul. Dar, într-un mod simplificat, o variabilă primitivă conține valoarea ei, iar conversia unei variabile primitive înseamnă schimbări ireversibile ale valorii sale:

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

După conversia din exemplul de mai sus, variabila myInt este 1 și nu putem restabili valoarea anterioară 1.1 din aceasta.

Variabilele de referință sunt diferite ; variabila de referință se referă doar la un obiect, dar nu conține obiectul în sine.

Iar distribuirea unei variabile de referință nu atinge obiectul la care se referă, ci doar etichetează acest obiect într-un alt mod, extinzând sau restrângând oportunitățile de a lucra cu acesta. Upcasting restrânge lista metodelor și proprietăților disponibile acestui obiect, iar downcasting-ul îl poate extinde.

O referință este ca o telecomandă la un obiect. Telecomanda are mai multe sau mai puține butoane în funcție de tipul său, iar obiectul în sine este stocat într-o grămadă. Când facem casting, schimbăm tipul telecomenzii, dar nu schimbăm obiectul în sine.

3. Upcasting

Distribuirea dintr-o subclasă într-o superclasă se numește ascendentă . De obicei, ascendența este implicit realizată de compilator.

Upcasting-ul este strâns legat de moștenire - un alt concept de bază în Java. Este obișnuit să folosiți variabile de referință pentru a vă referi la un tip mai specific. Și de fiecare dată când facem acest lucru, are loc o ascensiune implicită.

Pentru a demonstra ascultarea, să definim o clasă Animal :

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

Acum, să extindem Animal :

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

Acum putem crea un obiect din clasa Cat și îl putem atribui variabilei de referință de tip Cat :

Cat cat = new Cat();

Și o putem atribui și variabilei de referință de tip Animal :

Animal animal = cat;

În misiunea de mai sus, are loc ridicarea implicită. Am putea face acest lucru în mod explicit:

animal = (Animal) cat;

Dar nu este nevoie să aruncați în mod explicit arborele moștenirii. Compilatorul știe că pisica este un animal și nu afișează erori.

Rețineți că această referință se poate referi la orice subtip al tipului declarat.

Folosind upcasting, am restricționat numărul de metode disponibile instanței Cat, dar nu am schimbat instanța în sine. Acum , noi nu putem face nimic care este specific pentru Cat - nu putem invoca miau () pe animale variabila.

Deși obiectul Cat rămâne obiectul Cat , apelarea meow () ar cauza eroarea compilatorului:

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

Pentru a invoca meow () , trebuie să dărâmăm animalul și vom face acest lucru mai târziu.

Dar acum vom descrie ceea ce ne oferă revoltă. Datorită ascensiunii, putem profita de polimorfism.

3.1. Polimorfism

Să definim o altă subclasă de animale , o clasă de câine :

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

Acum putem defini metoda feed () care tratează toate pisicile și câinii ca animale :

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

Nu vrem ca AnimalFeeder să aibă grijă de animalul de pe listă - o pisică sau un câine . În metoda feed () , toți sunt animale .

Creșterea implicită apare atunci când adăugăm obiecte de un anumit tip în lista de animale :

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

Adăugăm pisici și câini și sunt supuși implicit la tipul Animal . Fiecare pisică este un animal și fiecare câine este un animal . Sunt polimorfe.

Apropo, toate obiectele Java sunt polimorfe, deoarece fiecare obiect este cel puțin un obiect . Putem atribui o instanță de Animal variabilei de referință a tipului de obiect și compilatorul nu se va plânge:

Object object = new Animal();

De aceea, toate obiectele Java pe care le creăm au ​​deja metode specifice obiectelor , de exemplu, toString () .

Ascultarea către o interfață este, de asemenea, obișnuită.

Putem crea interfața Mew și o putem face pe Cat să o implementeze:

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

Acum, orice obiect Cat poate fi, de asemenea, ascuns la Mew :

Mew mew = new Cat();

Cat este un Mew , ridicarea este legală și se face implicit.

Astfel, Pisica este Mew , Animal , Obiect și Pisică . Poate fi atribuit variabilelor de referință de toate cele patru tipuri din exemplul nostru.

3.2. Supranumit

În exemplul de mai sus, metoda eat () este anulată. Aceasta înseamnă că, deși eat () este apelată la variabila de tip Animal , munca se face prin metode invocate pe obiecte reale - pisici și câini:

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

Dacă adăugăm câteva înregistrări la cursurile noastre, vom vedea că metodele Cat și Dog sunt numite:

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 

În concluzie:

  • O variabilă de referință se poate referi la un obiect dacă obiectul este de același tip ca o variabilă sau dacă este un subtip
  • Upcasting-ul se întâmplă implicit
  • Toate obiectele Java sunt polimorfe și pot fi tratate ca obiecte de supertip datorită ascensiunii

4. Downcasting

Ce se întâmplă dacă vrem să folosim variabila de tip Animal pentru a invoca o metodă disponibilă doar pentru clasa Cat ? Aici vine declinul. Este castingul dintr-o superclasă într-o subclasă.

Să luăm un exemplu:

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

În acest tutorial fundamental, am explorat ce este ascultarea, descreșterea, cum să le folosiți și cum aceste concepte vă pot ajuta să profitați de polimorfism.

Ca întotdeauna, codul pentru acest articol este disponibil pe GitHub.