Objekttyp Gjutning i Java

1. Översikt

Java-typsystemet består av två typer av typer: primitiva och referenser.

Vi täckte primitiva omvandlingar i den här artikeln, och vi kommer att fokusera på referenser som gjuts här för att få en god förståelse för hur Java hanterar typer.

2. Primitiv kontra referens

Även om primitiva omvandlingar och referensvariabler kan se ut likadana, är de ganska olika begrepp.

I båda fallen ”förvandlar” vi en typ till en annan. Men på ett förenklat sätt innehåller en primitiv variabel dess värde, och konvertering av en primitiv variabel betyder irreversibla förändringar i dess värde:

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

Efter konverteringen i exemplet ovan är myInt- variabeln 1 , och vi kan inte återställa det tidigare värdet 1.1 från det.

Referensvariabler är olika ; referensvariabeln hänvisar bara till ett objekt men innehåller inte själva objektet.

Och att kasta en referensvariabel inte rör objektet det refererar till, utan bara märker detta objekt på ett annat sätt, utökar eller förminskar möjligheterna att arbeta med det. Upcasting begränsar listan över metoder och egenskaper som är tillgängliga för detta objekt, och downcasting kan utöka det.

En referens är som en fjärrkontroll till ett objekt. Fjärrkontrollen har fler eller färre knappar beroende på typ, och själva objektet lagras i en hög. När vi gör gjutning ändrar vi typen av fjärrkontroll men ändrar inte själva objektet.

3. Uppsändning

Att casta från en underklass till en superklass kallas upcasting . Vanligtvis utförs upcasting implicit av kompilatorn.

Upcasting är nära relaterat till arv - ett annat kärnkoncept i Java. Det är vanligt att använda referensvariabler för att hänvisa till en mer specifik typ. Och varje gång vi gör detta sker implicit utkastning.

För att demonstrera uppkastning, låt oss definiera en djurklass :

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

Låt oss nu förlänga djur :

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

Nu kan vi skapa ett objekt av Cat- klass och tilldela det till referensvariabeln av typen Cat :

Cat cat = new Cat();

Och vi kan också tilldela den till referensvariabeln av typen Animal :

Animal animal = cat;

I ovanstående uppdrag sker implicit utkastning. Vi kan göra det uttryckligen:

animal = (Animal) cat;

Men det finns ingen anledning att göra uttryckliga kasta upp arveträdet. Kompilatorn vet att katten är ett djur och inte visar några fel.

Observera att referensen kan referera till vilken undertyp som helst av den deklarerade typen.

Med hjälp av upcasting har vi begränsat antalet tillgängliga metoder för Cat- instans men har inte ändrat själva instansen. Nu kan vi inte göra något som är specifik för katt - vi kan inte åberopa mjau ()djuret variabel.

Även Cat objekt förblir Cat objekt, ringer mjau () skulle orsaka kompilatorn fel:

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

För att åberopa meow () måste vi nedkasta djuret , och vi gör det senare.

Men nu ska vi beskriva vad som ger oss uppkastningen. Tack vare utkastning kan vi dra nytta av polymorfism.

3.1. Polymorfism

Låt oss definiera en annan underklass av djur , en hundklass :

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

Nu kan vi definiera feed () -metoden som behandlar alla katter och hundar som djur :

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

Vi vill inte att AnimalFeeder bryr sig om vilket djur som finns på listan - en katt eller en hund . I matningsmetoden () är de alla djur .

Implicit uppkastning sker när vi lägger till objekt av en viss typ i djurlistan :

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

Vi lägger katter och hundar och de är upcast till Animal typ implicit. Varje katt är ett djur och varje hund är ett djur . De är polymorfa.

Förresten, alla Java-objekt är polymorfa eftersom varje objekt är ett objekt åtminstone. Vi kan tilldela en instans av djur till referensvariabeln för objekttyp och kompilatorn klagar inte:

Object object = new Animal();

Därför har alla Java-objekt vi skapar redan objektspecifika metoder, till exempel toString () .

Uppsändning till ett gränssnitt är också vanligt.

Vi kan skapa Mew- gränssnitt och få Cat att implementera det:

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

Nu alla Cat objekt kan också vara upcast till Mew :

Mew mew = new Cat();

Cat is a Mew , upcasting är lagligt och gjort implicit.

Således är katt en mew , djur , objekt och katt . Det kan tilldelas referensvariabler av alla fyra typerna i vårt exempel.

3.2. Åsidosättande

I exemplet ovan åsidosätts metoden eat () . Detta innebär att även om eat () kallas på variabeln av typen Animal , utförs arbetet med metoder som åberopas på riktiga föremål - katter och hundar:

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

I denna grundläggande handledning har vi undersökt vad som är upcasting, downcasting, hur man använder dem och hur dessa begrepp kan hjälpa dig att dra nytta av polymorfism.

Som alltid finns koden för den här artikeln tillgänglig på GitHub.