Programmering

Polymorfisme og arv i Java

Ifølge legenden Venkat Subramaniam er polymorfisme det vigtigste begreb inden for objektorienteret programmering. Polymorfisme--eller et objekts evne til at udføre specialiserede handlinger baseret på dets type - er det, der gør Java-kode fleksibel. Designmønstre som Command, Observer, Decorator, Strategy og mange andre skabt af Gang Of Four bruger alle en eller anden form for polymorfisme. At mestre dette koncept forbedrer din evne til at tænke igennem løsninger på programmeringsudfordringer.

Få koden

Du kan få kildekoden til denne udfordring og køre dine egne tests her: //github.com/rafadelnero/javaworld-challengers

Grænseflader og arv i polymorfisme

Med denne Java Challenger fokuserer vi på forholdet mellem polymorfisme og arv. Det vigtigste at huske på er, at polymorfisme kræver arv eller interface implementering. Du kan se dette i eksemplet nedenfor med Duke og Juggy:

 offentlig abstrakt klasse JavaMascot {offentlig abstrakt ugyldig executeAction (); } offentlig klasse Duke udvider JavaMascot {@Override public void executeAction () {System.out.println ("Punch!"); }} public class Juggy udvider JavaMascot {@Override public void executeAction () {System.out.println ("Fly!"); }} JavaMascotTest i offentlig klasse {public static void main (String ... args) {JavaMascot dukeMascot = new Duke (); JavaMascot juggyMascot = ny Juggy (); dukeMascot.executeAction (); juggyMascot.executeAction (); }} 

Output fra denne kode vil være:

 Stans! Flyve! 

På grund af deres specifikke implementeringer er begge dele Hertug og JuggyHandlinger vil blive udført.

Er metode overbelastning af polymorfisme?

Mange programmører er forvirrede over forholdet mellem polymorfisme og metodeoverstyring og metodeoverbelastning. Faktisk er den eneste metodeoverstyring ægte polymorfisme. Overbelastning deler samme metodes navn, men parametrene er forskellige. Polymorfisme er et bredt udtryk, så der vil altid være diskussioner om dette emne.

Hvad er formålet med polymorfisme?

Den store fordel og formål med at bruge polymorfisme er at afkoble klientklassen fra implementeringskoden. I stedet for at være hårdkodet modtager klientklassen implementeringen for at udføre den nødvendige handling. På denne måde ved klientklassen lige nok til at udføre sine handlinger, hvilket er et eksempel på løs kobling.

For bedre at forstå formålet med polymorfisme, se på SweetCreator:

 offentlig abstrakt klasse SweetProducer {offentlig abstrakt ugyldig produceSweet (); } offentlig klasse CakeProducer udvider SweetProducer {@Override offentlig ugyldig produceSweet () {System.out.println ("Kage produceret"); }} offentlig klasse ChocolateProducer udvider SweetProducer {@Override offentlig ugyldig produceSweet () {System.out.println ("Chokolade produceret"); }} offentlig klasse CookieProducer udvider SweetProducer {@Override offentlig ugyldig produceSweet () {System.out.println ("Cookie produceret"); }} offentlig klasse SweetCreator {privat liste sweetProducer; offentlig SweetCreator (liste sweetProducer) {this.sweetProducer = sweetProducer; } offentlig tomrum createSweets () {sweetProducer.forEach (sweet -> sweet.produceSweet ()); }} offentlig klasse SweetCreatorTest {public static void main (String ... args) {SweetCreator sweetCreator = new SweetCreator (Arrays.asList (new CakeProducer (), new ChocolateProducer (), new CookieProducer ())); sweetCreator.createSweets (); }} 

I dette eksempel kan du se, at SweetCreator klasse kun kender  SweetProducer klasse. Det kender ikke implementeringen af ​​hver Sød. Denne adskillelse giver os fleksibilitet til at opdatere og genbruge vores klasser, og det gør koden meget lettere at vedligeholde. Når du designer din kode, skal du altid se efter måder at gøre den så fleksibel og vedligeholdelig som muligt. polymorfisme er en meget kraftfuld teknik til brug til disse formål.

Tip: Det @Override annotation forpligter programmøren til at bruge den samme metodesignatur, som skal tilsidesættes. Hvis metoden ikke tilsidesættes, vil der være en kompileringsfejl.

Kovariante returneringstyper i metode tilsidesat

Det er muligt at ændre returtypen for en tilsidesat metode, hvis det er en covariant type. EN covariant type er dybest set en underklasse af returtypen. Overvej et eksempel:

 offentlig abstrakt klasse JavaMascot {abstrakt JavaMascot getMascot (); } offentlig klasse Duke udvider JavaMascot {@Override Duke getMascot () {returner ny Duke (); }} 

Fordi Hertug er en JavaMascot, vi er i stand til at ændre returtypen, når vi tilsidesætter.

Polymorfisme med de centrale Java-klasser

Vi bruger polymorfisme hele tiden i de centrale Java-klasser. Et meget simpelt eksempel er, når vi instantierer ArrayList klasse, der erklærerListe interface som en type:

 Liste liste = ny ArrayList (); 

For at gå videre skal du overveje denne kodeeksempel ved hjælp af Java Collections API uden polymorfisme:

 offentlig klasse ListActionWithoutPolymorphism {// Eksempel uden polymorfisme void executeVectorActions (Vector vector) {/ * Code repetition here * /} void executeArrayListActions (ArrayList arrayList) {/ * Code repetition here * /} ugyldig executeLinkedListActions (LinkedList linkedList) gentagelse her * /} ugyldig executeCopyOnWriteArrayListActions (CopyOnWriteArrayList copyOnWriteArrayList) {/ * Kode gentagelse her * /}} offentlig klasse ListActionInvokerWithoutPolymorphism {listAction.executeVectorActions (new Vector ()); listAction.executeArrayListActions (ny ArrayList ()); listAction.executeLinkedListActions (ny LinkedList ()); listAction.executeCopyOnWriteArrayListActions (ny CopyOnWriteArrayList ()); } 

Grim kode, er det ikke? Forestil dig at prøve at vedligeholde det! Se nu på det samme eksempel med polymorfisme:

 public static void main (String ... polymorphism) {ListAction listAction = new ListAction (); listAction.executeListActions (); } public class ListAction {void executeListActions (List list) {// Udfør handlinger med forskellige lister}} public class ListActionInvoker {public static void main (String ... masterPolymorphism) {ListAction listAction = new ListAction (); listAction.executeListActions (ny Vector ()); listAction.executeListActions (ny ArrayList ()); listAction.executeListActions (ny LinkedList ()); listAction.executeListActions (ny CopyOnWriteArrayList ()); }} 

Fordelen ved polymorfisme er fleksibilitet og udvidelighed. I stedet for at oprette flere forskellige metoder kan vi kun erklære en metode, der modtager det generiske Liste type.

Påkald af specifikke metoder i et polymorfisk metodeopkald

Det er muligt at påberåbe sig specifikke metoder i et polymorfisk opkald, men det gøres på bekostning af fleksibilitet. Her er et eksempel:

 offentlig abstrakt klasse MetalGearCharacter {abstrakt ugyldigt brugWeapon (strengvåben); } offentlig klasse BigBoss udvider MetalGearCharacter {@ Override ugyldig useWeapon (strengvåben) {System.out.println ("Big Boss bruger et" + våben); } ugyldigt giveOrderToTheArmy (String orderMessage) {System.out.println (orderMessage); }} offentlig klasse SolidSnake udvider MetalGearCharacter {ugyldigt useWeapon (strengvåben) {System.out.println ("Solid Snake bruger et" + våben); }} offentlig klasse UseSpecificMethod {public static void executeActionWith (MetalGearCharacter metalGearCharacter) {metalGearCharacter.useWeapon ("SOCOM"); // Linjen nedenfor fungerer ikke // metalGearCharacter.giveOrderToTheArmy ("Attack!"); hvis (metalGearCharacter-forekomst af BigBoss) {(((BigBoss) metalGearCharacter). giveOrderToTheArmy ("Attack!"); }} public static void main (String ... specificPolymorphismInvocation) {executeActionWith (new SolidSnake ()); executeActionWith (ny BigBoss ()); }} 

Den teknik, vi bruger her, er støbningeller bevidst ændre objekttypen ved kørsel.

Bemærk, at det er muligt at påberåbe sig en bestemt metode kun når du støber den generiske type til den specifikke type. En god analogi ville være udtrykkeligt at sige til compileren: "Hej, jeg ved hvad jeg laver her, så jeg vil kaste objektet til en bestemt type og bruge en bestemt metode."

Med henvisning til ovenstående eksempel er der en vigtig grund til, at kompilatoren nægter at acceptere specifik metodeopkald: den klasse, der sendes, kunne være SolidSnake. I dette tilfælde er der ingen måde for compileren at sikre hver underklasse af MetalGearCharacter har giveOrderToTheArmy metode erklæret.

Det forekomst af reserveret nøgleord

Vær opmærksom på det reserverede ord forekomst af. Før vi påberåber os den specifikke metode, har vi spurgt, om MetalGearCharacter er “forekomst afChef. Hvis det ikke var -en Chef For eksempel modtager vi følgende undtagelsesmeddelelse:

 Undtagelse i tråden "hoved" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake kan ikke kastes til com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss 

Det super reserveret nøgleord

Hvad hvis vi ville henvise til en attribut eller metode fra en Java-superklasse? I dette tilfælde kunne vi bruge super reserveret ord. For eksempel:

 offentlig klasse JavaMascot {void executeAction () {System.out.println ("Java Mascot er ved at udføre en handling!"); }} offentlig klasse Duke udvider JavaMascot {@Override void executeAction () {super.executeAction (); System.out.println ("Duke vil slå!"); } public static ugyldig main (String ... superReservedWord) {new Duke (). executeAction (); }} 

Brug af det reserverede ord super i Hertug'S executeAction metode påberåber sig superklassemetoden. Vi udfører derefter den specifikke handling fra Hertug. Derfor kan vi se begge meddelelser i output nedenfor:

 Java Mascot er ved at udføre en handling! Hertug skal slå! 

Tag polymorfisme udfordringen!

Lad os prøve, hvad du har lært om polymorfisme og arv. I denne udfordring får du en håndfuld metoder fra Matt Groening's The Simpsons, og din udfordring er at udlede, hvad output for hver klasse vil være. For at starte skal du analysere følgende kode omhyggeligt:

 offentlig klasse PolymorphismChallenge {statisk abstrakt klasse Simpson {ugyldig samtale () {System.out.println ("Simpson!"); } beskyttet ugyldigt prank (String prank) {System.out.println (prank); }} statisk klasse Bart udvider Simpson {String prank; Bart (String prank) {this.prank = prank; } beskyttet void talk () {System.out.println ("Spis mine shorts!"); } beskyttet ugyldigt prank () {super.prank (prank); System.out.println ("Slå Homer ned"); }} statisk klasse Lisa udvider Simpson {void talk (String toMe) {System.out.println ("Jeg elsker Sax!"); }} public static ugyldig main (String ... doYourBest) {new Lisa (). talk ("Sax :)"); Simpson simpson = ny Bart ("D'oh"); simpson.talk (); Lisa lisa = ny Lisa (); lisa.talk (); ((Bart) simpson) .prank (); }} 

Hvad synes du? Hvad vil den endelige output være? Brug ikke en IDE til at finde ud af dette! Pointen er at forbedre dine kodeanalysefærdigheder, så prøv at bestemme output for dig selv.

Vælg dit svar, så kan du finde det rigtige svar nedenfor.

 A) Jeg elsker Sax! D'oh Simpson! D'oh B) Sax :) Spis mine shorts! Jeg elsker Sax! D'oh banker Homer ned C) Sax :) D'oh Simpson! Slå Homer ned D) Jeg elsker Sax! Spis mine shorts! Simpson! D'oh slår Homer ned 

Hvad skete der lige? Forståelse af polymorfisme

For følgende metodeopkald:

 nye Lisa (). snak ("Sax :)"); 

output vil være “Jeg elsker Sax!”Dette er fordi vi passerer en Snor til metoden og Lisa har metoden.

Til næste påkaldelse:

 Simpson simpson = ny Bart ("D'oh");

simpson.talk ();

Outputtet bliver "Spis mine shorts!"Dette skyldes, at vi instantierer Simpson skriv med Bart.

Tjek nu denne, som er lidt vanskeligere:

 Lisa lisa = ny Lisa (); lisa.talk (); 

Her bruger vi metodeoverbelastning med arv. Vi videregiver ikke noget til samtalemetoden, hvorfor Simpson tale metode påberåbes. I dette tilfælde vil output være:

 "Simpson!" 

Her er en mere:

 ((Bart) simpson) .prank (); 

I dette tilfælde er prank String blev bestået, da vi instantierede Bart klasse med nye Bart ("D'oh");. I dette tilfælde først super.prank metode påberåbes, efterfulgt af det specifikke prank metode fra Bart. Outputtet vil være:

 "D'oh" "Slå Homer ned" 

Video udfordring! Fejlretning af Java polymorfisme og arv

Fejlfinding er en af ​​de nemmeste måder til fuldt ud at absorbere programmeringskoncepter, samtidig med at du forbedrer din kode. I denne video kan du følge med, mens jeg debugger og forklarer Java-polymorfisme-udfordringen:

Almindelige fejl med polymorfisme

Det er en almindelig fejl at tro, at det er muligt at påberåbe sig en bestemt metode uden at bruge casting.

En anden fejl er at være usikker på, hvilken metode der vil blive påberåbt, når man indleder en klasse polymorf. Husk, at den metode, der skal påberåbes, er metoden til den oprettede forekomst.

Husk også, at metodeoverstyring ikke er metodeoverbelastning.

Det er umuligt at tilsidesætte en metode, hvis parametrene er forskellige. Det er muligt for at ændre returtypen for den tilsidesatte metode, hvis returtypen er en underklasse af superklassemetoden.

Hvad man skal huske ved polymorfisme

  • Den oprettede forekomst bestemmer, hvilken metode der skal påberåbes, når man bruger polymorfisme.
  • Det @Override anmærkning forpligter programmøren til at bruge en tilsidesat metode; hvis ikke, vil der være en kompilatorfejl.
  • Polymorfisme kan bruges med normale klasser, abstrakte klasser og grænseflader.
  • De fleste designmønstre afhænger af en form for polymorfisme.
  • Den eneste måde at bruge en bestemt metode i din polymorfe underklasse er ved hjælp af støbning.
  • Det er muligt at designe en stærk struktur i din kode ved hjælp af polymorfisme.
  • Kør dine tests. Gør dette, vil du være i stand til at mestre dette kraftfulde koncept!

Svar nøgle

Svaret på denne Java-udfordrer er D. Outputtet ville være:

 Jeg elsker Sax! Spis mine shorts! Simpson! D'oh slår Homer ned 

Denne historie, "Polymorfisme og arv i Java" blev oprindeligt udgivet af JavaWorld.

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