Polymorfisme i Java-vejledning - med objektorienteret kode til programmeringseksempel

Polymorfisme tillader genstande at blive behandlet på en udskiftelig måde. Dette reducerer duplikering af kode, når du ønsker, at de samme handlinger skal udføres på forskellige objekttyper. Polymorfisme betyder bogstaveligt ” mange former ”.

Lad os forklare, hvad vi mener med dette nøjagtigt.

Forklaring af polymorfisme ved analogi

Hvis du nogensinde har rejst internationalt, vil en vare på din tjekliste for emballering sandsynligvis være en adapter til elektrisk stik. Ellers kan du muligvis ikke oplade din telefon og andre enheder.

pakke.jpg

Mærkeligt nok er der cirka 16 forskellige typer stikkontakter over hele verden. Nogle har 2 ben, andre har 3 ben, nogle ben er cirkulære, nogle ben er rektangulære, og konfigurationen af ​​benene varierer.

Løsningen de fleste mennesker tager er at købe en universal stikadapter.

For at se på problemet på en anden måde, er problemet generelt, at vi har en stikkontakt, der kun accepterer 1 type stikobjekt! Stikkontakter er ikke polymorfe.

Livet ville være meget lettere for alle, hvis vi havde stikkontakter, der kunne acceptere mange forskellige typer stik. Vi kan gøre stikgrænsefladen polymorf ved at oprette forskellige formede slidser. Du kan se på billedet nedenfor, hvordan dette er gjort.

stikkontakt-metafor

Polymorfisme hjælper os med at skabe mere universelle grænseflader.

Forklaring med kode

Ethvert objekt, der har et IS-A-forhold, betragtes som polymorf. Du har et IS-A-forhold gennem arv (ved hjælp af det udvidede søgeord i klassesignaturen) eller gennem grænseflader (ved hjælp af implementeringsnøgleordet i klassesignaturen).

For at forstå polymorfisme fuldstændigt, skal du også forstå arv og grænseflader.

class Dog extends Animal implements Canine{ // ... some code here } 

På baggrund af uddrag ovenfor, en Doghar den følgende IS-A relationer: Animal, Canine, og Object(hver klasse implicit arver fra Object klassen, der lyder lidt latterligt!).

Lad os give et simpelt (fjollet) eksempel for at illustrere, hvordan vi kan bruge polymorfisme til at forenkle vores kode. Vi vil oprette en app med en forhør, der kan overbevise ethvert dyr om at tale.

forhør

Vi opretter en Interrogatorklasse, der er ansvarlig for at overbevise dyrene om at tale. Vi ønsker ikke at skrive en metode for hver type dyr: convinceDogToTalk(Dog dog), convinceCatToTalk(Cat cat), og så videre.

Vi foretrækker en generel metode, der accepterer ethvert dyr. Hvordan kan vi gøre dette?

class Interrogator{ public static void convinceToTalk(Animal subject) { subject.talk(); } } // We don't want anyone creating an animal object! abstract class Animal { public abstract void talk(); } class Dog extends Animal { public void talk() { System.out.println("Woof!"); } } class Cat extends Animal { public void talk() { System.out.println("Meow!"); } } public class App { public static void main(String[] args){ Dog dog = new Dog(); Cat cat = new Cat(); Animal animal = new Dog(); Interrogator.convinceToTalk(dog); //prints "Woof!" Interrogator.convinceToTalk(cat); //prints "Meow!" Interrogator.convinceToTalk(animal); //prints "Woof!" } } 

Vi opretter convinceToTalkmetoden til at acceptere et Animalobjekt som en parameter. Inde i metoden kalder vi talkmetoden for det objekt. Så længe objekttypen er en Animaleller en underklasse af Animal, er compileren glad.

Java Virtual Machine (JVM) beslutter ved kørsel, hvilken metode der skal kaldes baseret på objektets klasse. Hvis objektet har en type Dog, påkalder JVM implementeringen, der siger "Woof!".

Dette betaler sig på to måder:

  1. Vi behøver kun at skrive en generel metode. Vi behøver ikke foretage nogen formskontrol.
  2. I fremtiden, hvis vi opretter en ny dyretype, behøver vi ikke ændre Interrogatorklassen.

Denne type polymorfisme kaldes overordnet.

Tilsidesættelse

Eksemplet, vi diskuterede, dækkede allerede det brede begreb overordnet. Lad os give en formel definition og mere detaljerede detaljer.

Overstyring er, når du opretter en anden implementering af den nøjagtige samme instansmetode (identisk metodesignatur) i en relateret klasse.

Ved kørsel vælges metoden af objekttypen . Dette er grunden til, at tilsidesættelse også kaldes runtime polymorphism.

Tilsidesættelse opnås ved at give en anden implementering af en metode i en underordnet klasse (underklasse), der er defineret i dens overordnede klasse (superklasse).

overordnet arv

Overstyring opnås også ved at tilvejebringe forskellige implementeringer af en metode defineret i en grænseflade.

overordnet interface

Regler for tilsidesættelse af en metode:

