Polymorfisme i Java

1. Oversigt

Alle objektorienterede programmeringssprog (OOP) kræves for at udvise fire grundlæggende egenskaber: abstraktion, indkapsling, arv og polymorfisme.

I denne artikel dækker vi to kernetyper af polymorfisme: statisk polymorfisme eller kompileringstid og dynamisk eller runtimepolymorfisme. Statisk polymorfisme håndhæves ved kompileringstid, mens dynamisk polymorfisme realiseres ved kørselstid.

2. Statisk polymorfisme

Ifølge Wikipedia er statisk polymorfisme en efterligning af polymorfisme, der løses ved kompileringstidspunktet og dermed fjerner opslukning af virtuelle tabelopkøb.

For eksempel vores TextFile klasse i en filhåndteringsapp kan have tre metoder med den samme signatur af Læs() metode:

public class TextFile udvider GenericFile {// ... public String read () {returner this.getContent () .toString (); } public String read (int limit) {return this.getContent () .toString () .substring (0, limit); } offentlig strenglæsning (int start, int stop) {returner this.getContent () .toString (). substring (start, stop); }}

Under kodekompilering verificerer compileren, at alle påkald af Læs fremgangsmåde svarer til mindst en af ​​de tre metoder, der er defineret ovenfor.

3. Dynamisk polymorfisme

Med dynamisk polymorfisme er den Java Virtual Machine (JVM) håndterer påvisningen af ​​den passende metode, der skal udføres, når en underklasse er tildelt sin overordnede form. Dette er nødvendigt, fordi underklassen kan tilsidesætte nogle eller alle de metoder, der er defineret i den overordnede klasse.

Lad os i en hypotetisk filhåndteringsapp definere overordnet klasse for alle filer, der kaldes GenericFile:

offentlig klasse GenericFile {privat strengnavn; // ... public String getFileInfo () {return "Generic File Impl"; }}

Vi kan også implementere en ImageFile klasse, der udvider GenericFile men tilsidesætter getFileInfo () metode og tilføjer flere oplysninger:

offentlig klasse ImageFile udvider GenericFile {privat int højde; privat int bredde; // ... getters and setters public String getFileInfo () {returner "Image File Impl"; }}

Når vi opretter en forekomst af ImageFile og tildel det til en GenericFile klasse, er en implicit rollebesætning færdig. Imidlertid holder JVM en henvisning til den faktiske form for ImageFile.

Ovenstående konstruktion er analog med metodeoverstyring. Vi kan bekræfte dette ved at påberåbe os getFileInfo () metode ved:

public static void main (String [] args) {GenericFile genericFile = new ImageFile ("SampleImageFile", 200, 100, new BufferedImage (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0" ); logger.info ("File Info: \ n" + genericFile.getFileInfo ()); }

Som forventet, genericFile.getFileInfo () udløser getFileInfo () metode til ImageFile klasse som vist i output nedenfor:

Filinfo: Impl

4. Andre polymorfe egenskaber i Java

Ud over disse to hovedtyper af polymorfisme i Java er der andre egenskaber i Java-programmeringssproget, der udviser polymorfisme. Lad os diskutere nogle af disse egenskaber.

4.1. Tvang

Polymorf tvang handler om implicit typekonvertering udført af compileren for at forhindre typefejl. Et typisk eksempel ses i et heltal og en streng sammenkædning:

Streng str = “streng” + 2;

4.2. Operatør Overbelastning

Operatør eller metodeoverbelastning henviser til en polymorf karakteristik af samme symbol eller operatør, der har forskellige betydninger (former) afhængigt af sammenhængen.

For eksempel kan plus-symbolet (+) bruges til matematisk tilføjelse såvel som Snor sammenkædning. I begge tilfælde er det kun kontekst (dvs. argumenttyper), der bestemmer fortolkningen af ​​symbolet:

Streng str = "2" + 2; int sum = 2 + 2; System.out.printf ("str =% s \ n sum =% d \ n", str, sum);

Produktion:

str = 22 sum = 4

4.3. Polymorfe parametre

Parametrisk polymorfisme tillader, at et navn på en parameter eller metode i en klasse associeres med forskellige typer. Vi har et typisk eksempel nedenfor, hvor vi definerer indhold som en Snor og senere som en Heltal:

offentlig klasse TextFile udvider GenericFile {privat strengindhold; public String setContentDelimiter () {int content = 100; this.content = this.content + indhold; }}

Det er også vigtigt at bemærke det erklæring af polymorfe parametre kan føre til et problem kendt somvariabel skjulning hvor en lokal erklæring af en parameter altid tilsidesætter den globale erklæring for en anden parameter med samme navn.

For at løse dette problem tilrådes det ofte at bruge globale referencer som f.eks det her nøgleord for at pege på globale variabler inden for en lokal sammenhæng.

4.4. Polymorfe undertyper

Polymorf subtype gør det let for os at tildele flere undertyper til en type og forventer, at alle påkaldelser på typen udløser de tilgængelige definitioner i undertypen.

For eksempel hvis vi har en samling af GenericFiles og vi påberåber os få information() metode på hver af dem, kan vi forvente, at output skal være forskelligt afhængigt af undertypen, hvorfra hvert element i samlingen blev afledt:

GenericFile [] files = {new ImageFile ("SampleImageFile", 200, 100, new BufferedImage (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0"), new TextFile ("SampleTextFile" , "Dette er et eksempel på tekstindhold", "v1.0.0")}; for (int i = 0; i <files.length; i ++) {filer [i] .getInfo (); }

Undertype polymorfisme er muliggjort af en kombination afupcasting og sen binding. Upcasting involverer casting af arvshierarki fra en supertype til en undertype:

ImageFile imageFile = ny ImageFile (); GenericFile-fil = imageFile;

Den resulterende effekt af ovenstående er, at ImageFile-specifikke metoder kan ikke påberåbes på den nye upcast GenericFile. Metoder i undertypen tilsidesætter dog lignende metoder, der er defineret i supertypen.

For at løse problemet med ikke at være i stand til at påberåbe sig subtypespecifikke metoder, når vi udstikker til en supertype, kan vi lave en nedkastning af arven fra en supertype til en undertype. Dette gøres ved:

ImageFile imageFile = (ImageFile) fil;

Sen bindingstrategi hjælper kompilatoren med at løse, hvis metode der skal udløses efter opkastning. I tilfælde af imageFile # getInfo vs. fil # getInfo i ovenstående eksempel beholder compileren en henvisning til ImageFile'S få information metode.

5. Problemer med polymorfisme

Lad os se på nogle uklarheder i polymorfisme, der potentielt kan føre til runtime-fejl, hvis de ikke kontrolleres korrekt.

5.1. Typeidentifikation under downcasting

Husk at vi tidligere mistede adgangen til nogle subtypespecifikke metoder efter at have udført en upcast. Selvom vi var i stand til at løse dette med en downcast, garanterer dette ikke den faktiske typekontrol.

For eksempel, hvis vi udfører en upcast og efterfølgende downcast:

GenericFile-fil = ny GenericFile (); ImageFile imageFile = (ImageFile) fil; System.out.println (imageFile.getHeight ());

Vi bemærker, at compileren tillader en downcast af en GenericFile ind i en ImageFile, selvom klassen faktisk er en GenericFile og ikke en ImageFile.

Derfor, hvis vi forsøger at påberåbe os getHeight () metode til imageFile klasse, får vi en ClassCastException som GenericFile definerer ikke getHeight () metode:

Undtagelse i tråden "main" java.lang.ClassCastException: GenericFile kan ikke kastes til ImageFile

For at løse dette problem udfører JVM en RTTI-kontrol (Run-Time Type Information Information). Vi kan også forsøge en eksplicit typeidentifikation ved hjælp af forekomst af nøgleord ligesom dette:

ImageFile imageFile; hvis (filforekomst af ImageFile) {imageFile = fil; }

Ovenstående hjælper med at undgå a ClassCastException undtagelse ved runtime. En anden mulighed, der kan bruges, er indpakning af rollebesætningen i en prøve og fangst blokere og fange ClassCastException.

Det skal bemærkes, at RTTI-kontrol er dyr på grund af den tid og ressourcer, der er nødvendige for effektivt at kontrollere, at en type er korrekt. Derudover hyppig brug af forekomst af nøgleord næsten altid indebærer et dårligt design.

5.2. Skrøbeligt grundklasseproblem

Ifølge Wikipedia betragtes base eller superklasser som skrøbelige, hvis tilsyneladende sikre ændringer til en basisklasse kan medføre, at afledte klasser ikke fungerer korrekt.

Lad os overveje en erklæring om en superklasse kaldet GenericFile og dens underklasse TextFile:

offentlig klasse GenericFile {privat strengindhold; ugyldig writeContent (strengindhold) {this.content = indhold; } ugyldig toString (String str) {str.toString (); }}
offentlig klasse TextFile udvider GenericFile {@Override ugyldig writeContent (strengindhold) {toString (indhold); }}

Når vi ændrer GenericFile klasse:

public class GenericFile {// ... void toString (String str) {writeContent (str); }}

Vi bemærker, at ovenstående ændring forlader TextFile i en uendelig rekursion i writeContent () metode, som til sidst resulterer i et stackoverløb.

For at løse et skrøbeligt problem i basisklassen kan vi bruge endelig nøgleord for at forhindre, at underklasser tilsidesætter writeContent () metode. Korrekt dokumentation kan også hjælpe. Og sidst men ikke mindst, bør sammensætningen generelt foretrækkes frem for arv.

6. Konklusion

I denne artikel diskuterede vi det grundlæggende begreb polymorfisme med fokus på både fordele og ulemper.

Som altid er kildekoden til denne artikel tilgængelig på GitHub.