Programování

Polymorfismus a dědičnost v Javě

Podle legendy Venkat Subramaniam je polymorfismus nejdůležitějším konceptem v objektově orientovaném programování. Polymorfismus--nebo schopnost objektu provádět specializované akce na základě jeho typu - je to, co dělá Java kód flexibilním. Designové vzory jako Command, Observer, Decorator, Strategy a mnoho dalších vytvořené Gang Of Four, všechny používají nějakou formu polymorfismu. Zvládnutí tohoto konceptu výrazně zlepší vaši schopnost přemýšlet nad řešeními výzev programování.

Získejte kód

Zde můžete získat zdrojový kód a spustit vlastní testy zde: //github.com/rafadelnero/javaworld-challengers

Rozhraní a dědičnost v polymorfismu

S tímto Java Challenger se zaměřujeme na vztah mezi polymorfismem a dědičností. Hlavní věc, kterou je třeba mít na paměti, je, že polymorfismus vyžaduje dědičnost nebo implementace rozhraní. Můžete to vidět v níže uvedeném příkladu, kde najdete Duka a Juggy:

 public abstract class JavaMascot {public abstract void executeAction (); } public class Duke extends JavaMascot {@Override public void executeAction () {System.out.println ("Punch!"); }} public class Juggy rozšiřuje JavaMascot {@Override public void executeAction () {System.out.println ("Fly!"); }} public class JavaMascotTest {public static void main (String ... args) {JavaMascot dukeMascot = new Duke (); JavaMascot juggyMascot = new Juggy (); dukeMascot.executeAction (); juggyMascot.executeAction (); }} 

Výstup z tohoto kódu bude:

 Rána pěstí! Létat! 

Kvůli jejich specifickým implementacím, oběma Vévoda a JuggyAkce budou provedeny.

Přetíží metoda polymorfismus?

Mnoho programátorů je zmateno ve vztahu polymorfismu k přepsání metody a přetížení metody. Ve skutečnosti je skutečným polymorfismem pouze přepsání metody. Přetížení sdílí název stejné metody, ale parametry se liší. Polymorfismus je široký pojem, takže o tomto tématu bude vždy probíhat diskuse.

Jaký je účel polymorfismu?

Velkou výhodou a účelem použití polymorfismu je oddělení třídy klienta od implementačního kódu. Místo toho, aby byla pevně zakódována, obdrží třída klienta implementaci k provedení nezbytné akce. Tímto způsobem třída klienta ví jen tolik, aby mohla provádět své akce, což je příklad volného propojení.

Chcete-li lépe porozumět účelu polymorfismu, podívejte se na SweetCreator:

 public abstract class SweetProducer {public abstract void produceSweet (); } public class CakeProducer rozšiřuje SweetProducer {@Override public void produceSweet () {System.out.println ("Cake produced"); }} veřejná třída ChocolateProducer rozšiřuje SweetProducer {@Override public void produceSweet () {System.out.println ("Chocolate produced"); }} public class CookieProducer rozšiřuje SweetProducer {@Override public void produceSweet () {System.out.println ("Cookie produced"); }} veřejná třída SweetCreator {soukromý seznam sweetProducer; public SweetCreator (seznam sweetProducer) {this.sweetProducer = sweetProducer; } public void createSweets () {sweetProducer.forEach (sweet -> sweet.produceSweet ()); }} veřejná třída SweetCreatorTest {public static void main (String ... args) {SweetCreator sweetCreator = new SweetCreator (Arrays.asList (new CakeProducer (), new ChocolateProducer (), new CookieProducer ())); sweetCreator.createSweets (); }} 

V tomto příkladu vidíte, že SweetCreator třída zná pouze  SweetProducer třída. Nezná implementaci každého z nich Bonbón. Toto oddělení nám dává flexibilitu při aktualizaci a opětovném použití našich tříd a údržba kódu je mnohem snazší. Při navrhování kódu vždy hledejte způsoby, jak jej učinit co nejflexibilnějším a udržovatelným. polymorfismus je velmi silná technika, kterou lze pro tyto účely použít.

Spropitné: The @ Přepis anotace zavazuje programátora použít stejný podpis metody, který musí být přepsán. Pokud metoda není přepsána, dojde k chybě kompilace.

Covariant návratové typy v přepsání metody

Je možné změnit návratový typ přepsané metody, pokud se jedná o kovariantní typ. A kovarianční typ je v podstatě podtřída návratového typu. Zvažte příklad:

 public abstract class JavaMascot {abstract JavaMascot getMascot (); } veřejná třída Duke rozšiřuje JavaMascot {@Override Duke getMascot () {vrátit nový Duke (); }} 

Protože Vévoda je JavaMascot, jsme schopni změnit typ návratu při přepsání.

Polymorfismus se základními třídami Java

V základních třídách Java používáme po celou dobu polymorfismus. Jedním z velmi jednoduchých příkladů je situace, kdy vytvoříme instanci ArrayList třída deklarujícíSeznam rozhraní jako typ:

 Seznam seznamu = nový ArrayList (); 