  1. Det skal være en metode, der er defineret gennem et IS-A-forhold (gennem extendseller implements). Dette er grunden til, at du måske finder det omtalt som undertype polymorfisme.
  2. Den skal have den samme argumentliste som den oprindelige metodedefinition.
  3. Den skal have den samme returtype eller en returtype, der er en underklasse af returtypen for den oprindelige metodedefinition.
  4. Det kan ikke have en mere begrænsende adgangsmodifikator.
  5. Det kan have en mindre begrænsende adgangsmodifikator.
  6. Det må ikke kaste en ny eller bredere kontrolleret undtagelse.
  7. Det kaster muligvis smallere, færre eller ingen kontrollerede undtagelser, for eksempel kan en metode, der erklærer en IOException, tilsidesættes af en metode, der erklærer en FileNotFoundException (fordi det er en underklasse af IOException ).
  8. Den overordnede metode kan kaste enhver ukontrolleret undtagelse, uanset om den tilsidesatte metode erklærer undtagelsen.

Anbefaling: Brug @override- kommentaren, når du tilsidesætter metoder. Det giver kompileringstidsfejlkontrol på metodesignaturen. Dette hjælper dig med at undgå at bryde ovenstående regler.

tilsidesætte annotering

Forbyder tilsidesættelse

Hvis du ikke vil have tilsidesat en metode, skal du erklære den som endelig.

class Account { public final void withdraw(double amount) { double newBalance = balance - amount; if(newBalance > 0){ balance = newBalance; } } } 

Statiske metoder

Du kan ikke tilsidesætte en statisk metode . Du opretter virkelig en uafhængig definition af metoden i en relateret klasse.

class A { public static void print() { System.out.println("in A"); } } class B extends A { public static void print() { System.out.println("in B"); } } class Test { public static void main(String[] args) { A myObject = new B(); myObject.print(); // prints “in A” } } 

At køre Testklassen i eksemplet ovenfor vil udskrive "i A". Dette viser, at tilsidesættelse ikke sker her.

Hvis du ændrer printmetoden i klasser Aog Bskal være en instansmetode ved at fjerne staticfra metodesignaturen og køre Testklassen igen, vil den i stedet udskrive "i B"! Tilsidesættelse sker nu.

Husk, at overordnet vælger metoden baseret på objekttypen, ikke variabeltypen. ?

Overbelastning (funktionel polymorfisme)

Overbelastning er, når du opretter forskellige versioner af den samme metode.

Navnet på metoden skal være den samme, men vi kan ændre parametrene

og returtype.

I Java's Math-klasse finder du mange eksempler på overbelastede metoder. Den maxfremgangsmåde er overbelastet for forskellige typer. I alle tilfælde returnerer det antallet med den højeste værdi fra de 2 angivne værdier, men det gør det for forskellige (ikke-relaterede) nummertyper.

overbelastning-max-eksempel

Den (reference) variable type er det, der bestemmer, hvilken overbelastet metode der vælges. Overbelastning sker på kompileringstidspunktet.

Overbelastede metoder giver mere fleksibilitet for folk, der bruger din klasse. Personer, der bruger din klasse, kan have data i forskellige formater eller have forskellige data tilgængelige for dem afhængigt af forskellige situationer i deres applikation.

For eksempel overbelaster List-klassen removemetoden. En liste er en ordnet samling af objekter. Så du vil måske fjerne et objekt på en bestemt position (indeks) på en liste. Eller du kender muligvis ikke placeringen og vil bare fjerne objektet, uanset hvor det er. Så derfor har den to versioner.

liste-overbelastede-metoder

Konstruktører kan også overbelastes.

For eksempel har scannerklassen mange forskellige input, der kan leveres til oprettelse af et objekt. Nedenfor er et lille øjebliksbillede af de konstruktører, der imødekommer dette.

constructor

Regler for overbelastning af en metode:

  1. Den skal have en anden argumenteliste.
  2. Det kan have en anden returtype.
  3. Det kan have forskellige adgangsmodifikatorer.
  4. Det kan kaste forskellige undtagelser.
  5. Metoder fra en superklasse kan overbelastes i en underklasse.

Forskelle mellem tilsidesættelse og overbelastning

  1. Overriding must be based on a method from an IS-A relationship, overloading doesn't have to be. Overloading can occur within a single class.
  2. Overridden methods are chosen based on the object type, whereas overloaded methods are chosen based on the (reference) variable type.
  3. Overriding occurs at run-time, while overloading occurs at compile-time.

Parametric polymorphism

Parameteric polymorphism is achieved through generics in Java.

Generics were added to the language in version 5.0. They were designed to extend Java's type system to allow "a type or method to operate on objects of various types while providing compile-time type safety".

Basically, a generic form of a class or method can have all of its types replaced.

A simple example is ArrayList. The class definition has a generic in it, and it is signified by . Some of the instance methods such as add use this generic type in their signatures.

definition af arraylist-klasse

Metoder til definition af arraylistedefinition

By providing a type in angle brackets when we create an ArrayList object, we fill in the generic references defined throughout the class. So, if we create an ArrayList with the Dog generic type, the add method will only accept a Dog object as an argument.

arraylist hundemetode signatur

There is a compile-time error if you try to add anything other than a Dog! If you use a code editor such as IntelliJ, you will get the red squiggly line to highlight your offense (as below).

kontrol af arraylistetype

Final Words

Polymorfisme er et vanskeligt emne at tage fat på, især når du er ny inden for programmering. Det tager noget tid at identificere de rigtige situationer for at bruge det i din kode.

Men når du først er fortrolig med det, vil du opdage, at det forbedrer din kode meget.

Foto Tilskrivning

Bannerfoto af Markus Spiske på Unsplash.