Beseda polimorfizem prihaja iz grščine za "številne oblike". Večina razvijalcev Java izraz povezuje s sposobnostjo predmeta, da čarobno izvaja pravilno vedenje metode na ustreznih točkah programa. Vendar ta pogled, usmerjen k izvedbi, prej privede do podob čarovništva in ne do razumevanja temeljnih konceptov.
Polimorfizem v Javi je vedno podtip polimorfizma. Če natančno preučimo mehanizme, ki ustvarjajo to raznolikost polimorfnega vedenja, moramo zavreči običajne pomisleke glede izvedbe in razmišljati v smislu vrste. Ta članek raziskuje tipsko usmerjeno perspektivo predmetov in kako se ta perspektiva ločuje kaj vedenje, iz katerega lahko objekt izraža kako predmet dejansko izraža to vedenje. Z osvoboditvijo našega koncepta polimorfizma iz hierarhije izvajanja odkrivamo tudi, kako vmesniki Java olajšajo polimorfno vedenje v skupinah predmetov, ki nimajo skupne implementacijske kode.
Quattro polimorfi
Polimorfizem je širok objektno usmerjen izraz. Čeprav običajno enačimo splošni koncept s sorto podtipa, v resnici obstajajo štiri različne vrste polimorfizma. Preden podrobno preučimo podtip polimorfizma, je v nadaljevanju predstavljen splošen pregled polimorfizma v objektno usmerjenih jezikih.
Luca Cardelli in Peter Wegner, avtorja knjige "O razumevanju tipov, abstrakcije podatkov in polimorfizma" (glejte Vire za povezavo do članka) polimorfizem delita v dve glavni kategoriji - ad hoc in univerzalno - in štiri sorte: prisila, preobremenitev, parametrično in vključitev. Klasifikacijska struktura je:
| - prisila | - ad hoc - | | - preobremenjujoči polimorfizem - | | - parametrični | - univerzalni - | | - vključitev
V tej splošni shemi polimorfizem predstavlja sposobnost entitete, da ima več oblik. Univerzalni polimorfizem se nanaša na enotnost strukture tipov, pri kateri polimorfizem deluje nad neskončnim številom tipov, ki imajo skupno značilnost. Manj strukturirani ad hoc polimorfizem deluje nad končnim številom morda nepovezanih vrst. Štiri sorte lahko opišemo kot:
- Prisila: ena abstrakcija služi več vrst z implicitno pretvorbo tipa
- Preobremenitev: en identifikator označuje več abstrakcij
- Parametrično: abstrakcija deluje enakomerno pri različnih vrstah
- Vključitev: abstrakcija deluje prek razmerja vključenosti
Na kratko bom razpravljal o vsaki sorti, preden se bom posebej posvetil polimorfizmu podtipa.
Prisila
Prisila predstavlja implicitno pretvorbo tipa parametra v tip, ki ga pričakuje metoda ali operater, s čimer se izognemo napakam tipa. Za naslednje izraze mora prevajalnik določiti, ali je primeren binarni +
operator obstaja za vrste operandov:
2.0 + 2.0 2.0 + 2 2.0 + "2"
Prvi izraz doda dva dvojno
operandi; jezik Java posebej določa takega operaterja.
Vendar drugi izraz doda a dvojno
in an int
; Java ne opredeli operaterja, ki bi sprejel te vrste operandov. Na srečo prevajalnik implicitno pretvori drugi operand v dvojno
in uporablja operator, definiran za dva dvojno
operandi. To je izjemno priročno za razvijalca; brez implicitne pretvorbe bi prišlo do napake v času prevajanja ali pa bi moral programer eksplicitno oddati datoteko int
do dvojno
.
Tretji izraz doda a dvojno
in a Vrvica
. Še enkrat, jezik Java ne določa takega operaterja. Torej prevajalnik prisili dvojno
operand v a Vrvica
, in operator plus izvede združevanje nizov.
Prisila se pojavi tudi pri priklicu metode. Recimo razred Izpeljano
podaljša razred Osnova
, in razred C
ima metodo s podpisom m (podnožje)
. Za klic metode v spodnji kodi prevajalnik implicitno pretvori izpeljana
referenčna spremenljivka, ki ima tip Izpeljano
, do Osnova
vrsta, predpisana s podpisom metode. Ta implicitna pretvorba omogoča m (podnožje)
koda izvedbene metode za uporabo samo operacij tipa, ki jih določa Osnova
:
C c = novo C (); Izpeljano izpeljano = novo Izvedeno (); c.m (pridobljeno);
Spet implicitna prisila med priklicem metode odpravi okoren tip ali nepotrebno napako pri prevajanju. Seveda prevajalnik še vedno preveri, ali so vse pretvorbe tipov skladne z definirano hierarhijo tipov.
Preobremenitev
Preobremenitev dovoljuje uporabo istega imena operaterja ali metode za označevanje več različnih ločenih pomenov programa. The +
operator, uporabljen v prejšnjem oddelku, je razstavil dve obliki: eno za dodajanje dvojno
operandi, enega za združevanje Vrvica
predmetov. Obstajajo tudi drugi obrazci za dodajanje dveh celih števil, dveh dolžin itd. Pokličemo operaterja preobremenjeni in se zanašajte na prevajalnik, da izbere ustrezno funkcionalnost glede na programski kontekst. Kot smo že omenili, prevajalnik po potrebi implicitno pretvori vrste operanda, da se ujemajo z natančnim podpisom operaterja. Čeprav Java določa nekatere preobremenjene operaterje, ne podpira uporabniško definiranega preobremenitve operaterjev.
Java dovoljuje uporabniško določeno preobremenitev imen metod. Razred ima lahko več metod z istim imenom, če so podpisi metode različni. To pomeni, da se mora število parametrov razlikovati ali pa mora imeti vsaj en položaj parametra drugačen tip. Edinstveni podpisi omogočajo prevajalniku, da razlikuje med metodami z istim imenom. Prevajalnik imena metod pokvari z uporabo edinstvenih podpisov in tako ustvari edinstvena imena. Glede na to vsako navidezno polimorfno vedenje ob natančnejšem pregledu izhlapi.
Prisila in preobremenitev sta razvrščena kot priložnostna, ker vsaka zagotavlja polimorfno vedenje le v omejenem smislu. Čeprav spadajo pod široko definicijo polimorfizma, so te sorte predvsem ugodnosti za razvijalce. Prisila odpravi okorne eksplicitne odlitke vrst ali nepotrebne napake tipa prevajalnika. Preobremenitev pa zagotavlja skladenjski sladkor, ki razvijalcu omogoča, da isto ime uporablja za različne metode.
Parametrično
Parametrični polimorfizem omogoča uporabo ene ali več abstrakcij. Na primer, a Seznam
abstrakcijo, ki predstavlja seznam homogenih predmetov, bi lahko zagotovili kot generični modul. Abstrakcijo bi ponovno uporabili tako, da bi določili vrste predmetov na seznamu. Ker je lahko parametrizirani tip kateri koli uporabniško določen podatkovni tip, je generične abstrakcije potencialno neskončno veliko, kar je verjetno najmočnejša vrsta polimorfizma.
Na prvi pogled zgoraj Seznam
zdi se, da je abstrakcija koristnost razreda java.util.List
. Vendar Java ne podpira resničnega parametričnega polimorfizma na varen način, zato java.util.List
in java.util
drugi razredi zbirke so napisani v smislu prvotnega razreda Java, java.lang.Object
. (Za več podrobnosti glejte moj članek »Primordialni vmesnik?«.) Enokoreninsko dedovanje implementacije Java ponuja delno rešitev, ne pa tudi resnično moč parametričnega polimorfizma. Odličen članek Erica Allena "Glej moč parametričnega polimorfizma" opisuje potrebo po generičnih tipih v Javi in predloge za obravnavo zahteve za specifikacijo Java's Java št. 000014, "Dodajanje generičnih tipov v programski jezik Java." (Za povezavo glejte Vire.)
Vključenost
Inkluzijski polimorfizem doseže polimorfno vedenje z vključevalno zvezo med vrstami ali sklopi vrednosti. Za številne objektno usmerjene jezike, vključno z Javo, je vključitvena relacija podtip relacije. V Javi je torej vključujoči polimorfizem podtip polimorfizma.
Kot smo že omenili, ko se razvijalci Java splošno nanašajo na polimorfizem, vedno pomenijo polimorfizem podtipa. Če želimo trdno razumeti moč podtipa polimorfizma, je treba mehanizme, ki ustvarjajo polimorfno vedenje, gledati s tipsko usmerjenega vidika. Preostanek tega članka natančno preučuje to perspektivo. Za kratkost in jasnost uporabljam izraz polimorfizem v pomenu podtipa polimorfizem.
Tip usmerjen pogled
Diagram razredov UML na sliki 1 prikazuje preprosto hierarhijo vrst in razredov, ki se uporablja za ponazoritev mehanike polimorfizma. Model prikazuje pet vrst, štiri razrede in en vmesnik. Čeprav se model imenuje diagram razredov, ga smatram kot tipski diagram. Kot je podrobno opisano v poglavju "Hvala in nežen razred", vsak razred in vmesnik Java razglasi uporabniško določen podatkovni tip. Torej iz pogleda, neodvisnega od izvedbe (tj. Tipsko usmerjenega pogleda), vsak od petih pravokotnikov na sliki predstavlja vrsto. Z vidika izvedbe so štirje tipi definirani s pomočjo konstrukcij razredov, eden pa z vmesnikom.
Naslednja koda definira in izvaja vsak uporabniško določen podatkovni tip. Namero poenostavljam čim bolj preprosto:
/ * Base.java * / javni razred Base {javni niz m1 () {return "Base.m1 ()"; } public String m2 (String s) {return "Base.m2 (" + s + ")"; }} / * IType.java * / vmesnik IType {String m2 (String s); Niz m3 (); } / * Izvedeno.java * / javni razred Izvedeno razširja Base izvaja IType {javni niz m1 () {return "Izvedeno.m1 ()"; } javni niz m3 () {return "Izvedeno.m3 ()"; }} / * Izvedeno2.java * / javni razred Izvedeno2 razširja Izvedeno {javni String m2 (String s) {return "Izvedeno2.m2 (" + s + ")"; } javni niz m4 () {return "Izvedeno2.m4 ()"; }} / * Separate.java * / javni razred Ločeno implementira IType {javni niz m1 () {return "Separate.m1 ()"; } public String m2 (String s) {return "Ločeno.m2 (" + s + ")"; } public String m3 () {return "Ločeno.m3 ()"; }}
Z uporabo teh deklaracij tipov in definicij razredov slika 2 prikazuje konceptualni pogled stavka Java:
Izvedeno2 izpeljano2 = novo Izvedeno2 ();
Zgornja izjava izjavlja izrecno tipkano referenčno spremenljivko, izpeljana2
, in sklic na novo ustvarjeno Izpeljano2
predmet razreda. Zgornja plošča na sliki 2 prikazuje Izpeljano2
sklic kot nabor lukenj, skozi katere je spodaj Izpeljano2
predmet si lahko ogledate. Za vsakega je ena luknja Izpeljano2
tip operacije. Dejansko Izpeljano2
objekt preslika vsak Izpeljano2
operacija do ustrezne izvedbene kode, kot to predpisuje hierarhija izvajanja, opredeljena v zgornji kodi. Na primer Izpeljano2
zemljevidi objektov m1 ()
k izvedbeni kodi, definirani v razredu Izpeljano
. Poleg tega ta izvedbena koda preglasi m1 ()
metoda pri pouku Osnova
. A Izpeljano2
referenčna spremenljivka ne more dostopati do razveljavljene m1 ()
izvajanje pri pouku Osnova
. To ne pomeni, da je dejanska izvedbena koda v razredu Izpeljano
ne morem uporabiti Osnova
izvajanje razreda prek super.m1 ()
. Kar pa zadeva referenčno spremenljivko izpeljana2
je zaskrbljen, da je ta koda nedostopna. Preslikave drugega Izpeljano2
operacije podobno prikazujejo izvedbeno kodo, izvedeno za vsako vrsto operacije.
Zdaj, ko imate Izpeljano2
predmeta, se lahko sklicujete na katero koli spremenljivko, ki ustreza tipu Izpeljano2
. To razkriva hierarhija tipov v diagramu UML na sliki 1 Izpeljano
, Osnova
, in IType
so vse super vrste Izpeljano2
. Tako je na primer a Osnova
sklic je lahko pritrjen na predmet. Slika 3 prikazuje konceptualni pogled naslednje izjave Java:
Osnovna osnova = izpeljana2;
V osnovi ni nobene spremembe Izpeljano2
objekt ali katero koli preslikavo operacije, čeprav metode m3 ()
in m4 ()
niso več dostopni prek Osnova
sklic. Klicanje m1 ()
ali m2 (niz)
z uporabo katere koli spremenljivke izpeljana2
ali osnova
povzroči izvedbo iste izvedbene kode:
Niz tmp; // Izpeljana referenca2 (slika 2) tmp = izvedena2.m1 (); // tmp je "Izpeljano.m1 ()" tmp = izpeljano2.m2 ("Pozdravljeni"); // tmp je "Izpeljano2.m2 (Pozdravljeni)" // Osnovna referenca (slika 3) tmp = base.m1 (); // tmp je "Izpeljano.m1 ()" tmp = base.m2 ("Pozdravljeni"); // tmp je "Izpeljano2.m2 (Pozdravljeni)"
Uresničevanje enakega vedenja prek obeh referenc je smiselno, ker Izpeljano2
objekt ne ve, kaj kliče posamezna metoda. Predmet ve samo, da ob klicu sledi zaporedjem pohodov, ki jih definira hierarhija izvajanja. Ti ukazi to določajo za metodo m1 ()
, Izpeljano2
objekt izvede kodo v razredu Izpeljano
, in za metodo m2 (niz)
, izvrši kodo v razredu Izpeljano2
. Dejanje osnovnega predmeta ni odvisno od vrste referenčne spremenljivke.
Vendar pri uporabi referenčnih spremenljivk ni vse enako izpeljana2
in osnova
. Kot je prikazano na sliki 3, a Osnova
sklic na tip lahko vidi samo Osnova
tipske operacije osnovnega predmeta. Torej čeprav Izpeljano2
ima preslikave za metode m3 ()
in m4 ()
, spremenljivka osnova
ne morem dostopati do teh metod:
Niz tmp; // Izpeljana referenca2 (slika 2) tmp = izvedena2.m3 (); // tmp je "Izpeljano.m3 ()" tmp = izpeljano2.m4 (); // tmp je "Izpeljano2.m4 ()" // Osnovna referenca (slika 3) tmp = base.m3 (); // Napaka časa prevajanja tmp = base.m4 (); // Napaka v času prevajanja
Izvajanje
Izpeljano2
objekt ostane popolnoma sposoben sprejeti bodisi
m3 ()
ali
m4 ()
klici metode. Omejitve vrste, ki onemogočajo poskuse klicev prek
Osnova