Java Challengers # 3: Polymorphismus und Vererbung

Java Challengers # 3: Polymorphismus und Vererbung


Wir übersetzen weiterhin eine Reihe von Artikeln mit Java-Problemen. Der letzte Beitrag über die Zeilen löste eine überraschend hitzige Diskussion aus . Wir hoffen, dass Sie auch an diesem Artikel nicht vorbeikommen. Und ja - wir laden Sie jetzt zum Stream zum zehnjährigen Jubiläum unseres Java Developer- Kurses ein.


Nach dem legendären Venkat Subramaniam ist Polymorphismus das wichtigste Konzept in der objektorientierten Programmierung. Polymorphismus - oder die Fähigkeit eines Objekts, spezielle Aktionen basierend auf seinem Typ auszuführen - macht Java-Code flexibel. Entwurfsmuster wie der Befehl, der Beobachter, der Dekorateur, die Strategie und viele andere, die von der Viererbande erstellt wurden, verwenden alle irgendeine Form von Polymorphismus. Wenn Sie dieses Konzept beherrschen, können Sie Softwarelösungen besser durchdenken.



Sie können den Quellcode für diesen Artikel verwenden und hier experimentieren: https://github.com/rafadelnero/javaworld-challengers


Schnittstellen und Vererbung im Polymorphismus


In diesem Artikel konzentrieren wir uns auf die Beziehung zwischen Polymorphismus und Vererbung. Das Wichtigste ist, dass Polymorphismus die Vererbung oder Implementierung einer Schnittstelle erfordert. Sie können dies im folgenden Beispiel mit Duke und 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 extends 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(); } } 

Die Ausgabe dieses Codes sieht folgendermaßen aus:


 Punch! Fly! 

Da bestimmte Implementierungen definiert sind, werden sowohl Juggy als auch Juggy Methoden aufgerufen.


Überlastet eine Methode einen Polymorphismus? Viele Programmierer verwechseln die Beziehung von Polymorphismus mit Überschreiben und Überladen . Tatsächlich ist nur eine Neudefinition der Methode ein echter Polymorphismus. Beim Überladen wird der gleiche Methodenname verwendet, jedoch unterschiedliche Parameter. Polymorphismus ist ein weit gefasster Begriff, daher wird es immer Diskussionen zu diesem Thema geben.


Was ist der Zweck des Polymorphismus


Ein großer Vorteil und Zweck der Verwendung von Polymorphismus besteht darin, die Client-Klassen-Verbindung mit der Implementierung zu reduzieren. Anstelle von Hardcode erhält die Client-Klasse eine Implementierung der Abhängigkeit, um die erforderliche Aktion auszuführen. Somit kennt die Client-Klasse das Minimum, um ihre Aktionen abzuschließen, was ein Beispiel für eine schwache Bindung ist.


Schauen Sie sich SweetCreator an, um den Zweck des Polymorphismus besser zu verstehen:


 public abstract class SweetProducer { public abstract void produceSweet(); } public class CakeProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cake produced"); } } public class ChocolateProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Chocolate produced"); } } public class CookieProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cookie produced"); } } public class SweetCreator { private List<SweetProducer> sweetProducer; public SweetCreator(List<SweetProducer> sweetProducer) { this.sweetProducer = sweetProducer; } public void createSweets() { sweetProducer.forEach(sweet -> sweet.produceSweet()); } } public class SweetCreatorTest { public static void main(String... args) { SweetCreator sweetCreator = new SweetCreator(Arrays.asList( new CakeProducer(), new ChocolateProducer(), new CookieProducer())); sweetCreator.createSweets(); } } 

In diesem Beispiel sehen Sie, dass die SweetCreator Klasse SweetCreator die SweetCreator Klasse kennt. Er kennt nicht die Umsetzung jedes Sweet . Diese Trennung gibt uns die Flexibilität, unsere Klassen zu aktualisieren und wiederzuverwenden, und dies erleichtert die Wartung des Codes erheblich. Suchen Sie beim Entwerfen von Code immer nach Möglichkeiten, ihn so flexibel und bequem wie möglich zu gestalten. Polymorphismus ist eine sehr mächtige Methode, um diesen Zweck zu erfüllen.


@Override muss der Programmierer dieselbe Methodensignatur verwenden, die neu definiert werden muss. Wenn die Methode nicht überschrieben wird, liegt ein Kompilierungsfehler vor.

Kovariante Rückgabetypen beim Überschreiben einer Methode


Sie können den Rückgabetyp einer überschriebenen Methode ändern, wenn es sich um einen kovarianten Typ handelt . Der kovariante Typ ist im Grunde eine Unterklasse des Rückgabewerts.


Betrachten Sie ein Beispiel:


 public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } } 

Da Duke JavaMascot , können wir den Typ des Rückgabewerts beim Überschreiben ändern.


Polymorphismus in Java-Basisklassen


