Casting typu objektu v Jave

1. Prehľad

Systém typov Java je tvorený dvoma druhmi typov: primitívmi a odkazmi.

V tomto článku sme sa zaoberali primitívnymi konverziami a zameriame sa tu na odovzdávanie referencií, aby sme dobre pochopili, ako Java pracuje s typmi.

2. Primitívne vs. referenčné

Aj keď primitívne konverzie a casting referenčných premenných môžu vyzerať podobne, sú to celkom odlišné koncepty.

V oboch prípadoch „premieňame“ jeden typ na druhý. Zjednodušene však primitívna premenná obsahuje svoju hodnotu a prepočet primitívnej premennej znamená nezvratné zmeny jej hodnoty:

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

Po konverzii vo vyššie uvedenom príklade myInt premenná je 1a nemôžeme obnoviť predchádzajúcu hodnotu 1.1 od toho.

Referenčné premenné sú rôzne; referenčná premenná sa vzťahuje iba na objekt, ale neobsahuje samotný objekt.

Vrhanie referenčnej premennej sa nedotýka objektu, na ktorý odkazuje, ale iba tento objekt označuje iným spôsobom, čím rozširuje alebo zužuje príležitosti na prácu s ním. Upcasting zužuje zoznam metód a vlastností dostupných pre tento objekt a downcasting ho môže rozšíriť.

Odkaz je ako diaľkové ovládanie objektu. Diaľkový ovládač má v závislosti od jeho typu viac alebo menej tlačidiel a samotný objekt je uložený na hromade. Keď prenášame, meníme typ diaľkového ovládača, ale nemeníme ani samotný objekt.

3. Upcasting

Casting z podtriedy do nadtriedy sa nazýva upcasting. Zvyčajne je prenesenie implicitne vykonané kompilátorom.

Upcasting úzko súvisí s dedičstvom - ďalším základným konceptom v Jave. Referenčné premenné sa bežne používajú na označenie konkrétnejších typov. A zakaždým, keď to urobíme, dôjde k implicitnému upcastingu.

Aby sme demonštrovali upcasting, definujme si Zviera trieda:

