Objecttype casten in Java

1. Overzicht

Het systeem van het Java-type bestaat uit twee soorten typen: primitieven en verwijzingen.

We hebben primitieve conversies in dit artikel behandeld en we zullen ons hier concentreren op het casten van referenties om een ​​goed begrip te krijgen van hoe Java met typen omgaat.

2. Primitief vs. referentie

Hoewel primitieve conversies en het casten van referentievariabelen op elkaar lijken, zijn het nogal verschillende concepten.

In beide gevallen 'veranderen' we het ene type in het andere. Maar op een vereenvoudigde manier bevat een primitieve variabele zijn waarde, en omzetting van een primitieve variabele betekent onomkeerbare veranderingen in zijn waarde:

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

Na de conversie in het bovenstaande voorbeeld, myInt variabele is 1, en we kunnen de vorige waarde niet herstellen 1.1 van het.

Referentievariabelen zijn verschillend; de referentievariabele verwijst alleen naar een object, maar bevat niet het object zelf.

En het casten van een referentievariabele raakt het object waarnaar het verwijst niet, maar labelt dit object alleen op een andere manier, waardoor de mogelijkheden om ermee te werken worden vergroot of verkleind. Upcasting verkleint de lijst met methoden en eigenschappen die beschikbaar zijn voor dit object, en downcasting kan deze uitbreiden.

Een verwijzing is als een afstandsbediening naar een object. De afstandsbediening heeft meer of minder knoppen, afhankelijk van het type, en het object zelf wordt in een hoop opgeslagen. Als we casten, veranderen we het type afstandsbediening, maar niet het object zelf.

3. Upcasting

Casten van een subklasse naar een superklasse wordt upcasting genoemd. Meestal wordt de upcasting impliciet uitgevoerd door de compiler.

Upcasting is nauw verwant aan overerving - een ander kernbegrip in Java. Het is gebruikelijk om referentievariabelen te gebruiken om naar een specifieker type te verwijzen. En elke keer dat we dit doen, vindt er impliciete upcasting plaats.

Laten we, om upcasting te demonstreren, een Dier klasse:

