Polimorfizem se nanaša na sposobnost nekaterih entitet, da se pojavljajo v različnih oblikah. V popularnosti ga predstavlja metulj, ki se preobrazi od ličinke do lutke do imaga. Polimorfizem obstaja tudi v programskih jezikih kot tehnika modeliranja, ki vam omogoča, da ustvarite en sam vmesnik za različne operande, argumente in predmete. Rezultat polimorfizma Java je koda, ki je bolj jedrnata in enostavnejša za vzdrževanje.
Čeprav se ta vadnica osredotoča na polimorfizem podtipov, morate vedeti še nekaj drugih. Začeli bomo s pregledom vseh štirih vrst polimorfizma.
prenos Prenesite kodo Prenesite izvorno kodo, na primer programe v tej vadnici. Ustvaril Jeff Friesen za JavaWorld.Vrste polimorfizma v Javi
V Javi obstajajo štiri vrste polimorfizma:
- Prisila je operacija, ki služi implicitnim pretvorbam več vrst. Celo število na primer delite z drugim celim številom ali vrednost s plavajočo vejico z drugo vrednostjo s plavajočo vejico. Če je en operand celo število, drugi pa vrednost s plavajočo vejico, je prevajalnik prisile (implicitno pretvori) celo število v vrednost s plavajočo vejico, da se prepreči napaka tipa. (Nobena operacija delitve ne podpira celoštevilčnega operanda in operanda s plavajočo vejico.) Drug primer je posredovanje sklica na objekt podrazreda na parameter superklase metode. Prevajalnik prisili tip podrazreda na tip superrazreda, da omeji operacije na nadrazred.
- Preobremenitev se nanaša na uporabo istega operaterskega simbola ali imena metode v različnih kontekstih. Na primer, lahko uporabite
+
za izvedbo seštevanja celih števil, dodajanja s plavajočo vejico ali združevanja nizov, odvisno od vrst njegovih operandov. V razredu se lahko pojavi tudi več metod z istim imenom (z izjavo in / ali dedovanjem). - Parametrično polimorfizem določa, da se lahko v izjavi razreda ime polja poveže z različnimi tipi, ime metode pa z različnimi vrstami parametrov in vrnitev. Nato lahko polje in metoda prevzameta različne tipe v vsakem primerku razreda (predmeta). Polje je na primer lahko vrste
Dvojno
(član standardne knjižnice razredov Java, ki zavije advojno
vrednost) in metoda lahko vrne aDvojno
v enem predmetu, isto polje pa je lahko tipaVrvica
in ista metoda lahko vrne aVrvica
v drugem predmetu. Java s pomočjo generikov podpira parametrični polimorfizem, o čemer bom razpravljal v prihodnjem članku. - Podtip pomeni, da lahko tip služi kot podtip drugega tipa. Ko se primerek podtipa pojavi v kontekstu nadtipa, izvajanje operacije nadtipa na primerku podtipa povzroči izvedbo različice podtipa te operacije. Na primer, razmislite o fragmentu kode, ki nariše poljubne oblike. To risbeno kodo lahko izrazite bolj jedrnato z uvedbo a
Oblika
razred z ažreb ()
metoda; z uvajanjemKrog
,Pravokotnik
in drugi podrazredi, ki preglasijožreb ()
; z uvedbo polja tipaOblika
katerih elementi shranjujejo reference naOblika
primeri podrazreda; in s klicemOblika
ježreb ()
za vsak primerek. Ko pokličetežreb ()
, to jeKrog
je,Pravokotnik
ali drugačeOblika
primerkižreb ()
metoda, ki se pokliče. Pravimo, da obstaja veliko oblikOblika
ježreb ()
metoda.
Ta vadnica predstavlja polimorfizem podtipa. Spoznali boste posodobitve in pozno vezavo, abstraktne razrede (ki jih ni mogoče instancirati) in abstraktne metode (ki jih ni mogoče poklicati). Spoznali boste tudi informacije o prenosu in identifikaciji tipa izvajalnega okolja ter si prvič ogledali kovariante vrst vrnitve. Parametrični polimorfizem bom shranil za prihodnjo vadnico.
Ad-hoc vs univerzalni polimorfizem
Kot mnoge razvijalce prisilo in preobremenitev uvrščam med ad hoc polimorfizem, parametrične in podtipe pa kot univerzalni polimorfizem. Čeprav sta dragoceni tehniki, ne verjamem, da sta prisila in preobremenitev resnični polimorfizem; so bolj podobni pretvorbi vrst in skladenjskemu sladkorju.
Podtip polimorfizma: posodobitev in pozna vezava
Podtip polimorfizma temelji na posodabljanju in pozni vezavi. Upcasting je oblika predvajanja, pri kateri razvrstite hierarhijo dedovanja iz podtipa v nadtip. Noben operater zasedbe ni vključen, ker je podtip specializacija nadtipa. Na primer, Oblika s = nov krog ();
posodobitve iz Krog
do Oblika
. To je smiselno, ker je krog neke vrste oblika.
Po posodobitvi Krog
do Oblika
, ne morete poklicati Krog
-specifične metode, kot je a getRadius ()
metoda, ki vrne polmer kroga, ker Krog
-specifične metode niso del Oblika
je vmesnik. Izguba dostopa do funkcij podtipa po zožitvi podrazreda na njegov superrazred se zdi nesmiselna, vendar je nujna za doseganje polimorfizma podtipa.
Recimo, da Oblika
izjavlja a žreb ()
metoda, njegova Krog
podrazred preglasi to metodo, Oblika s = nov krog ();
je pravkar izvedena, naslednja vrstica pa določa s. risanje ();
. Kateri žreb ()
metoda se imenuje: Oblika
je žreb ()
metoda oz Krog
je žreb ()
metoda? Prevajalnik ne ve, kateri žreb ()
način klica. Vse, kar lahko naredi, je preveriti, ali metoda obstaja v superklasi, in preveriti, ali se seznam argumentov in tip vrnitve klica metode ujema z izjavo metode superklase. Vendar pa prevajalnik v prevedeno kodo vstavi tudi navodilo, ki med izvajanjem pridobi in uporabi vse sklice, ki jih vsebuje s
da pokličete pravilno žreb ()
metoda. Ta naloga je znana kot pozna vezava.
Pozna vezava vs zgodnja vezava
Pozna vezava se uporablja za klicedokončno
primerov. Za vse druge klice metode prevajalnik ve, katero metodo naj pokliče. V prevedeno kodo vstavi navodilo, ki pokliče metodo, povezano s tipom spremenljivke, in ne z njeno vrednostjo. Ta tehnika je znana kot zgodnja vezava.
Ustvaril sem aplikacijo, ki prikazuje polimorfizem podtipa v smislu posodobitve in pozne vezave. Ta aplikacija je sestavljena iz Oblika
, Krog
, Pravokotnik
, in Oblike
razredi, kjer je vsak razred shranjen v svoji izvorni datoteki. Seznam 1 predstavlja prve tri razrede.
Seznam 1. Izjava o hierarhiji oblik
class Shape {void draw () {}} class Circle extends Shape {private int x, y, r; Krog (int x, int y, int r) {this.x = x; to.y = y; to.r = r; } // Zaradi kratkosti sem izpustil metode getX (), getY () in getRadius (). @Override void draw () {System.out.println ("Risalni krog (" + x + "," + y + "," + r + ")"); }} razred Rectangle razširja Shape {private int x, y, w, h; Pravokotnik (int x, int y, int w, int h) {this.x = x; to.y = y; to.w = w; to.h = h; } // Zaradi kratkosti sem izpustil metode getX (), getY (), getWidth () in getHeight () //. @Override void draw () {System.out.println ("Risalni pravokotnik (" + x + "," + y + "," + w + "," + h + ")"); }}
Seznam 2 predstavlja Oblike
aplikacijski razred, katerega glavni ()
metoda poganja aplikacijo.
Seznam 2. Nadgrajevanje in pozna vezava pri polimorfizmu podtipa
class Shapes {public static void main (String [] args) {Shape [] shape = {new Circle (10, 20, 30), new Rectangle (20, 30, 40, 50)}; for (int i = 0; i <shape.length; i ++) oblik [i] .draw (); }}
Izjava oblike
matrika prikazuje upcasting. The Krog
in Pravokotnik
sklici so shranjeni v oblike [0]
in oblike [1]
in so posodobljeni za tipkanje Oblika
. Vsak od oblike [0]
in oblike [1]
se šteje za a Oblika
primer: oblike [0]
se ne šteje za Krog
; oblike [1]
se ne šteje za Pravokotnik
.
Pozno vezavo dokazuje oblike [i] .draw ();
izraz. Kdaj jaz
enako 0
, vzrok, ki ga ustvari prevajalnik Krog
je žreb ()
metoda, ki jo je treba poklicati. Kdaj jaz
enako 1
, vendar to navodilo povzroča Pravokotnik
je žreb ()
metoda, ki jo je treba poklicati. To je bistvo podtipa polimorfizma.
Ob predpostavki, da so vse štiri izvorne datoteke (Oblike.java
, Oblika.java
, Pravokotnik.java
, in Krog.java
) se nahajajo v trenutnem imeniku, jih prevedite v eno od naslednjih ukaznih vrstic:
javac * .java javac Shapes.java
Zaženite nastalo aplikacijo:
java Oblike
Upoštevati morate naslednje rezultate:
Risalni krog (10, 20, 30) Risalni pravokotnik (20, 30, 40, 50)
Abstraktni razredi in metode
Pri načrtovanju hierarhij razredov boste ugotovili, da so razredi bližje vrhu teh hierarhij bolj splošni kot razredi nižje. Na primer, a Vozilo
superrazred je bolj splošen kot a Tovornjak
podrazred. Podobno a Oblika
superrazred je bolj splošen kot a Krog
ali a Pravokotnik
podrazred.
Primer generičnega razreda ni smiselno. Konec koncev, kaj bi a Vozilo
objekt opisati? Podobno kakšno obliko predstavlja a Oblika
predmet? Namesto da bi prazno kodo žreb ()
metoda v Oblika
, lahko preprečimo klicanje te metode in instanciranje tega razreda z razglasitvijo obeh entitet za abstraktne.
Java ponuja povzetek
rezervirana beseda za razglasitev razreda, ki ga ni mogoče ustvariti. Prevajalnik poroča o napaki, ko poskusite primeriti ta razred. povzetek
se uporablja tudi za razglasitev metode brez telesa. The žreb ()
metoda ne potrebuje telesa, ker ne more narisati abstraktne oblike. Seznam 3 prikazuje.
Seznam 3. Povzemanje razreda Shape in metode draw ()
abstraktni razred Oblika {abstract void draw (); // podpičje je obvezno}
Povzetek opozorila
Prevajalnik poroča o napaki, ko poskušate razglasiti razred povzetek
in dokončno
. Prevajalnik se na primer pritožuje izvleček zaključni razred Oblika
ker abstraktnega razreda ni mogoče ustvariti in končnega razreda ni mogoče razširiti. Prevajalnik poroča tudi o napaki, ko prijavite metodo povzetek
vendar ne razglasite njegovega razreda povzetek
. Odstranjevanje povzetek
Iz Oblika
glava razreda na seznamu 3 bi na primer povzročila napako. To bi bila napaka, ker ne-abstraktnega (konkretnega) razreda ni mogoče ustvariti primerka, če vsebuje abstraktno metodo. Nazadnje, ko razširite abstraktni razred, mora razširitveni razred preglasiti vse abstraktne metode, sicer pa je treba razširitveni razred razglasiti za abstraktnega; v nasprotnem primeru bo prevajalnik sporočil napako.
Abstraktni razred lahko poleg ali namesto abstraktnih metod prijavi polja, konstruktorje in ne abstraktne metode. Na primer povzetek Vozilo
razred lahko navede polja, ki opisujejo njegovo znamko, model in leto. Prav tako lahko razglasi konstruktor za inicializacijo teh polj in konkretne metode za vrnitev njihovih vrednosti. Oglejte si seznam 4.
Seznam 4. Povzemanje vozila
abstraktni razred Vozilo {private String make, model; zasebno int leto; Vozilo (znamka niza, model niza, int leto) {this.make = znamka; this.model = model; this.year = leto; } String getMake () {return make; } String getModel () {return model; } int getYear () {vrnjeno leto; } abstraktna void poteza (); }
To boste opazili Vozilo
izjavlja izvleček premakni ()
metoda za opis gibanja vozila. Na primer, avto se povalja po cesti, čoln pluje po vodi in letalo leti po zraku. Vozilo
podrazredi uporabnika bi preglasili premakni ()
in navedite ustrezen opis. Podedovali bi tudi metode, ki bi jih poklicali njihovi konstruktorji Vozilo
je konstruktor.
Downcasting in RTTI
Premik po hierarhiji razredov z upcastingom pomeni izgubo dostopa do funkcij podtipa. Na primer, dodelitev a Krog
ugovarjati Oblika
spremenljivka s
pomeni, da je ne morete uporabljati s
poklicati Krog
je getRadius ()
metoda. Vendar je mogoče še enkrat dostopati Krog
je getRadius ()
z izvedbo metode eksplicitna operacija oddaje kot je ta: Krog c = (Krog) s;
.
Ta naloga je znana kot downcasting ker zapuščate hierarhijo dedovanja iz nadtipa v podtip (iz Oblika
superrazred za Krog
podrazred). Čeprav je upcast vedno varen (vmesnik superklase je podmnožica vmesnika podrazreda), downcast ni vedno varen. Seznam 5 prikazuje, kakšne težave bi lahko nastale, če nepravilno uporabljate prenašanje.
Seznam 5. Težava s prenašanjem
razred Superclass {} class Podrazred razširja Superclass {void method () {}} javni razred BadDowncast {public static void main (String [] args) {Superclass superclass = new Superclass (); Podrazred podrazred = (podrazred) nadrazred; subclass.method (); }}
Seznam 5 predstavlja hierarhijo razredov, ki jo sestavljajo Superrazred
in Podrazred
, ki se razteza Superrazred
. Poleg tega Podrazred
izjavlja metoda ()
. Tretji razred z imenom BadDowncast
zagotavlja a glavni ()
metoda, ki ustvari primerek Superrazred
. BadDowncast
nato poskuša ta predmet znižati na Podrazred
in rezultat dodelite spremenljivki podrazred
.
V tem primeru se prevajalnik ne bo pritožil, ker je prenašanje iz superklase v podrazred v istovrstni hierarhiji zakonito. Če pa je bila dodelitev dodeljena, se bo aplikacija poskusila zrušiti subclass.method ();
. V tem primeru bi JVM poskušal poklicati neobstoječo metodo, ker Superrazred
se ne izjavi metoda ()
. Na srečo JVM preveri, ali je igralska zasedba zakonita, preden izvede operacijo igralske zasedbe. Odkrivanje tega Superrazred
se ne izjavi metoda ()
, vrglo bi ClassCastException
predmet. (O izjemah bom razpravljal v prihodnjem članku.)
Sestavite seznam 5, kot sledi:
javac BadDowncast.java
Zaženite nastalo aplikacijo:
java BadDowncast