Objekttype Casting i Java

1. Oversikt

Java-typesystemet består av to typer typer: primitiver og referanser.

Vi dekket primitive konverteringer i denne artikkelen, og vi vil fokusere på referanser i casting her, for å få en god forståelse av hvordan Java håndterer typer.

2. Primitive vs. referanse

Selv om primitive konverteringer og referansevariabel casting kan se like ut, er de ganske forskjellige konsepter.

I begge tilfeller "gjør" vi en type til en annen. Men på en forenklet måte inneholder en primitiv variabel dens verdi, og konvertering av en primitiv variabel betyr irreversible endringer i verdien:

dobbelt myDouble = 1,1; int myInt = (int) myDouble; assertNotEquals (myDouble, myInt);

Etter konverteringen i eksemplet ovenfor, minInt variabel er 1, og vi kan ikke gjenopprette den forrige verdien 1.1 fra det.

Referansevariabler er forskjellige; referansevariabelen refererer bare til et objekt, men inneholder ikke selve objektet.

Og å kaste en referansevariabel berører ikke objektet det refererer til, men merker bare dette objektet på en annen måte, og utvider eller innsnevrer muligheter for å jobbe med det. Upcasting begrenser listen over metoder og egenskaper som er tilgjengelige for dette objektet, og downcasting kan utvide den.

En referanse er som en fjernkontroll til et objekt. Fjernkontrollen har flere eller færre knapper, avhengig av type, og selve objektet er lagret i en haug. Når vi støper, endrer vi typen på fjernkontrollen, men endrer ikke selve objektet.

3. Upcasting

Å kaste fra en underklasse til en superklasse kalles upcasting. Oppkastingen utføres vanligvis implisitt av kompilatoren.

Upcasting er nært knyttet til arv - et annet kjernekonsept i Java. Det er vanlig å bruke referansevariabler for å referere til en mer spesifikk type. Og hver gang vi gjør dette, finner implisitt utkasting sted.

La oss definere en Dyr klasse:

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

La oss nå utvide Dyr:

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

Nå kan vi lage et objekt av Katt klasse og tilordne den til referansevariabelen av typen Katt:

Kattkatt = ny katt ();

Og vi kan også tildele den til referansevariabelen av typen Dyr:

Dyredyr = katt;

I oppgaven ovenfor foregår implisitt oppkast. Vi kunne gjøre det eksplisitt:

dyr = (Dyr) katt;

Men det er ikke nødvendig å gjøre eksplisitt oppkasting av arvetreet. Kompilatoren vet det katt er en Dyr og viser ingen feil.

Merk at referansen kan referere til hvilken som helst undertype av den deklarerte typen.

Ved hjelp av upcasting har vi begrenset antall tilgjengelige metoder Katt forekomst, men har ikke endret selve forekomsten. Nå kan vi ikke gjøre noe som er spesifikt for Katt - vi kan ikke påberope oss mjau()dyr variabel.

Selv om Katt gjenstand gjenstår Katt objekt, ringer mjau() ville forårsake kompilatorfeilen:

// animal.meow (); Metoden mjau () er udefinert for typen Dyr

Å påkalle mjau() vi trenger å slå ned dyr, og vi gjør dette senere.

Men nå skal vi beskrive hva som gir oss oppkastingen. Takket være oppkast kan vi dra nytte av polymorfisme.

3.1. Polymorfisme

La oss definere en annen underklasse av Dyr, a Hund klasse:

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

Nå kan vi definere mate() metode som behandler alle katter og hunder som dyr:

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

Vi vil ikke AnimalFeeder å bry seg om hvilken dyr står på listen - a Katt eller a Hund. I mate() metoden de alle er dyr.

Implisitt oppskyting skjer når vi legger til objekter av en bestemt type i dyr liste:

Liste dyr = ny ArrayList (); animals.add (ny katt ()); animals.add (ny hund ()); ny AnimalFeeder (). feed (dyr);

Vi legger til katter og hunder, og de er opptatt av Dyr skriv implisitt. Hver Katt er en Dyr og hver Hund er en Dyr. De er polymorfe.

Forresten, alle Java-objekter er polymorfe fordi hvert objekt er et Gjenstand i det minste. Vi kan tilordne en forekomst av Dyr til referansevariabelen til Gjenstand type og kompilatoren klager ikke:

Objektobjekt = nytt dyr ();

Derfor har alle Java-objekter vi allerede har Gjenstand spesifikke metoder, for eksempel toString ().

Upcasting til et grensesnitt er også vanlig.

Vi kan skape Mew grensesnitt og lage Katt implementer det:

offentlig grensesnitt Mew {public void meow (); } offentlig klasse Cat utvider Animal implementes Mew {public void eat () {// ...} public void meow () {// ...}}

Nå noen Katt objektet kan også kastes opp til Mew:

Mew mew = ny katt ();

Katt er en Mew, utkasting er lovlig og gjøres implisitt.

Og dermed, Katt er en Mew, Dyr, Gjenstand, og Katt. Det kan tildeles referansevariabler av alle fire typene i vårt eksempel.

3.2. Overstyring

I eksemplet ovenfor er spise() metoden overstyres. Dette betyr at selv om spise() kalles på variabelen til Dyr type, gjøres arbeidet med metoder påkalt på virkelige gjenstander - katter og hunder:

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

Hvis vi legger til noen loggføring i klassene våre, får vi se det Katt’S og HundMetodene kalles:

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 - hunden spiser 

Å oppsummere:

  • En referansevariabel kan referere til et objekt hvis objektet er av samme type som en variabel, eller hvis det er en undertype
  • Upcasting skjer implisitt
  • Alle Java-objekter er polymorfe og kan behandles som objekter av supertype på grunn av oppkast

4. Nedkastning

Hva om vi vil bruke variabelen av typen Dyr å påkalle en metode som bare er tilgjengelig for Katt klasse? Her kommer nedkastingen. Det er rollebesetningen fra en superklasse til en underklasse.

La oss ta et eksempel:

Dyredyr = ny katt ();

Vi vet det dyr variabel refererer til forekomsten av Katt. Og vi vil påberope oss Katt’S mjau() metoden på dyr. Men kompilatoren klager over det mjau() metoden eksisterer ikke for typen Dyr.

Å ringe mjau() vi burde slå ned dyr til Katt:

((Katt) dyr) .meow ();

De indre parentesene og typen de inneholder kalles noen ganger cast-operatøren. Merk at eksterne parenteser også er nødvendige for å kompilere koden.

La oss skrive det forrige om AnimalFeeder eksempel med mjau() metode:

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

Nå får vi tilgang til alle metodene som er tilgjengelige for Katt klasse. Se på loggen for å forsikre deg om at mjau() heter faktisk:

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.Hund - hunden spiser

Vær oppmerksom på at vi i eksemplet ovenfor prøver å nedstøpe bare de objektene som virkelig er forekomster av Katt. For å gjøre dette bruker vi operatøren tilfelle av.

4.1. tilfelle av Operatør

Vi bruker ofte tilfelle av før nedkasting for å sjekke om objektet tilhører den spesifikke typen:

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

4.2. ClassCastException

Hvis vi ikke hadde sjekket typen med tilfelle av operatør, ville ikke kompilatoren ha klaget. Men ved kjøretid vil det være et unntak.

For å demonstrere dette, la oss fjerne tilfelle av operatør fra ovennevnte kode:

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

Denne koden kompileres uten problemer. Men hvis vi prøver å kjøre det, ser vi et unntak:

java.lang.ClassCastException: com.baeldung.casting.Dog kan ikke kastes til com.baeldung.casting.Cat

Dette betyr at vi prøver å konvertere et objekt som er en forekomst av Hund inn i en Katt forekomst.

ClassCastException 's kastes alltid ved kjøretid hvis typen vi nedkaster til ikke samsvarer med typen av det virkelige objektet.

Merk at hvis vi prøver å downcast til en ikke-relatert type, vil ikke kompilatoren tillate dette:

Dyr dyr; String s = (String) dyr;

Kompilatoren sier "Can't cast from Animal to String".

For at koden skal kunne kompileres, bør begge typene være i samme arvetre.

La oss oppsummere:

  • Nedkastning er nødvendig for å få tilgang til medlemmer som er spesifikke for underklassen
  • Downcasting gjøres ved hjelp av cast operator
  • For å nedstøte et objekt trygt, trenger vi tilfelle av operatør
  • Hvis det virkelige objektet ikke samsvarer med typen vi nedkastet til, da ClassCastException blir kastet ved kjøretid

5. rollebesetning () Metode

Det er en annen måte å støpe gjenstander ved hjelp av metodene for Klasse:

offentlig ugyldig nårDowncastToCatWithCastMethod_thenMeowIsCalled () {Animal animal = new Cat (); hvis (Cat.class.isInstance (animal)) {Cat cat = Cat.class.cast (animal); cat.meow (); }}

I eksemplet ovenfor, rollebesetning () og isInstance () metoder brukes i stedet for støpt og tilfelle av operatører tilsvarende.

Det er vanlig å bruke rollebesetning () og isInstance () metoder med generiske typer.

La oss lage AnimalFeederGeneric klasse med mate() metode som "mater" bare en type dyr - katter eller hunder, avhengig av verdien av typeparameteren:

offentlig klasse AnimalFeederGeneric {privat klasse type; offentlig AnimalFeederGeneric (klassetype) {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);}}); returliste; }}

De mate() metoden sjekker hvert dyr og returnerer bare de som er forekomster av T.

Merk at Klasse forekomst skal også overføres til generisk klasse, ettersom vi ikke kan få den fra typeparameteren T. I vårt eksempel gir vi det i konstruktøren.

La oss lage T lik Katt og sørg for at metoden bare returnerer katter:

@Test offentlig ugyldig nårParameterCat_thenOnlyCatsFed () {List animals = new ArrayList (); animals.add (ny katt ()); animals.add (ny hund ()); AnimalFeederGeneric catFeeder = ny AnimalFeederGeneric (kat.klasse); Liste fedAnimals = catFeeder.feed (dyr); assertTrue (fedAnimals.size () == 1); assertTrue (fedAnimals.get (0) forekomst av Cat); }

6. Konklusjon

I denne grunnleggende opplæringen har vi utforsket hva som er upcasting, downcasting, hvordan du bruker dem og hvordan disse konseptene kan hjelpe deg med å dra nytte av polymorfisme.

Som alltid er koden for denne artikkelen tilgjengelig på GitHub.