Dedičnosť a zloženie sú dve programovacie techniky, ktoré vývojári používajú na nadviazanie vzťahov medzi triedami a objektmi. Zatiaľ čo dedičstvo odvodzuje jednu triedu od druhej, kompozícia definuje triedu ako súčet jej častí.
Triedy a objekty vytvorené dedením sú Tesne spojené pretože zmena rodiča alebo nadtriedy v dedičskom vzťahu môže spôsobiť porušenie kódu. Triedy a objekty vytvorené kompozíciou sú voľne spojené, čo znamená, že môžete ľahšie meniť jednotlivé súčasti bez toho, aby ste porušili kód.
Pretože voľne spojený kód ponúka väčšiu flexibilitu, mnoho vývojárov sa dozvedelo, že kompozícia je lepšia technika ako dedičstvo, ale pravda je zložitejšia. Výber programovacieho nástroja je podobný výberu správneho kuchynského nástroja: Na krájanie zeleniny by ste nepoužili nôž na maslo a rovnako by ste nemali zvoliť zloženie pre každý scenár programovania.
V tomto Java Challenger sa naučíte rozdiel medzi dedičstvom a zložením a ako sa rozhodnúť, ktoré je pre váš program správne. Ďalej vám predstavím niekoľko dôležitých, ale náročných aspektov dedičstva Java: prepísanie metódy, Super
kľúčové slovo a typ casting. Na záver otestujete, čo ste sa naučili postupovaním po príklade dedičstva po riadkoch, aby ste určili, aký by mal byť výstup.
Kedy použiť dedičstvo v Jave
V objektovo orientovanom programovaní môžeme dedičstvo použiť, keď vieme, že medzi dieťaťom a jeho nadradenou triedou existuje vzťah „je“. Niektoré príklady:
- Osoba je a človek.
- Mačka je zviera.
- Auto je a vozidlo.
V každom prípade je dieťaťom alebo podtriedou a špecializovaný verzia nadradenej alebo nadtriedy. Dedenie zo nadtriedy je príkladom opätovného použitia kódu. Ak chcete lepšie pochopiť tento vzťah, venujte chvíľu štúdiu Auto
triedy, ktorá dedí z Vozidlo
:
trieda Vozidlo {značka reťazca; Farba šnúrky; dvojitá váha; dvojnásobná rýchlosť; void move () {System.out.println ("Vozidlo sa pohybuje"); }} verejná trieda Auto rozširuje vozidlo {String licensePlateNumber; Majiteľ reťazca; Reťazec bodyStyle; public static void main (String ... inheritanceExample) {System.out.println (nové vozidlo (). značka); System.out.println (nová značka Car ().); new Car (). move (); }}
Keď uvažujete o použití dedičstva, položte si otázku, či je podtrieda skutočne špecializovanejšou verziou nadtriedy. V takom prípade je auto typom vozidla, takže dedičský vzťah má zmysel.
Kedy použiť zloženie v Jave
V objektovo orientovanom programovaní môžeme použiť kompozíciu v prípadoch, keď jeden objekt „má“ (alebo je súčasťou) iný objekt. Niektoré príklady:
- Auto má batéria (batéria je súčasťou auto).
- Osoba má srdce (srdce je súčasťou osoba).
- Dom má obývacia izba (obývacia izba je súčasťou dom).
Pre lepšie pochopenie tohto typu vzťahu zvážte zloženie a House
:
verejná trieda CompositionExample {public static void main (String ... houseComposition) {new House (new Bedroom (), new LivingRoom ()); // Dom je teraz zložený zo spálne a LivingRoom} statickej triedy House {Bedroom bedroom; LivingRoom livingRoom; Dom (spálňa, LivingRoom livingRoom) {this.bedroom = spálňa; this.livingRoom = livingRoom; }} statická trieda Spálňa {} statická trieda LivingRoom {}}
V takom prípade vieme, že dom má obývaciu izbu a spálňu, takže môžeme použiť dom Spálňa
a Obývačka
predmety v zložení a House
.
Získajte kód
Získajte zdrojový kód pre príklady v tomto Java Challenger. Podľa nasledujúcich príkladov môžete spustiť svoje vlastné testy.
Dedičstvo vs zloženie: dva príklady
Zvážte nasledujúci kód. Je to dobrý príklad dedičstva?
import java.util.HashSet; verejná trieda CharacterBadExampleInheritance rozširuje HashSet {public static void main (Reťazec ... badExampleOfInheritance) {BadExampleInheritance badExampleInheritance = nový BadExampleInheritance (); badExampleInheritance.add ("Homer"); badExampleInheritance.forEach (System.out :: println); }
V tomto prípade je odpoveď nie. Podradená trieda zdedí veľa metód, ktoré nikdy nepoužije, čoho výsledkom je úzko spojený kód, ktorý je neprehľadný a ťažko sa udržuje. Ak sa pozriete pozorne, je tiež zrejmé, že tento kód neprejde testom „je“.
Teraz skúsime ten istý príklad pomocou kompozície:
import java.util.HashSet; import java.util.Set; verejná trieda CharacterCompositionExample {static Set set = new HashSet (); public static void main (String ... goodExampleOfComposition) {set.add ("Homer"); set.forEach (System.out :: println); }
Použitie kompozície pre tento scenár umožňuje CharacterCompositionExample
triedy používať iba dve z HashSet
metódy bez toho, aby som ich všetky zdedil. Výsledkom je jednoduchší a menej spojený kód, ktorý bude ľahšie pochopiteľný a udržiavateľný.
Príklady dedičstva v JDK
Java Development Kit je plná dobrých príkladov dedičstva:
trieda IndexOutOfBoundsException rozširuje RuntimeException {...} trieda ArrayIndexOutOfBoundsException rozširuje IndexOutOfBoundsException {...} trieda FileWriter rozširuje OutputStreamWriter {...} trieda OutputStreamWriter rozširuje Writer {...} rozhranie Stream rozširuje BaseStream {...}
Všimnite si, že v každom z týchto príkladov je podradená trieda špecializovanou verziou svojho rodiča; napríklad, IndexOutOfBoundsException
je typ RuntimeException
.
Prepísanie metódy s dedičnosťou Java
Dedenie nám umožňuje znovu použiť metódy a ďalšie atribúty jednej triedy v novej triede, čo je veľmi výhodné. Aby však dedičstvo skutočne fungovalo, musíme tiež byť schopní zmeniť niektoré zdedené správanie v rámci našej novej podtriedy. Napríklad by sme mohli chcieť špecializovať zvuk a Kat
robí:
trieda Zviera {void emitSound () {System.out.println ("Zviera vydalo zvuk"); }} class Cat extends Animal {@Override void emitSound () {System.out.println ("Meow"); }} class Dog extends Animal {} public class Main {public static void main (String ... doYourBest) {Animal cat = new Cat (); // Mňauká Zvierací pes = nový Pes (); // Zviera vydalo zvuk Zviera zviera = nové Zviera (); // Zviera vydalo zvuk cat.emitSound (); dog.emitSound (); animal.emitSound (); }}
Toto je príklad dedičstva Java s prepísaním metódy. Najprv my predĺžiť the Zviera
triedy na vytvorenie nového Kat
trieda. Ďalej my prepísať the Zviera
triedy emitSound ()
metóda na získanie konkrétneho zvuku Kat
robí. Aj keď sme typ triedy deklarovali ako Zviera
, keď to vytvoríme inštanciou ako Kat
dostaneme mačku mňau.
Prevažujúcou metódou je polymorfizmus
Možno si z môjho posledného príspevku pamätáte, že prepísanie metódy je príkladom polymorfizmu alebo vyvolania virtuálnej metódy.
Má Java viacnásobné dedičstvo?
Na rozdiel od niektorých jazykov, napríklad C ++, Java neumožňuje viacnásobné dedenie s triedami. S rozhraniami však môžete použiť viac dedičstiev. Rozdiel medzi triedou a rozhraním je v tomto prípade ten, že rozhrania nezachovávajú stav.
Ak sa pokúsite o viac dedičstiev, ako mám nižšie, kód sa nezhromadí:
trieda zviera {} trieda cicavec {} trieda pes rozširuje zviera, cicavec {}
Riešením využívajúcim triedy by bolo dediť po jednom:
trieda Zviera {} trieda Cicavce rozširuje Zviera {} trieda Pes rozširuje cicavce {}
Ďalším riešením je nahradiť triedy rozhraniami:
interface Animal {} interface Mammal {} class Dog implementuje Animal, Mammal {}
Používanie „super“ na prístup k metódam nadradených tried
Keď sú dve triedy spojené prostredníctvom dedenia, podradená trieda musí mať prístup k každému prístupnému poľu, metóde alebo konštruktoru svojej nadradenej triedy. V Jave používame vyhradené slovo Super
aby sa zabezpečilo, že trieda dieťaťa bude mať naďalej prístup k prepísanej metóde svojho rodiča:
public class SuperWordExample {class Character {Character () {System.out.println ("Znak bol vytvorený"); } void move () {System.out.println ("Chôdza po znakoch ..."); }} trieda Moe rozširuje znak {Moe () {super (); } void giveBeer () {super.move (); System.out.println ("Dajte pivo"); }}}
V tomto príklade Postava
je rodičovská trieda pre Moe. Použitím Super
, máme prístup Postava
je presunúť ()
spôsob, ako dať Moe pivo.
Používanie konštruktorov s dedičstvom
Keď jedna trieda dedí od druhej, najskôr sa načíta konštruktor nadtriedy a až potom sa načíta jej podtrieda. Vo väčšine prípadov vyhradené slovo Super
bude automaticky pridaný do konštruktora. Ak má však nadtrieda vo svojom konštruktore parameter, budeme musieť zámerne vyvolať Super
konštruktor, ako je uvedené nižšie:
public class ConstructorSuper {class Character {Character () {System.out.println ("Bol vyvolaný superkonštruktor"); }} trieda Barney rozširuje znak {// Nie je potrebné deklarovať konštruktor ani vyvolávať superkonštruktor // K tomu bude JVM}}
Ak má nadradená trieda konštruktor s aspoň jedným parametrom, potom musíme deklarovať konštruktor v podtriede a použiť Super
výslovne vyvolať nadradený konštruktor. The Super
vyhradené slovo sa nepridá automaticky a kód sa bez neho kompiluje. Napríklad:
public class CustomizedConstructorSuper {class Character {Character (String name) {System.out.println (name + "was invoked"); }} trieda Barney rozširuje znak {// Budeme mať chybu kompilácie, ak explicitne nevyvoláme konštruktor // Musíme ho pridať Barney () {super ("Barney Gumble"); }}}
Zadajte casting a ClassCastException
Casting je spôsob výslovnej komunikácie s kompilátorom, že skutočne chcete previesť daný typ. Je to ako povedať: „Hej, JVM, viem, čo robím, preto prosím obsadte túto triedu týmto typom.“ Ak trieda, ktorú ste obsadili, nie je kompatibilná s typom triedy, ktorý ste deklarovali, dostanete a ClassCastException
.
V rámci dedičstva môžeme triedu dieťaťa priradiť k triede rodiča bez prenášania, ale nemôžeme priradiť triedu rodiča k triede dieťaťa bez použitia prenášania.
Uvažujme o nasledujúcom príklade:
verejná trieda CastingExample {public static void main (String ... castingExample) {Animal animal = new Animal (); Pes pes Zviera = (Pes) zviera; // Dostaneme ClassCastException Dog dog = new Dog (); Zvierací pesWithAnimalType = nový pes (); Dog specificDog = (Pes) dogWithAnimalType; specificDog.bark (); Zviera anotherDog = pes; // Je to tu v poriadku, nie je potrebné prenášať System.out.println ((((Dog) anotherDog)); // Toto je ďalší spôsob obsadenia objektu}} class Animal {} class Dog extends Animal {void bark () {System.out.println ("Au au"); }}
Keď sa pokúšame odhodiť Zviera
inštancia na a pes
dostaneme výnimku. Je to preto, lebo Zviera
nevie nič o svojom dieťati. Môže to byť mačka, vták, jašterica atď. O konkrétnom zvierati nie sú k dispozícii žiadne informácie.
Problém v tomto prípade je, že sme vytvorili inštanciu Zviera
Páči sa ti to:
Zvieracie zviera = nové zviera ();
Potom to skúsil obsadiť takto:
Pes pes Zviera = (Pes) zviera;
Pretože nemáme pes
napríklad je nemožné priradiť Zviera
do pes
. Ak sa pokúsime, dostaneme ClassCastException
.
Aby sme sa vyhli výnimke, mali by sme vytvoriť inštanciu pes
Páči sa ti to:
Pes pes = nový Pes ();
potom to priradiť k Zviera
:
Zviera anotherDog = pes;
V tomto prípade, pretože sme rozšírili Zviera
triedy, pes
inštancia nemusí byť ani obsadená; the Zviera
typ nadradenej triedy jednoducho prijme zadanie.
Casting s supertypmi
Je možné deklarovať a pes
so supertypom Zviera
, ale ak chceme vyvolať konkrétnu metódu z pes
, budeme to musieť obsadiť. Ako príklad, čo keby sme chceli vyvolať súbor štekať()
metóda? The Zviera
supertyp nemá žiadny spôsob, ako presne zistiť, na ktorú zvieraciu inštanciu sa odvolávame, takže musíme nahodiť pes
manuálne predtým, ako môžeme vyvolať štekať()
metóda:
Zvierací pesWithAnimalType = nový pes (); Dog specificDog = (Pes) dogWithAnimalType; specificDog.bark ();
Môžete tiež použiť casting bez priradenia objektu k typu triedy. Tento prístup je užitočný, ak nechcete deklarovať inú premennú:
System.out.println ((((Dog) anotherDog)); // Toto je ďalší spôsob obsadenia objektu
Prijmite výzvu v oblasti dedičstva Java!
Naučili ste sa niekoľko dôležitých pojmov dedičstva, takže teraz je čas vyskúšať dedičskú výzvu. Najskôr si prečítajte nasledujúci kód: