Polymorfisme i Java

1. Oversikt

Alle objektorienterte programmeringsspråk (OOP) er pålagt å ha fire grunnleggende egenskaper: abstraksjon, innkapsling, arv og polymorfisme.

I denne artikkelen dekker vi to kjernetyper av polymorfisme: statisk eller kompileringstid polymorfisme og dynamisk eller kjøretidpolymorfisme. Statisk polymorfisme håndheves ved kompileringstid mens dynamisk polymorfisme realiseres ved kjøretid.

2. Statisk polymorfisme

I følge Wikipedia er statisk polymorfisme en etterligning av polymorfisme som løses ved kompileringstid og dermed fjerner oppslagsvinduer for virtuelle tabeller.

For eksempel vår TextFile klasse i en filbehandling-app kan ha tre metoder med samme signatur av lese() metode:

public class TextFile utvider GenericFile {// ... public String read () {return this.getContent () .toString (); } public String read (int limit) {return this.getContent () .toString () .substring (0, limit); } public String read (int start, int stop) {return this.getContent () .toString () .substring (start, stop); }}

Under kodekompilering verifiserer kompilatoren at alle påkallinger av lese metoden tilsvarer minst en av de tre metodene som er definert ovenfor.

3. Dynamisk polymorfisme

Med dynamisk polymorfisme, er Java Virtual Machine (JVM) håndterer påvisning av riktig metode for å utføre når en underklasse er tilordnet det overordnede skjemaet. Dette er nødvendig fordi underklassen kan overstyre noen eller alle metodene som er definert i foreldreklassen.

I en hypotetisk filbehandling-app, la oss definere overordnet klasse for alle filer som heter GenericFile:

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

Vi kan også implementere en ImageFile klasse som utvider GenericFile men overstyrer getFileInfo () metode og legger til mer informasjon:

offentlig klasse ImageFile utvider GenericFile {private int høyde; privat int bredde; // ... getters and setters public String getFileInfo () {return "Image File Impl"; }}

Når vi oppretter en forekomst av ImageFile og tilordne den til en GenericFile klasse, er en implisitt rollebesetning gjort. Imidlertid holder JVM en referanse til den faktiske formen for ImageFile.

Ovennevnte konstruksjon er analog med metodeoverstyring. Vi kan bekrefte dette ved å påkalle 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 ("Filinfo: \ n" + genericFile.getFileInfo ()); }

Som forventet, genericFile.getFileInfo () utløser getFileInfo () metoden for ImageFile klasse som vist i utdataene nedenfor:

File Info: Image File Impl

4. Andre polymorfe egenskaper i Java

I tillegg til disse to hovedtyper av polymorfisme i Java, er det andre egenskaper i Java-programmeringsspråket som viser polymorfisme. La oss diskutere noen av disse egenskapene.

4.1. Tvang

Polymorf tvang håndterer implisitt typekonvertering utført av kompilatoren for å forhindre typefeil. Et typisk eksempel ses i et heltall og streng sammenkobling:

Streng str = “streng” + 2;

4.2. Overbelastning av operatør

Overbelastning av operatør eller metode refererer til en polymorf karakteristikk av samme symbol eller operatør som har forskjellige betydninger (former) avhengig av konteksten.

For eksempel kan plussymbolet (+) også brukes til matematisk tillegg String sammenkobling. I begge tilfeller er det bare kontekst (dvs. argumenttyper) som bestemmer tolkningen av symbolet:

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

Produksjon:

str = 22 sum = 4

4.3. Polymorfe parametere

Parametrisk polymorfisme tillater at et navn på en parameter eller metode i en klasse assosieres med forskjellige typer. Vi har et typisk eksempel nedenfor der vi definerer innhold som en String og senere som en Heltall:

offentlig klasse TextFile utvider GenericFile {private strenginnhold; public String setContentDelimiter () {int content = 100; this.content = this.content + innhold; }}

Det er også viktig å merke seg det erklæring av polymorfe parametere kan føre til et problem kjent somvariabelt skjul der en lokal deklarasjon av en parameter alltid overstyrer den globale erklæringen til en annen parameter med samme navn.

For å løse dette problemet, anbefales det ofte å bruke globale referanser som f.eks dette nøkkelord for å peke på globale variabler innenfor en lokal sammenheng.

4.4. Polymorfe undertyper

Polymorf undertype gjør det enkelt for oss å tilordne flere undertyper til en type og forventer at alle påkallelser på typen utløser tilgjengelige definisjoner i undertypen.

For eksempel hvis vi har en samling av GenericFiles og vi påkaller få informasjon() metode på hver av dem, kan vi forvente at utdataene vil være forskjellige, avhengig av undertypen som hvert element i samlingen ble hentet fra:

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å tekstinnhold", "v1.0.0")}; for (int i = 0; i <files.length; i ++) {files [i] .getInfo (); }

Undertype polymorfisme er mulig ved en kombinasjon avoppkast og sen binding. Upcasting innebærer avstøpning av arvshierarki fra en supertype til en undertype:

ImageFile imageFile = ny ImageFile (); GenericFile file = imageFile;

Den resulterende effekten av det ovennevnte er at ImageFile-spesifikke metoder kan ikke påberopes på den nye upcast GenericFile. Imidlertid overstyrer metoder i undertypen lignende metoder som er definert i supertypen.

For å løse problemet med ikke å kunne påkalle subtypespesifikke metoder når vi kaster opp til en supertype, kan vi gjøre en nedkastning av arven fra en supertype til en undertype. Dette gjøres av:

ImageFile imageFile = (ImageFile) fil;

Sen bindingstrategi hjelper kompilatoren med å løse hvilken metode som skal utløses etter oppkast. I tilfelle av imageFile # getInfo vs. fil # getInfo i eksemplet ovenfor holder kompilatoren en referanse til ImageFile‘S få informasjon metode.

5. Problemer med polymorfisme

La oss se på noen uklarheter i polymorfisme som potensielt kan føre til kjøretidsfeil hvis de ikke er riktig kontrollert.

5.1. Typeidentifikasjon under nedkasting

Husk at vi tidligere mistet tilgangen til noen subtypespesifikke metoder etter å ha utført en upcast. Selv om vi klarte å løse dette med en nedstøt, garanterer ikke dette faktisk typekontroll.

For eksempel hvis vi utfører en upcast og påfølgende downcast:

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

Vi merker at kompilatoren tillater nedtasting av a GenericFile inn i en ImageFile, selv om klassen faktisk er en GenericFile og ikke en ImageFile.

Følgelig, hvis vi prøver å påberope oss getHeight () metoden på imageFile klasse, får vi en ClassCastException som GenericFile definerer ikke getHeight () metode:

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

For å løse dette problemet utfører JVM en RTTI-sjekk (Run-Time Type Information). Vi kan også prøve en eksplisitt typeidentifikasjon ved å bruke tilfelle av nøkkelord akkurat slik:

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

Ovennevnte bidrar til å unngå a ClassCastException unntak ved kjøretid. Et annet alternativ som kan brukes er å pakke inn rollebesetningen i en prøve og å fange blokkere og fange ClassCastException.

Det er verdt å merke seg at RTTI-sjekk er dyrt på grunn av tiden og ressursene som trengs for å effektivt bekrefte at en type er riktig. I tillegg er hyppig bruk av tilfelle av nøkkelord innebærer nesten alltid et dårlig design.

5.2. Skjørt grunnklasseproblem

I følge Wikipedia betraktes base eller superklasser som skjøre hvis tilsynelatende sikre modifikasjoner av en basisklasse kan føre til at avledede klasser ikke fungerer.

La oss vurdere en erklæring om en superklasse som heter GenericFile og dens underklasse TextFile:

offentlig klasse GenericFile {privat strenginnhold; ugyldig writeContent (strenginnhold) {this.content = innhold; } ugyldig toString (String str) {str.toString (); }}
offentlig klasse TextFile utvider GenericFile {@Override void writeContent (String content) {toString (content); }}

Når vi endrer GenericFile klasse:

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

Vi observerer at modifikasjonen ovenfor går TextFile i en uendelig rekursjon i skriv innhold () metode, som til slutt resulterer i en stack overflow.

For å løse et skjørt problem i baseklassen kan vi bruke endelig nøkkelord for å forhindre at underklasser overstyrer skriv innhold () metode. Riktig dokumentasjon kan også hjelpe. Og sist men ikke minst, bør sammensetningen generelt foretrekkes fremfor arv.

6. Konklusjon

I denne artikkelen diskuterte vi det grunnleggende begrepet polymorfisme, med fokus på både fordeler og ulemper.

Som alltid er kildekoden for denne artikkelen tilgjengelig på GitHub.