Polymorfisme henviser til nogle enheders evne til at forekomme i forskellige former. Det er populært repræsenteret af sommerfuglen, der skifter fra larve til puppe til imago. Polymorfisme findes også i programmeringssprog som en modelleringsteknik, der giver dig mulighed for at oprette en enkelt grænseflade til forskellige operander, argumenter og objekter. Java polymorfisme resulterer i kode, der er mere kortfattet og lettere at vedligeholde.
Mens denne vejledning fokuserer på undertype polymorfisme, er der flere andre typer, du bør vide om. Vi starter med en oversigt over alle fire typer polymorfisme.
download Hent koden Download kildekoden for eksempel applikationer i denne vejledning. Oprettet af Jeff Friesen til JavaWorld.Typer af polymorfisme i Java
Der er fire typer polymorfisme i Java:
- Tvang er en operation, der tjener flere typer gennem implicit-konvertering. For eksempel deler du et heltal med et andet heltal eller en flydende punktværdi med en anden flydende punktværdi. Hvis den ene operand er et heltal, og den anden operand er en flydende punktværdi, kompilatoren tvinger (konverterer implicit) heltal til en flydende punktværdi for at forhindre en typefejl. (Der er ingen delingsoperation, der understøtter et heltal-operand og en floand-point-operand.) Et andet eksempel er at sende en underklasse-objektreference til en metodes superklasseparameter. Compileren tvinger underklassetypen til superklassetypen for at begrænse operationerne til superklassens.
- Overbelastning henviser til at bruge det samme operatørsymbol eller metodenavn i forskellige sammenhænge. For eksempel kan du bruge
+
for at udføre tilføjelse af heltal, tilføjelse med flydende punkt eller sammenkædning af streng afhængigt af typerne af dets operander. Flere metoder med samme navn kan også vises i en klasse (gennem erklæring og / eller arv). - Parametrisk polymorfisme bestemmer, at inden for en klassedeklaration kan et feltnavn associeres med forskellige typer, og et metodenavn kan associeres med forskellige parameter- og returtyper. Feltet og metoden kan derefter påtage sig forskellige typer i hver klasseinstans (objekt). For eksempel kan et felt være af typen
Dobbelt
(et medlem af Java's standard klassebibliotek, der omslutter endobbelt
værdi) og en metode kan returnere enDobbelt
i et objekt, og det samme felt kan være af typenSnor
og den samme metode kan returnere enSnor
i et andet objekt. Java understøtter parametrisk polymorfisme via generiske lægemidler, som jeg vil diskutere i en fremtidig artikel. - Undertype betyder, at en type kan tjene som en anden type undertype. Når en undertype-forekomst vises i en supertypekontekst, resulterer udførelsen af en supertype-handling på undertype-forekomsten i, at undertypens version af den operation udføres. Overvej f.eks. Et fragment af kode, der tegner vilkårlige former. Du kan udtrykke denne tegningskode mere kortfattet ved at indføre en
Form
klasse med entegne()
metode; ved at introducereCirkel
,Rektangel
og andre underklasser, der tilsidesættertegne()
; ved at introducere en række typerForm
hvis elementer gemmer henvisninger tilForm
underklasseforekomster; og ved at ringeForm
'stegne()
metode på hver forekomst. Når du ringertegne()
, Det erCirkel
's,Rektangel
eller andetForm
instanstegne()
metode, der kaldes. Vi siger, at der er mange former forForm
'stegne()
metode.
Denne tutorial introducerer undertype polymorfisme. Du lærer om opkastning og sen binding, abstrakte klasser (som ikke kan instantieres) og abstrakte metoder (som ikke kan kaldes). Du lærer også om downcasting og identifikation af runtime-typen, og du får et første kig på kovariante returtyper. Jeg gemmer parametrisk polymorfisme til en fremtidig tutorial.
Ad-hoc vs universel polymorfisme
Som mange udviklere klassificerer jeg tvang og overbelastning som ad hoc polymorfisme og parametrisk og undertype som universel polymorfisme. Selvom værdifulde teknikker, tror jeg ikke, at tvang og overbelastning er ægte polymorfisme; de er mere som typeomdannelser og syntaktisk sukker.
Undertype polymorfisme: Upcasting og sen binding
Undertype polymorfisme er afhængig af opkastning og sen binding. Upcasting er en form for casting, hvor du caster arvshierarkiet fra en undertype til en supertype. Ingen rollebesætningsmedlemmer er involveret, fordi undertypen er en specialisering af supertypen. For eksempel, Form s = ny cirkel ();
upcasts fra Cirkel
til Form
. Dette giver mening, fordi en cirkel er en slags form.
Efter opkastning Cirkel
til Form
, du kan ikke ringe Cirkel
-specifikke metoder, såsom en getRadius ()
metode, der returnerer cirkelens radius, fordi Cirkel
-specifikke metoder er ikke en del af Form
interface. At miste adgang til undertypefunktioner efter at indsnævre en underklasse til sin superklasse virker meningsløs, men er nødvendig for at opnå undertype polymorfisme.
Antag at Form
erklærer en tegne()
metode, dens Cirkel
underklasse tilsidesætter denne metode, Form s = ny cirkel ();
har netop udført, og den næste linje specificerer s. tegne ();
. Hvilken tegne()
metoden kaldes: Form
's tegne()
metode eller Cirkel
's tegne()
metode? Compileren ved ikke hvilken tegne()
metode til at ringe til. Alt, hvad det kan gøre, er at kontrollere, at der findes en metode i superklassen, og kontrollere, at metodekaldets argumenteliste og returtype matcher superklassens metodedeklaration. Imidlertid indsætter compileren også en instruktion i den kompilerede kode, der ved kørsel henter og bruger den reference, der er i s
at kalde det rigtige tegne()
metode. Denne opgave er kendt som sen binding.
Sen binding vs tidlig binding
Sen binding bruges til opkald til ikke-endelig
instansmetoder. For alle andre metodeopkald ved compileren, hvilken metode der skal kaldes. Den indsætter en instruktion i den kompilerede kode, der kalder den metode, der er knyttet til variabelens type og ikke dens værdi. Denne teknik er kendt som tidlig binding.
Jeg har oprettet en applikation, der demonstrerer undertype polymorfisme med hensyn til opkastning og sen binding. Denne applikation består af Form
, Cirkel
, Rektangel
og Former
klasser, hvor hver klasse er gemt i sin egen kildefil. Liste 1 viser de første tre klasser.
Fortegnelse 1. Erklæring om et hierarki af figurer
class Shape {void draw () {}} class Circle extends Shape {private int x, y, r; Cirkel (int x, int y, int r) {this.x = x; this.y = y; this.r = r; } // For kortfattethed har jeg udeladt getX (), getY () og getRadius () metoder. @ Override ugyldig tegning () {System.out.println ("Tegningscirkel (" + x + "," + y + "," + r + ")"); }} klasse Rektangel udvider figur {privat int x, y, w, h; Rektangel (int x, int y, int w, int h) {this.x = x; this.y = y; this.w = w; this.h = h; } // For kortfattethed har jeg udeladt getX (), getY (), getWidth () og getHeight () // metoder. @ Override ugyldig tegning () {System.out.println ("Tegningsrektangel (" + x + "," + y + "," + w + "," + h + ")"); }}
Liste 2 præsenterer Former
ansøgningsklasse hvis hoved ()
metode driver applikationen.
Listing 2. Upcasting og sen binding i undertype polymorfisme
klasse Shapes {public static void main (String [] args) {Shape [] shapes = {new Circle (10, 20, 30), new Rectangle (20, 30, 40, 50)}; for (int i = 0; i <shapes.length; i ++) former [i] .draw (); }}
Erklæringen fra former
array viser upcasting. Det Cirkel
og Rektangel
referencer er gemt i figurer [0]
og figurer [1]
og er upcast for at skrive Form
. Hver af figurer [0]
og figurer [1]
betragtes som en Form
eksempel: figurer [0]
betragtes ikke som en Cirkel
; figurer [1]
betragtes ikke som en Rektangel
.
Sen binding demonstreres af figurer [i] .draw ();
udtryk. Hvornår jeg
lige med 0
, forårsager den compiler-genererede instruktion Cirkel
's tegne()
metode, der skal kaldes. Hvornår jeg
lige med 1
dog forårsager denne instruktion Rektangel
's tegne()
metode, der skal kaldes. Dette er essensen af undertype polymorfisme.
Forudsat at alle fire kildefiler (Shapes.java
, Form.java
, Rektangel.java
og Cirkel.java
) findes i den aktuelle mappe, kompiler dem via en af følgende kommandolinjer:
javac * .java javac Shapes.java
Kør den resulterende applikation:
java figurer
Du skal overholde følgende output:
Tegningscirkel (10, 20, 30) Tegningsrektangel (20, 30, 40, 50)
Abstrakte klasser og metoder
Når du designer klassehierarkier, finder du ud af, at klasser nærmere toppen af disse hierarkier er mere generiske end klasser, der er lavere. F.eks Køretøj
superklasse er mere generisk end en Lastbil
underklasse. Tilsvarende er en Form
superklasse er mere generisk end en Cirkel
eller a Rektangel
underklasse.
Det giver ikke mening at starte en generisk klasse. Når alt kommer til alt, hvad ville en Køretøj
objekt beskrive? Tilsvarende, hvilken slags form er repræsenteret af en Form
objekt? I stedet for at kode et tomt tegne()
metode i Form
, kan vi forhindre, at denne metode kaldes, og at denne klasse instantieres ved at erklære begge enheder for at være abstrakte.
Java leverer abstrakt
reserveret ord til at erklære en klasse, der ikke kan instantieres. Compileren rapporterer en fejl, når du prøver at instantiere denne klasse. abstrakt
bruges også til at erklære en metode uden lig. Det tegne()
metoden behøver ikke en krop, fordi den ikke er i stand til at tegne en abstrakt form. Liste 3 viser.
Listing 3. Abstraktion af Shape-klassen og dens draw () -metode
abstrakt klasse Form {abstrakt ugyldigt tegn (); // semikolon er påkrævet}
Abstrakte advarsler
Compileren rapporterer en fejl, når du forsøger at erklære en klasse abstrakt
og endelig
. For eksempel klager compileren over abstrakt endelig klasse Form
fordi en abstrakt klasse ikke kan instantieres, og en endelig klasse ikke kan udvides. Compileren rapporterer også en fejl, når du erklærer en metode abstrakt
men erklær ikke sin klasse abstrakt
. Fjerner abstrakt
fra Form
klassens overskrift i liste 3 vil f.eks. resultere i en fejl. Dette ville være en fejl, fordi en ikke-abstrakt (konkret) klasse ikke kan instantieres, når den indeholder en abstrakt metode. Endelig, når du udvider en abstrakt klasse, skal den udvidende klasse tilsidesætte alle de abstrakte metoder, ellers skal den udvidende klasse selv erklæres for at være abstrakt; Ellers rapporterer compileren en fejl.
En abstrakt klasse kan erklære felter, konstruktører og ikke-abstrakte metoder ud over eller i stedet for abstrakte metoder. For eksempel et abstrakt Køretøj
klasse kan erklære felter, der beskriver mærke, model og år. Det kan også erklære en konstruktør til at initialisere disse felter og konkrete metoder til at returnere deres værdier. Tjek Listing 4.
Notering 4. Abstraktion af et køretøj
abstrakt klasse Vehicle {private String mærke, model; privat int år; Vehicle (String make, String model, int year) {this.make = make; this.model = model; dette.år = år; } String getMake () {return make; } String getModel () {returmodel; } int getYear () {returår; } abstrakt ugyldigt træk (); }
Du bemærker det Køretøj
erklærer et abstrakt bevæge sig()
metode til at beskrive et køretøjs bevægelse. For eksempel ruller en bil ned ad vejen, en båd sejler over vandet, og et fly flyver gennem luften. Køretøj
's underklasser ville tilsidesætte bevæge sig()
og give en passende beskrivelse. De ville også arve metoderne, og deres konstruktører ville kalde Køretøj
s konstruktør.
Downcasting og RTTI
At bevæge sig op i klassehierarkiet via upcasting indebærer at miste adgangen til undertypefunktioner. For eksempel tildele en Cirkel
protesterer mod Form
variabel s
betyder, at du ikke kan bruge s
at ringe Cirkel
's getRadius ()
metode. Det er dog muligt igen at få adgang Cirkel
's getRadius ()
metode ved at udføre en eksplicit rollebesætning som denne: Cirkel c = (Cirkel) s;
.
Denne opgave er kendt som nedkastning fordi du kaster ned arvshierarkiet fra en supertype til en undertype (fra Form
superklasse til Cirkel
underklasse). Selvom en upcast altid er sikker (superklassens interface er en delmængde af subklassens interface), er en downcast ikke altid sikker. Fortegnelse 5 viser, hvilken slags problemer der kan opstå, hvis du bruger downcasting forkert.
Liste 5. Problemet med downcasting
klasse Superklasse {} klasse Underklasse udvider Superklasse {ugyldig metode () {}} offentlig klasse BadDowncast {offentlig statisk ugyldig hoved (String [] args) {Superklasse superklasse = ny Superklasse (); Underklasse underklasse = (Underklasse) superklasse; underklasse. metode (); }}
Listing 5 præsenterer et klassehierarki bestående af Superklasse
og Underklasse
, som strækker sig Superklasse
. Desuden, Underklasse
erklærer metode()
. En tredje klasse navngivet Dårlig udsendelse
giver en hoved ()
metode, der instantierer Superklasse
. Dårlig udsendelse
forsøger derefter at nedsky dette objekt til Underklasse
og tildel resultatet til variabel underklasse
.
I dette tilfælde vil compileren ikke klage, fordi downcasting fra en superklasse til en underklasse i samme typehierarki er lovlig. Når det er sagt, hvis opgaven var tilladt, ville applikationen gå ned, når den forsøgte at udføre underklasse. metode ();
. I dette tilfælde vil JVM forsøge at kalde en ikke-eksisterende metode, fordi Superklasse
erklærer ikke metode()
. Heldigvis verificerer JVM, at en rollebesætning er lovlig, før den udfører en rollebesætning. At opdage det Superklasse
erklærer ikke metode()
, det ville kaste en ClassCastException
objekt. (Jeg diskuterer undtagelser i en fremtidig artikel.)
Kompilér liste 5 som følger:
javac BadDowncast.java
Kør den resulterende applikation:
java BadDowncast