verejná trieda Zviera {public void eat () {// ...}}

Teraz to rozšírime Zviera:

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

Teraz môžeme vytvoriť objekt z Kat triedy a priraďte ju k referenčnej premennej typu Kat:

Cat cat = new Cat ();

A môžeme ho tiež priradiť k referenčnej premennej typu Zviera:

Zviera zviera = mačka;

Vo vyššie uvedenom zadaní dochádza k implicitnému upcastingu. Mohli by sme to urobiť výslovne:

zviera = (Zvieracia) mačka;

Nie je však potrebné robiť výslovné zhromaždenie dedičského stromu. Kompilátor to vie kat je Zviera a nezobrazuje žiadne chyby.

Upozorňujeme, že tento odkaz sa môže vzťahovať na akýkoľvek podtyp deklarovaného typu.

Pomocou upcastingu sme obmedzili počet dostupných metód Kat inštanciu, ale samotnú inštanciu nezmenili. Teraz nemôžeme urobiť nič, čo by bolo špecifické pre Mačka - nemôžeme sa dovolať mňau() na zviera premenná.

Hoci Kat predmet zostáva Kat objekt, volanie mňau() spôsobí chybu kompilátora:

// animal.meow (); Metóda meow () nie je definovaná pre typ Zviera

Vyvolať mňau() musíme byť skleslí zviera, a urobíme to neskôr.

Teraz však popíšeme, čo nás vedie k vyhýbavosti. Vďaka upcastingu môžeme využívať výhody polymorfizmu.

3.1. Polymorfizmus

Definujme ďalšiu podtriedu Zviera, a pes trieda:

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

Teraz môžeme definovať krmivo () metóda, ktorá lieči všetky mačky a psy ako zvieratá:

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

Nechceme Zvierací podávač starať sa o ktoré zviera je na zozname - a Kat alebo a pes. V krmivo () metóda sú všetky zvieratá.

Implicitné rozostrenie nastane, keď do objektu pridáme objekty konkrétneho typu zvieratá zoznam:

Zoznam zvierat = nový ArrayList (); animals.add (new Cat ()); animals.add (new Dog ()); nový AnimalFeeder (). krmivo (zvieratá);

Pridáme mačky a psy a sú naštvaní na Zviera typ implicitne. Každý Kat je Zviera a každý pes je Zviera. Sú polymorfné.

Mimochodom, všetky objekty Java sú polymorfné, pretože každý objekt je Objekt najmenej. Môžeme priradiť inštanciu Zviera k referenčnej premennej Objekt typu a prekladač sa nebude sťažovať:

Objekt objekt = new Animal ();

Preto všetky objekty Java, ktoré vytvoríme, už majú Objekt konkrétne metódy, napríklad natiahnuť().

Časté je aj vysielanie do rozhrania.

Môžeme vytvárať Mew rozhranie a urobiť Kat implementovať:

verejné rozhranie Mew {public void meow (); } verejná trieda Mačka sa rozširuje Zviera implementuje Mew {public void eat () {// ...} public void meow () {// ...}}

Teraz akékoľvek Kat objekt môže byť tiež upcast na Mew:

Mew mew = nová mačka ();

Kat je a Mew, upcasting je legálny a prebieha implicitne.

Teda Kat je a Mew, Zviera, Objekta Kat. Môže byť priradený k referenčným premenným všetkých štyroch typov v našom príklade.

3.2. Naliehavé

Vo vyššie uvedenom príklade je jesť () metóda je prepísaná. To znamená, že hoci jesť () je volaná na premennej Zviera typu, práca sa vykonáva metódami vyvolanými na skutočných objektoch - mačkách a psoch:

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

Ak do svojich tried pridáme nejaké protokolovanie, uvidíme KatA pesMetódy sa nazývajú:

web - 2018-02-15 22:48: 49 354 [main] INFO com.baeldung.casting.Cat - mačka žerie web - 2018-02-15 22:48: 49,363 [main] INFO com.baeldung.casting.Dog - pes žerie 

Sumarizovať:

  • Referenčná premenná môže odkazovať na objekt, ak je objekt rovnakého typu ako premenná, alebo ak ide o podtyp
  • K upcastingu dochádza implicitne
  • Všetky objekty Java sú polymorfné a je možné ich kvôli upcastingu považovať za objekty supertypu

4. Downcasting

Čo ak chceme použiť premennú typu Zviera vyvolať metódu dostupnú iba pre Kat trieda? Tu prichádza k úpadku. Je to odliatok zo nadtriedy do podtriedy.

Uveďme si príklad:

Zvieracie zviera = nová mačka ();

My to vieme zviera premenná odkazuje na inštanciu Kat. A chceme sa dovolať Kat‘S mňau() metóda na zviera. Ale kompilátor sa tomu sťažuje mňau() metóda pre daný typ neexistuje Zviera.

Zavolať mňau() mali by sme byť skleslí zviera do Kat:

((Mačka) zviera) .meow ();

Vnútorné zátvorky a typ, ktorý obsahujú, sa niekedy nazývajú operátor obsadenia. Upozorňujeme, že na zostavenie kódu sú potrebné aj externé zátvorky.

Prepíšeme to predchádzajúce Zvierací podávač príklad s mňau() metóda:

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

Teraz získavame prístup ku všetkým metódam, ktoré sú k dispozícii Kat trieda. Skontrolujte protokol a uistite sa, že mňau() sa v skutočnosti volá:

web - 2018-02-16 18:13:45 445 [main] INFO com.baeldung.casting.Cat - mačka žerie web - 2018-02-16 18:13:45 454 [main] INFO com.baeldung.casting.Cat - mňau web - 2018-02-16 18:13:45 455 [hlavné] INFO com.baeldung.casting.Dog - pes žerie

Všimnite si, že vo vyššie uvedenom príklade sa snažíme downcastovať iba tie objekty, ktoré sú skutočne inštanciami Kat. K tomu používame operátor inštancia.

4.1. inštancia Prevádzkovateľ

Často používame inštancia operátor pred downcastingom skontrolovať, či objekt patrí ku konkrétnemu typu:

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

4.2. ClassCastException

Keby sme typ nekontrolovali pomocou inštancia operátor, kompilátor by sa nesťažoval. Ale za behu by existovala výnimka.

Aby sme to demonštrovali, odstráňte inštancia operátor z vyššie uvedeného kódu:

public void nezaškrtnutéFeed (List animals) {animals.forEach (animal -> {animal.eat (); ((Cat) animal) .meow ();}); }

Tento kód sa kompiluje bez problémov. Ak sa ho však pokúsime spustiť, zobrazí sa výnimka:

java.lang.ClassCastException: com.baeldung.casting.Dog nie je možné obsadiť do com.baeldung.casting.Cat

To znamená, že sa pokúšame previesť objekt, ktorý je inštanciou pes do a Kat inštancia.

ClassCastException 's vždy vyhodené za behu, ak typ, ktorý sklopíme, sa nezhoduje s typom skutočného objektu.

Upozorňujeme, že ak sa pokúsime downcastovať na nesúvisiaci typ, kompilátor to nepovolí:

Zviera; Reťazec s = (Reťazec) zviera;

Kompilátor hovorí „Nie je možné prenášať zo zvieraťa na strunu“.

Aby sa mohol kompilovať kód, oba typy by mali byť v rovnakom dedičskom strome.

Zhrňme si to:

  • Pre získanie prístupu k členom špecifickým pre podtriedu je nevyhnutná downcasting
  • Downcasting sa vykonáva pomocou operátora obsadenia
  • Potrebujeme bezpečne zosadiť objekt inštancia operátor
  • Ak sa skutočný objekt nezhoduje s typom, z ktorého sme zostupovali, potom ClassCastException bude vyhodený za behu

5. obsadenie () Metóda

Existuje ďalší spôsob, ako vrhať objekty pomocou metód Trieda:

public void whenDowncastToCatWithCastMethod_thenMeowIsCalled () {Animal animal = new Cat (); if (Cat.class.isInstance (animal)) {Cat cat = Cat.class.cast (animal); kat.meow (); }}

Vo vyššie uvedenom príklade obsadenie () a isInstance () metódy sa používajú namiesto obsadenia a inštancia zodpovedajúcim spôsobom.

Je to bežné použitie obsadenie () a isInstance () metódy s generickými typmi.

Poďme tvoriť AnimalFeederGeneric trieda s krmivo () metóda, ktorá „kŕmi“ iba jeden druh zvierat - mačky alebo psy, v závislosti od hodnoty parametra typu:

public class AnimalFeederGeneric {private Class type; public AnimalFeederGeneric (typ triedy) {this.type = type; } public List feed (Zoznam zvierat) {List list = new ArrayList (); animals.forEach (animal -> {if (type.isInstance (animal)) {T objAsType = type.cast (animal); list.add (objAsType);}}); návratový zoznam; }}

The krmivo () metóda skontroluje každé zviera a vráti iba tie, ktoré sú inštanciami T.

Upozorňujeme, že Trieda inštancia by sa mala odovzdať aj generickej triede, pretože ju nemôžeme získať z parametra type T. V našom príklade to odovzdáme v konštruktore.

Poďme urobiť T rovná Kat a uistite sa, že metóda vracia iba mačky:

@Test public void whenParameterCat_thenOnlyCatsFed () {List animals = new ArrayList (); animals.add (new Cat ()); animals.add (new Dog ()); AnimalFeederGeneric catFeeder = nový AnimalFeederGeneric (kat. Trieda); Zoznam fedAnimals = catFeeder.feed (zvieratá); assertTrue (fedAnimals.size () == 1); assertTrue (fedAnimals.get (0) instanceof Cat); }

6. Záver

V tomto základnom tutoriáli sme skúmali, čo je to vykašliavanie, vyústenie, ako ich používať a ako vám tieto koncepty môžu pomôcť využiť polymorfizmus.

Ako vždy, kód tohto článku je k dispozícii na GitHub.