Chcete-li jít dále, zvažte tento ukázkový kód pomocí rozhraní Java Collections API bez polymorfismus:

 veřejná třída ListActionWithoutPolymorphism {// Příklad bez polymorfismu void executeVectorActions (vektorový vektor) {/ * Opakování kódu zde * /} void executeArrayListActions (ArrayList arrayList) {/ * opakování kódu zde * /} void executeLinkedListActions (LinkedList linkedList) {/ * opakování kódu zde * /} void executeCopyOnWriteArrayListActions (CopyOnWriteArrayList copyOnWriteArrayList) {/ * opakování kódu zde * /}} veřejná třída ListActionInvokerWithoutPolymorphism {listAction.executeVectorActions (new Vector ()); listAction.executeArrayListActions (nový ArrayList ()); listAction.executeLinkedListActions (nový LinkedList ()); listAction.executeCopyOnWriteArrayListActions (nový CopyOnWriteArrayList ()); } 

Ošklivý kód, že? Představte si, že se to snažíte udržet! Nyní se podívejme na stejný příklad s polymorfismus:

 public static void main (String ... polymorphism) {ListAction listAction = new ListAction (); listAction.executeListActions (); } public class ListAction {void executeListActions (List list) {// Execute actions with different lists}} public class ListActionInvoker {public static void main (String ... masterPolymorphism) {ListAction listAction = new ListAction (); listAction.executeListActions (new Vector ()); listAction.executeListActions (nový ArrayList ()); listAction.executeListActions (nový LinkedList ()); listAction.executeListActions (nový CopyOnWriteArrayList ()); }} 

Výhodou polymorfismu je flexibilita a rozšiřitelnost. Místo vytváření několika různých metod můžeme deklarovat pouze jednu metodu, která přijímá obecnou Seznam typ.

Vyvolání konkrétních metod při volání polymorfní metody

Je možné vyvolat konkrétní metody v polymorfním volání, ale dělá to za cenu flexibility. Zde je příklad:

 public abstract class MetalGearCharacter {abstract void useWeapon (strunná zbraň); } veřejná třída BigBoss rozšiřuje MetalGearCharacter {@Override void useWeapon (strunná zbraň) {System.out.println ("Big Boss používá" + zbraň); } void giveOrderToTheArmy (String orderMessage) {System.out.println (orderMessage); }} veřejná třída SolidSnake rozšiřuje MetalGearCharacter {void useWeapon (strunná zbraň) {System.out.println ("Solid Snake používá" + zbraň); }} veřejná třída UseSpecificMethod {public static void executeActionWith (MetalGearCharacter metalGearCharacter) {metalGearCharacter.useWeapon ("SOCOM"); // Níže uvedený řádek nebude fungovat // metalGearCharacter.giveOrderToTheArmy ("Attack!"); if (metalGearCharacter instance of BigBoss) {((BigBoss) metalGearCharacter) .giveOrderToTheArmy ("Attack!"); }} public static void main (String ... specificPolymorphismInvocation) {executeActionWith (new SolidSnake ()); executeActionWith (nový BigBoss ()); }} 

Technika, kterou zde používáme, je odlévání, nebo záměrně měnit typ objektu za běhu.

Je možné vyvolat konkrétní metodu pouze při přetypování obecného typu na konkrétní typ. Dobrá analogie by byla výslovně říct kompilátoru: „Hej, vím, co tady dělám, takže hodím objekt na konkrétní typ a použiji konkrétní metodu.“

S odkazem na výše uvedený příklad existuje důležitý důvod, proč kompilátor odmítá přijmout vyvolání konkrétní metody: předávaná třída by mohla být Pevný had. V tomto případě neexistuje žádný způsob, jak kompilátor zajistit každou podtřídu MetalGearPostavagiveOrderToTheArmy deklarovaná metoda.

The instanceof vyhrazené klíčové slovo

Věnujte pozornost vyhrazenému slovu instanceof. Před vyvoláním konkrétní metody jsme se zeptali, zda MetalGearPostava je "instanceofVelký šéf. Pokud si to nebyl A Velký šéf instance, obdržíme následující zprávu o výjimce:

 Výjimku ve vlákně „main“ java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake nelze přetypovat na com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss 

The super vyhrazené klíčové slovo

Co kdybychom chtěli odkazovat na atribut nebo metodu z nadtřídy Java? V tomto případě bychom mohli použít super vyhrazené slovo. Například:

 veřejná třída JavaMascot {void executeAction () {System.out.println ("Java Mascot se chystá provést akci!"); }} veřejná třída Duke rozšiřuje JavaMascot {@Override void executeAction () {super.executeAction (); System.out.println („Duke se chystá udeřit!“); } public static void main (String ... superReservedWord) {new Duke (). executeAction (); }} 

Použití vyhrazeného slova super v VévodaJe provést akci metoda vyvolá metodu nadtřídy. Poté provedeme konkrétní akci z Vévoda. Proto můžeme obě zprávy vidět na výstupu níže:

 Maskot Java se chystá provést akci! Vévoda se chystá udeřit! 

Přijměte výzvu polymorfismu!

Pojďme si vyzkoušet, co jste se naučili o polymorfismu a dědičnosti. V této výzvě jste dostali několik metod od Matta Groeninga The Simpsons a vaším úkolem je odvodit, jaký bude výstup pro každou třídu. Chcete-li začít, pečlivě analyzujte následující kód:

 veřejná třída PolymorphismChallenge {statická abstraktní třída Simpson {void talk () {System.out.println ("Simpson!"); } chráněný prázdný žert (String žert) {System.out.println (žert); }} statická třída Bart rozšiřuje Simpsona {String žert; Bart (String prank) {this.prank = žert; } protected void talk () {System.out.println ("Eat my shorts!"); } protected void prank () {super.prank (prank); System.out.println ("Knock Homer down"); }} statická třída Lisa rozšiřuje Simpson {void talk (String toMe) {System.out.println ("I love Sax!"); }} public static void main (String ... doYourBest) {new Lisa (). talk ("Sax :)"); Simpson simpson = nový Bart ("D'oh"); simpson.talk (); Lisa lisa = nová Lisa (); lisa.talk (); ((Bart) simpson) .prank (); }} 

Co myslíš? Jaký bude konečný výstup? Nepoužívejte k tomu IDE! Jde o to, zdokonalit své dovednosti v analýze kódu, takže zkuste určit výstup sami.

Vyberte svou odpověď a níže najdete správnou odpověď.

 A) Miluji Saxa! D'oh Simpson! D'oh B) Sax :) Sněz mi trenky! Miluji Sax! D'oh Knock Homer dolů C) Sax :) D'oh Simpson! Sraz Homera dolů D) Miluji Saxa! Sněz moje kraťasy! Simpsone! D'oh srazil Homera 

Co se právě stalo? Pochopení polymorfismu

Pro následující vyvolání metody:

 nová Lisa (). talk ("Sax :)"); 

výstup bude „Miluji Sax!"Je to proto, že míjíme a." Tětiva k metodě a Lisa má metodu.

Pro další vyvolání:

 Simpson simpson = nový Bart ("D'oh");

simpson.talk ();

Výstup bude „Sněz moje kraťasy!„Je to proto, že vytváříme instanci Simpson zadejte s Bart.

Nyní zkontrolujte tento, což je trochu složitější:

 Lisa lisa = nová Lisa (); lisa.talk (); 

Zde používáme přetížení metody s dědičností. Metodě mluvení nic nepředáváme, proto Simpson mluvit metoda je vyvolána. V tomto případě bude výstup:

 „Simpsone!“ 

Zde je ještě jedna:

 ((Bart) simpson) .prank (); 

V tomto případě žert String byl předán, když jsme vytvořili instanci Bart třída s nový Bart („D'oh“);. V tomto případě nejprve super. žert bude vyvolána metoda následovaná konkrétním žert metoda od Bart. Výstup bude:

 „D'oh“ „Sraz Homera dolů“ 

Video výzva! Ladění polymorfismu a dědičnosti Java

Ladění je jedním z nejjednodušších způsobů, jak plně absorbovat programovací koncepty a zároveň vylepšit váš kód. V tomto videu můžete sledovat, zatímco ladím a vysvětluji výzvu polymorfismu Java:

Časté chyby s polymorfismem

Je častou chybou si myslet, že je možné vyvolat konkrétní metodu bez použití castingu.

Další chybou je nejistota, jaká metoda bude vyvolána při instanci třídy polymorfně. Nezapomeňte, že vyvolávanou metodou je metoda vytvořené instance.

Nezapomeňte také, že přepsání metody není přetížení metody.

Je nemožné přepsat metodu, pokud jsou parametry odlišné. To je možné změnit návratový typ přepsané metody, pokud je návratový typ podtřídou metody nadtřídy.

Co si pamatovat o polymorfismu

  • Vytvořená instance určí, jaká metoda bude vyvolána při použití polymorfismu.
  • The @ Přepis anotace zavazuje programátora k použití přepsané metody; pokud ne, dojde k chybě kompilátoru.
  • Polymorfismus lze použít s normálními třídami, abstraktními třídami a rozhraními.
  • Většina návrhových vzorů závisí na nějaké formě polymorfismu.
  • Jediným způsobem, jak použít konkrétní metodu ve vaší polymorfní podtřídě, je použití castingu.
  • Je možné navrhnout výkonnou strukturu vašeho kódu pomocí polymorfismu.
  • Spusťte testy. Tímto způsobem budete moci zvládnout tento silný koncept!

Odpovědět klíč

Odpověď na tohoto Java challenger je D. Výstup by byl:

 Miluji Sax! Sněz moje kraťasy! Simpsone! D'oh srazil Homera dolů 

Tento příběh „Polymorfismus a dědičnost v Javě“ původně publikoval JavaWorld.