Wir verwenden ständig Polymorphismus in den Basis-Java-Klassen. Ein sehr einfaches Beispiel ist das Instanziieren einer ArrayList Klasse mit einer Typdeklaration als List Schnittstelle.


 List<String> list = new ArrayList<>(); 

Stellen Sie sich einen Beispielcode vor, der die Java Collections-API ohne Polymorphismus verwendet:


 public class ListActionWithoutPolymorphism { //    void executeVectorActions(Vector<Object> vector) {/*    */} void executeArrayListActions(ArrayList<Object> arrayList) {/*    */} void executeLinkedListActions(LinkedList<Object> linkedList) {/*    */} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList<Object> copyOnWriteArrayList) { /*    */} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector<>()); listAction.executeArrayListActions(new ArrayList<>()); listAction.executeLinkedListActions(new LinkedList<>()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>()); } 

Ekelhafter Code, richtig? Stellen Sie sich vor, Sie müssen ihn begleiten! Betrachten Sie nun dasselbe Beispiel mit Polymorphismus:


 public static void main(String... polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List<Object> list) { //      } } public class ListActionInvoker { public static void main(String... masterPolymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(new Vector<>()); listAction.executeListActions(new ArrayList<>()); listAction.executeListActions(new LinkedList<>()); listAction.executeListActions(new CopyOnWriteArrayList<>()); } } 

Der Vorteil des Polymorphismus ist Flexibilität und Erweiterbarkeit. Anstatt mehrere verschiedene Methoden zu erstellen, können wir eine Methode deklarieren, die einen Listentyp erhält.


Aufruf spezifischer Methoden für eine polymorphe Methode


Sie können bestimmte Methoden mit einem polymorphen Methodenaufruf aufrufen. Dies liegt an der Flexibilität. Hier ist ein Beispiel:


 public abstract class MetalGearCharacter { abstract void useWeapon(String weapon); } public class BigBoss extends MetalGearCharacter { @Override void useWeapon(String weapon) { System.out.println("Big Boss is using a " + weapon); } void giveOrderToTheArmy(String orderMessage) { System.out.println(orderMessage); } } public class SolidSnake extends MetalGearCharacter { void useWeapon(String weapon) { System.out.println("Solid Snake is using a " + weapon); } } public class UseSpecificMethod { public static void executeActionWith(MetalGearCharacter metalGearCharacter) { metalGearCharacter.useWeapon("SOCOM"); //      // metalGearCharacter.giveOrderToTheArmy("Attack!"); if (metalGearCharacter instanceof BigBoss) { ((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!"); } } public static void main(String... specificPolymorphismInvocation) { executeActionWith(new SolidSnake()); executeActionWith(new BigBoss()); } } 

Die Technik, die wir hier verwenden, besteht darin, den Typ eines Objekts zur Laufzeit zu gießen oder bewusst zu ändern.


Bitte beachten Sie, dass ein Aufruf einer bestimmten Methode nur möglich ist, wenn ein allgemeinerer Typ in einen spezifischeren Typ umgewandelt wird. Eine gute Analogie wäre, dem Compiler explizit zu sagen: "Hey, ich weiß, was ich hier mache, also werde ich das Objekt in einen bestimmten Typ umwandeln und diese Methode verwenden."


In Bezug auf das obige Beispiel hat der Compiler einen guten Grund, den Aufruf bestimmter Methoden nicht zu akzeptieren: Die Klasse, die übergeben wird, muss SolidSnake . In diesem Fall kann der Compiler nicht sicherstellen, dass jede Unterklasse von MetalGearCharacter über eine giveOrderToTheArmy Methode verfügt.


Instanz des Schlüsselworts


Beachten Sie die reservierte instanceof . Vor dem Aufruf einer bestimmten Methode haben wir gefragt, ob MetalGearCharacter Instanz von BigBoss . Wenn dies keine Instanz von BigBoss , erhalten wir die folgende Ausnahme:


 Exception in thread `main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss 

Stichwort super


Was ist, wenn wir auf ein Attribut oder eine Methode aus der übergeordneten Klasse verweisen möchten? In diesem Fall können wir das Schlüsselwort super .
Zum Beispiel:


 public class JavaMascot { void executeAction() { System.out.println("The Java Mascot is about to execute an action!"); } } public class Duke extends JavaMascot { @Override void executeAction() { super.executeAction(); System.out.println("Duke is going to punch!"); } public static void main(String... superReservedWord) { new Duke().executeAction(); } } 

Wenn Sie das reservierte Wort super in der executeAction Methode der Duke Klasse verwenden, wird die Methode der übergeordneten Klasse executeAction . Dann führen wir eine bestimmte Aktion aus der Duke Klasse aus. Aus diesem Grund können wir beide Meldungen in der Ausgabe sehen:


 The Java Mascot is about to execute an action! Duke is going to punch! 

Lösen Sie das Polymorphismusproblem


Lassen Sie uns überprüfen, was Sie über Polymorphismus und Vererbung gelernt haben.


In dieser Aufgabe erhalten Sie verschiedene Methoden aus Matt Grönings Die Simpsons. Aus den Wellen müssen Sie herausfinden, wie die Ausgabe für jede Klasse aussehen wird. Analysieren Sie zunächst den folgenden Code sorgfältig:


 public class PolymorphismChallenge { static abstract class Simpson { void talk() { System.out.println("Simpson!"); } protected void prank(String prank) { System.out.println(prank); } } static class Bart extends Simpson { String prank; Bart(String prank) { this.prank = prank; } protected void talk() { System.out.println("Eat my shorts!"); } protected void prank() { super.prank(prank); System.out.println("Knock Homer down"); } } static class Lisa extends Simpson { void talk(String toMe) { System.out.println("I love Sax!"); } } public static void main(String... doYourBest) { new Lisa().talk("Sax :)"); Simpson simpson = new Bart("D'oh"); simpson.talk(); Lisa lisa = new Lisa(); lisa.talk(); ((Bart) simpson).prank(); } } 

Was meinen Sie? Was wird das Ergebnis sein? Verwenden Sie die IDE nicht, um dies herauszufinden! Das Ziel ist es, Ihre Fähigkeiten zur Codeanalyse zu verbessern. Versuchen Sie also, selbst zu entscheiden.


Wählen Sie Ihre Antwort (die richtige Antwort finden Sie am Ende des Artikels).


A)
Ich liebe Sax!
D'oh
Simpson!
D'oh


B)
Sax :)
Iss meine Shorts!
Ich liebe Sax!
D'oh
Schlage Homer nieder


C)
Sax :)
D'oh
Simpson!
Schlage Homer nieder


D)
Ich liebe Sax!
Iss meine Shorts!
Simpson!
D'oh
Schlage Homer nieder


Was ist passiert? Polymorphismus verstehen


Für den folgenden Methodenaufruf:


 new Lisa().talk("Sax :)"); 

Die Ausgabe wird "Ich liebe Sax!" sein. Dies liegt daran, dass wir den String an die Methode übergeben und die Lisa Klasse eine solche Methode hat.


Für den nächsten Anruf:


 Simpson simpson = new Bart("D'oh"); simpson.talk(); 

Die Ausgabe lautet "Eat my shorts!". Dies liegt daran, dass wir den Simpson Typ mit Bart initialisieren.


Nun sieh mal, das ist etwas kniffliger:


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

Hier verwenden wir Methodenüberladung mit Vererbung. Wir geben nichts an die talk , daher wird die talk von Simpson aufgerufen.


In diesem Fall lautet die Ausgabe "Simpson!".


Hier ist noch einer:


 ((Bart) simpson).prank(); 

In diesem Fall wurde die Streichzeichenfolge übergeben, als die Bart Klasse durch den new Bart("D'oh"); instanziiert wurde new Bart("D'oh"); . In diesem Fall wird zuerst die Methode super.prank() und dann die Methode super.prank() aus der Bart Klasse aufgerufen. Die Schlussfolgerung wird sein:


 "D'oh" "Knock Homer down" 

Häufige Polymorphismusfehler


Ein häufiger Fehler besteht darin, zu glauben, dass Sie eine bestimmte Methode aufrufen können, ohne Typumwandlungen zu verwenden.


Ein weiterer Fehler ist die Unsicherheit darüber, welche Methode aufgerufen wird, wenn die Klasse polymorph instanziiert wird. Denken Sie daran, dass die aufgerufene Methode die Methode der instanziierten Instanz ist.


Denken Sie auch daran, dass das Überschreiben einer Methode keine Überladung einer Methode darstellt.


Methode kann nicht überschrieben werden, wenn die Parameter unterschiedlich sind. Sie können den Rückgabetyp einer überschriebenen Methode ändern, wenn der Rückgabetyp eine Unterklasse ist.


Was Sie über Polymorphismus beachten müssen


  • Die erstellte Instanz bestimmt, welche Methode bei Verwendung von Polymorphismus aufgerufen wird.


  • @Override muss der Programmierer eine überschriebene Methode verwenden. Andernfalls tritt ein Compilerfehler auf.


  • Polymorphismus kann mit regulären Klassen, abstrakten Klassen und Schnittstellen verwendet werden.


  • Die meisten Entwurfsmuster hängen von irgendeiner Form von Polymorphismus ab.


  • Die einzige Möglichkeit, Ihre Methode in einer polymorphen Unterklasse aufzurufen, ist die Verwendung von Typguss.


  • Mit Polymorphismus können Sie eine leistungsstarke Codestruktur erstellen.


  • Experimentieren. Auf diese Weise können Sie dieses leistungsstarke Konzept beherrschen!



Die Antwort


Die Antwort lautet D.


Die Schlussfolgerung wird sein:


 I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

Wie immer freue ich mich über Ihre Kommentare und Fragen. Und wir warten in einer offenen Lektion auf Vitaly .

Source: https://habr.com/ru/post/de429120/


All Articles