openbare klasse Animal {public void eat () {// ...}}

Laten we nu uitbreiden Dier:

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

Nu kunnen we een object maken van Kat class en wijs deze toe aan de referentievariabele van het type Kat:

Cat cat = nieuwe Cat ();

En we kunnen het ook toewijzen aan de referentievariabele van het type Dier:

Dierlijk dier = kat;

Bij bovenstaande opdracht vindt impliciete upcasting plaats. We zouden het expliciet kunnen doen:

dier = (Dierlijke) kat;

Maar het is niet nodig om de erfenisboom expliciet op te werpen. De compiler weet dat kat is een Dier en geeft geen fouten weer.

Merk op dat die verwijzing naar elk subtype van het gedeclareerde type kan verwijzen.

Door upcasting te gebruiken, hebben we het aantal beschikbare methoden beperkt Kat instantie maar heeft de instantie zelf niet gewijzigd. Nu kunnen we niets doen dat specifiek is voor Kat - we kunnen er geen beroep op doen mauw() op de dier variabele.

Hoewel Kat object blijft Kat object, bellen mauw() zou de compilatiefout veroorzaken:

// dier.meow (); De methode meow () is niet gedefinieerd voor het type Animal

Inroepen mauw() we moeten neerslachtig zijn dier, en we zullen dit later doen.

Maar nu zullen we beschrijven wat ons de upcasting oplevert. Dankzij upcasting kunnen we profiteren van polymorfisme.

3.1. Polymorfisme

Laten we een andere subklasse definiëren van Dier, een Hond klasse:

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

Nu kunnen we de eten geven() methode die alle katten en honden graag behandelt dieren:

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

We willen niet AnimalFeeder om welke dier staat op de lijst - a Kat of een Hond. In de eten geven() methode ze zijn allemaal dieren.

Impliciete upcasting vindt plaats wanneer we objecten van een specifiek type toevoegen aan het dieren lijst:

Lijst dieren = nieuwe ArrayList (); animals.add (nieuwe Cat ()); animals.add (new Dog ()); nieuwe AnimalFeeder (). feed (dieren);

We voegen katten en honden toe en ze zijn opgewonden Dier typ impliciet. Elk Kat is een Dier en elk Hond is een Dier. Ze zijn polymorf.

Overigens zijn alle Java-objecten polymorf omdat elk object een Voorwerp minstens. We kunnen een instantie van Dier naar de referentievariabele van Voorwerp type en de compiler zal niet klagen:

Object object = nieuw dier ();

Daarom hebben alle Java-objecten die we maken al Voorwerp specifieke methoden, bijvoorbeeld toString ().

Upcasting naar een interface is ook gebruikelijk.

We kunnen creëren Mew interface en make Kat Implementeren:

openbare interface Mew {public void meow (); } public class Cat breidt uit Animal implementeert Mew {public void eat () {// ...} public void meow () {// ...}}

Nu elk Kat object kan ook worden geactualiseerd Mew:

Mew mew = nieuwe Cat ();

Kat is een Mewis upcasting legaal en wordt het impliciet gedaan.

Dus, Kat is een Mew, Dier, Voorwerp, en Kat. Het kan worden toegewezen aan referentievariabelen van alle vier de typen in ons voorbeeld.

3.2. Overheersend

In het bovenstaande voorbeeld is de eten() methode wordt overschreven. Dit betekent dat hoewel eten() wordt aangeroepen op de variabele van de Dier type, wordt het werk gedaan door methoden die worden aangeroepen op echte objecten - katten en honden:

openbare void feed (lijst dieren) {animals.forEach (animal -> {animal.eat ();}); }

Als we wat logboekregistratie aan onze lessen toevoegen, zullen we dat zien Kat’S en Hond’S methoden worden genoemd:

web - 2018-02-15 22: 48: 49,354 [main] INFO com.baeldung.casting.Cat - kat eet web - 2018-02-15 22: 48: 49,363 [main] INFO com.baeldung.casting.Dog - hond eet 

Opsommen:

  • Een referentievariabele kan naar een object verwijzen als het object van hetzelfde type is als een variabele of als het een subtype is
  • Upcasting gebeurt impliciet
  • Alle Java-objecten zijn polymorf en kunnen vanwege upcasting als supertype-objecten worden behandeld

4. Downcasting

Wat als we de variabele van type willen gebruiken Dier om een ​​methode aan te roepen die alleen beschikbaar is voor Kat klasse? Hier komt de downcasting. Het is het gieten van een superklasse naar een subklasse.

Laten we een voorbeeld nemen:

Dierlijk dier = nieuwe kat ();

We weten dat dier variabele verwijst naar de instantie van Kat. En we willen een beroep doen Kat’S mauw() methode op de dier. Maar de compiler klaagt dat mauw() methode bestaat niet voor het type Dier.

Bellen mauw() we moeten neerslachtig zijn dier naar Kat:

((Cat) dier) .meow ();

De binnenste haakjes en het type dat ze bevatten, worden soms de cast-operator genoemd. Merk op dat externe haakjes ook nodig zijn om de code te compileren.

Laten we de vorige herschrijven AnimalFeeder voorbeeld met mauw() methode:

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

Nu krijgen we toegang tot alle beschikbare methoden voor Kat klasse. Bekijk het logboek om er zeker van te zijn mauw() heet eigenlijk:

web - 2018-02-16 18: 13: 45,445 [main] INFO com.baeldung.casting.Cat - kat eet 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 - hond eet

Merk op dat we in het bovenstaande voorbeeld alleen die objecten proberen te downcasten die echt exemplaren zijn van Kat. Hiervoor gebruiken we de operator instantie van.

4.1. instantie van Operator

We gebruiken vaak instantie van operator voordat u downcasting om te controleren of het object tot het specifieke type behoort:

if (dierlijk exemplaar van kat) {((kat) dier) .meow (); }

4.2. ClassCastException

Als we het type niet hadden gecontroleerd met de instantie van operator, zou de compiler niet hebben geklaagd. Maar tijdens runtime zou er een uitzondering zijn.

Laten we, om dit te demonstreren, het instantie van operator van de bovenstaande code:

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

Deze code compileert zonder problemen. Maar als we het proberen uit te voeren, zien we een uitzondering:

java.lang.ClassCastException: com.baeldung.casting.Dog kan niet worden gecast com.baeldung.casting.Cat

Dit betekent dat we proberen een object te converteren dat een instantie is van Hond in een Kat voorbeeld.

ClassCastException 's wordt altijd tijdens runtime gegenereerd als het type waarnaar we downcasten niet overeenkomt met het type van het echte object.

Merk op dat als we proberen te downcasten naar een niet-gerelateerd type, de compiler dit niet toestaat:

Dierlijk dier; String s = (String) dier;

De compiler zegt "Kan niet casten van Animal naar String".

Om de code te compileren, moeten beide typen zich in dezelfde overervingsboom bevinden.

Laten we samenvatten:

  • Downcasting is nodig om toegang te krijgen tot leden die specifiek zijn voor de subklasse
  • Downcasting wordt gedaan met behulp van de cast-operator
  • Om een ​​object veilig neer te halen, hebben we nodig instantie van operator
  • Als het echte object niet overeenkomt met het type waarnaar we neerslaan, dan ClassCastException zal tijdens runtime worden gegooid

5. gips() Methode

Er is een andere manier om objecten te casten met behulp van de methoden van Klasse:

openbare leegte whenDowncastToCatWithCastMethod_thenMeowIsCalled () {Dierlijk dier = nieuwe kat (); if (Cat.class.isInstance (dier)) {Cat cat = Cat.class.cast (dier); cat.meow (); }}

In het bovenstaande voorbeeld gips() en isInstance () methoden worden gebruikt in plaats van cast en instantie van operators dienovereenkomstig.

Het is gebruikelijk om te gebruiken gips() en isInstance () methoden met generieke typen.

Laten we creëren AnimalFeederGeneric les met eten geven() methode die slechts één type dieren 'voedt' - katten of honden, afhankelijk van de waarde van de parameter type:

openbare klasse AnimalFeederGeneric {type privéklasse; openbare AnimalFeederGeneric (Class type) {this.type = type; } public List feed (List dieren) {List list = new ArrayList (); animals.forEach (dier -> {if (type.isInstance (dier)) {T objAsType = type.cast (dier); list.add (objAsType);}}); retourlijst; }}

De eten geven() methode controleert elk dier en retourneert alleen diegenen die voorbeelden zijn van T.

Merk op dat de Klasse instantie moet ook worden doorgegeven aan de generieke klasse, omdat we deze niet kunnen krijgen van de parameter type T. In ons voorbeeld geven we het door in de constructor.

Laten we maken T gelijk aan Kat en zorg ervoor dat de methode alleen katten retourneert:

@Test openbare leegte whenParameterCat_thenOnlyCatsFed () {Dierenlijst = nieuwe ArrayList (); animals.add (nieuwe Cat ()); animals.add (new Dog ()); AnimalFeederGeneric catFeeder = nieuwe AnimalFeederGeneric (Cat.class); Lijst fedAnimals = catFeeder.feed (dieren); assertTrue (fedAnimals.size () == 1); assertTrue (fedAnimals.get (0) instantie van Cat); }

6. Conclusie

In deze fundamentele tutorial hebben we onderzocht wat upcasting, downcasting is, hoe u ze kunt gebruiken en hoe deze concepten u kunnen helpen voordeel te halen uit polymorfisme.

Zoals altijd is de code voor dit artikel beschikbaar op GitHub.


$config[zx-auto] not found$config[zx-overlay] not found