Polimorfizmas reiškia kai kurių subjektų gebėjimą atsirasti skirtingomis formomis. Ją populiariai reprezentuoja drugelis, kuris morfuoja nuo lervos iki lėliukės iki imago. Polimorfizmas egzistuoja ir programavimo kalbose, kaip modeliavimo technika, leidžianti sukurti vieną sąsają su įvairiais operandais, argumentais ir objektais. „Java“ polimorfizmo rezultatas yra glaustesnis ir lengviau prižiūrimas kodas.
Nors šioje pamokoje daugiausia dėmesio skiriama potipio polimorfizmui, turėtumėte žinoti keletą kitų tipų. Pradėsime nuo visų keturių polimorfizmo tipų apžvalgos.
atsisiųsti Gauti kodą Atsisiųskite šaltinį, pvz., programas, šioje pamokoje. Sukūrė Jeffas Friesenas, skirtas „JavaWorld“.Java polimorfizmo tipai
„Java“ yra keturi polimorfizmo tipai:
- Prievarta yra operacija, kuri tarnauja keliems tipams per numanomo tipo konversiją. Pvz., Sveiką skaičių padalijate iš kito sveiko skaičiaus arba slankiojo kablelio vertę iš kitos slankiojo kablelio vertės. Jei vienas operandas yra sveikasis skaičius, o kitas operantas yra slankiojo kablelio reikšmė, kompiliatorius prievartos (netiesiogiai konvertuoja) sveikąjį skaičių į slankiojo kablelio vertę, kad būtų išvengta tipo klaidos. (Nėra padalijimo operacijos, palaikančios sveiko skaičiaus operandą ir slankiojo kablelio operandą.) Kitas pavyzdys yra poklasio objekto nuorodos perdavimas metodo superklasės parametrui. Kompiliatorius priverčia poklasio tipą naudoti į superklasės tipą, kad būtų apribotos operacijos tik anteklakio.
- Perkrovimas reiškia to paties operatoriaus simbolio ar metodo pavadinimo naudojimą skirtinguose kontekstuose. Pavyzdžiui, galite naudoti
+
atlikti sveikojo skaičiaus pridėjimą, slankiojo kablelio pridėjimą ar styginių sujungimą, atsižvelgiant į jo operandų tipus. Be to, klasėje gali būti keli metodai, turintys tą patį pavadinimą (deklaruojant ir (arba) paveldint). - Parametrinis polimorfizmas numato, kad klasės deklaracijoje lauko pavadinimas gali būti susietas su skirtingais tipais, o metodo pavadinimas - su skirtingais parametrų ir grąžinimo tipais. Tada laukas ir metodas kiekvienoje klasės egzemplioriuje (objekte) gali įgauti skirtingus tipus. Pvz., Laukas gali būti tipo
Dvigubai
(„Java“ standartinės klasės bibliotekos, apimančios a., narysdvigubai
vertė) ir metodas gali grąžinti aDvigubai
viename objekte ir tas pats laukas gali būti tipoStygos
ir tas pats metodas gali pateikti aStygos
kitame objekte. „Java“ palaiko parametrinį polimorfizmą per generikus, kuriuos aptarsiu būsimame straipsnyje. - Potipis reiškia, kad tipas gali būti kito tipo potipis. Kai potipio egzempliorius pasirodo supertipo kontekste, vykdant supertipo operaciją potipio egzemplioriuje, gaunama tos operacijos vykdymo potipio versija. Pavyzdžiui, apsvarstykite kodo fragmentą, kuris piešia savavališkas figūras. Galite glaudžiau išreikšti šį piešimo kodą, įvesdami a
Figūra
klasė su apiešti ()
metodas; įvedantApskritimas
,Stačiakampis
ir kiti poklasiai, kurie viršijapiešti ()
; įvedant tipo masyvąFigūra
kurio elementuose saugomos nuorodosFigūra
poklasio egzemplioriai; ir paskambinusFigūra
'spiešti ()
metodas kiekvienam atvejui. Kai paskambinsitepiešti ()
, taiApskritimas
,Stačiakampis
ar ktFigūra
egzemplioriųpiešti ()
metodas, kuris yra vadinamas. Mes sakome, kad yra daugybė formųFigūra
'spiešti ()
metodas.
Šioje pamokoje pristatomas potipio polimorfizmas. Sužinosite apie supaprastinimą ir vėlyvą įrišimą, abstrakčias klases (kurių negalima sukurti tiesiogiai) ir abstrakčius metodus (kurių negalima iškviesti). Jūs taip pat sužinosite apie „downcasting“ ir „Runtime“ tipo identifikavimą ir pirmiausia apžvelgsite kovariantinius grąžinimo tipus. Parametrinį polimorfizmą išsaugosiu būsimai pamokai.
Ad-hoc ir universalus polimorfizmas
Kaip ir daugelis kūrėjų, prievartą ir perkrovą priskiriu ad-hoc polimorfizmui, parametrinius ir potipius - universaliems. Nors vertingos technikos, aš nemanau, kad prievarta ir perkrova yra tikras polimorfizmas; jie labiau panašūs į tipo konversijas ir sintaksinį cukrų.
Potipio polimorfizmas: pakilimas ir vėlyvas prisijungimas
Potipio polimorfizmas priklauso nuo sutrumpinimo ir vėlyvo prisijungimo. Padebėjimas yra liejimo forma, kai paveldėjimo hierarchiją iškeliate iš potipio į supertipą. Nei vienas dalyvių dalyvis nedalyvauja, nes potipis yra supertipo specializacija. Pavyzdžiui, Forma s = naujas apskritimas ();
upcastai iš Apskritimas
į Figūra
. Tai prasminga, nes apskritimas yra tam tikra forma.
Po to, kai sutriko Apskritimas
į Figūra
, negalite skambinti Apskritimas
- konkretūs metodai, tokie kaip a „getRadius“ ()
metodas, kuris grąžina apskritimo spindulį, nes Apskritimas
specifiniai metodai nėra Figūra
sąsaja. Prarasti prieigą prie potipio ypatybių, susiaurinus poklasį iki jo superklasės, atrodo beprasmiška, tačiau būtina norint pasiekti potipio polimorfizmą.
Tarkime, kad Figūra
pareiškia a piešti ()
metodas, jo Apskritimas
poklasis pakeičia šį metodą, Forma s = naujas apskritimas ();
ką tik įvykdė, o kitoje eilutėje nurodoma s.piešti ();
. Kuris piešti ()
metodas vadinamas: Figūra
's piešti ()
metodas arba Apskritimas
's piešti ()
metodas? Kompiliatorius nežino, kuris piešti ()
metodas skambinti. Viskas, ką ji gali padaryti, yra patikrinti, ar metodas yra superklase, ir patikrinti, ar metodo iškvietimo argumentų sąrašas ir grąžinimo tipas atitinka superklasės metodo deklaraciją. Tačiau kompiliatorius į sukurtą kodą taip pat įterpia instrukciją, kuri vykdymo metu gauna ir naudoja bet kokią nuorodą s
paskambinti teisingiesiems piešti ()
metodas. Ši užduotis yra žinoma kaip vėlyvas įrišimas.
Vėlyvas susiejimas prieš ankstyvą rišimą
Vėlyvas susiejimas naudojamas skambinant negalutinis
egzempliorių metodai. Visų kitų metodo skambučių atveju kompiliatorius žino, kurį metodą iškviesti. Į sukurtą kodą įterpiama instrukcija, kurioje iškviečiamas metodas, susietas su kintamojo tipu, o ne jo verte. Ši technika yra žinoma kaip ankstyvas įrišimas.
Aš sukūriau programą, kuri demonstruoja polipo polimorfizmą potvynių ir vėlyvo susiejimo atžvilgiu. Ši programa susideda iš Figūra
, Apskritimas
, Stačiakampis
ir Formos
klasės, kur kiekviena klasė yra saugoma savo šaltinio faile. 1 sąraše pateikiamos pirmosios trys klasės.
Sąrašas 1. Formų hierarchijos deklaravimas
class Shape {void draw () {}} class Circle tęsiasi Shape {private int x, y, r; Apskritimas (int x, int y, int r) {this.x = x; tai.y = y; tai.r = r; } // Trumpumo dėlei praleidau metodus getX (), getY () ir getRadius (). @Paisyti negaliojančią piešinį () {System.out.println ("Piešimo apskritimas (" + x + "," + y + "," + r + ")"); }} klasės stačiakampis tęsiasi forma {private int x, y, w, h; Stačiakampis (int x, int y, int w, int h) {tai.x = x; tai.y = y; tai.w = w; tai.h = h; } // Trumpumo dėlei praleidau metodus getX (), getY (), getWidth () ir getHeight () //. @ Nepaisyti tuštumo piešimo () {System.out.println ("Brėžinio stačiakampis (" + x + "," + y + "," + w + "," + h + ")"); }}
2 sąraše pateikiama Formos
taikymo klasė, kurios pagrindinis ()
metodas varo programą.
Išvardinimas 2. Poveikis ir vėlyvas susiejimas polipo polipyje
klasės formos {public static void main (String [] args) {Shape [] formos = {naujas apskritimas (10, 20, 30), naujas stačiakampis (20, 30, 40, 50)}; už (int i = 0; i <formos.ilgis; i ++) figūras [i] .piešti (); }}
Deklaracija formos
masyvas demonstruoja pakėlimą. Apskritimas
ir Stačiakampis
nuorodos saugomos formos [0]
ir formos [1]
ir yra nepriekaištingo tipo Figūra
. Kiekvienas iš formos [0]
ir formos [1]
yra laikomas a Figūra
instancija: formos [0]
nelaikomas a Apskritimas
; formos [1]
nelaikomas a Stačiakampis
.
Vėlyvą prisijungimą įrodo formos [i] .piešti ();
išraiška. Kada i
lygu 0
, kompiliatoriaus sukurta instrukcija sukelia Apskritimas
's piešti ()
metodas turi būti vadinamas. Kada i
lygu 1
tačiau ši instrukcija sukelia Stačiakampis
's piešti ()
metodas turi būti vadinamas. Tai yra potipio polimorfizmo esmė.
Darant prielaidą, kad visi keturi šaltinio failai (Formos.java
, Forma.java
, Stačiakampis.java
ir Apskritimas.java
) yra dabartiniame kataloge, sudarykite juos naudodami bet kurią iš šių komandų eilių:
javac * .java javac Formos.java
Paleiskite gautą programą:
java Shapes
Turėtumėte stebėti šį rezultatą:
Piešimo apskritimas (10, 20, 30) Piešimo stačiakampis (20, 30, 40, 50)
Anotacijos ir metodai
Kuriant klasių hierarchijas pastebėsite, kad arčiau šių hierarchijų viršaus esančios klasės yra bendresnės nei žemiau žemiau esančios klasės. Pavyzdžiui, a Transporto priemonė
superklasė yra bendresnė nei a Sunkvežimis
poklasis. Panašiai, a Figūra
superklasė yra bendresnė nei a Apskritimas
arba a Stačiakampis
poklasis.
Nėra prasmės inicijuoti bendros klasės. Galų gale, kas būtų a Transporto priemonė
objektas apibūdinti? Panašiai, kokią formą vaizduoja a Figūra
objektas? Užuot kodavęs tuščią kodą piešti ()
metodas Figūra
, mes galime užkirsti kelią šio metodo iškvietimui ir šios klasės išaiškinimui paskelbdami abu subjektus abstrakčiais.
„Java“ teikia abstraktus
rezervuotas žodis, kad būtų paskelbta klasė, kurios negalima iš karto sukurti. Kompiliatorius praneša apie klaidą, kai bandote sukurti šią klasę. abstraktus
taip pat naudojamas deklaruoti metodą be kūno. piešti ()
metodui nereikia kūno, nes jis negali nupiešti abstrakčios formos. 3 sąrašas rodo.
Sąrašas 3. Formos klasės ir jos piešimo () metodo santrauka
abstrakti klasė Forma {abstraktus void draw (); // būtinas kabliataškis}
Abstraktūs įspėjimai
Kompiliatorius praneša apie klaidą, kai bandote paskelbti klasę abstraktus
ir galutinis
. Pavyzdžiui, kompiliatorius skundžiasi abstrakti galutinės klasės forma
nes abstrakti klasė negali būti iš karto sukurta ir paskutinė klasė negali būti pratęsta. Kompiliatorius taip pat praneša apie klaidą, kai deklaruojate metodą abstraktus
bet nedeklaruok jos klasės abstraktus
. Pašalinimas abstraktus
nuo Figūra
klasės antraštė 3 sąraše sukeltų, pavyzdžiui, klaidą. Tai būtų klaida, nes ne abstrakti (konkreti) klasė negali būti sukurta, kai joje yra abstraktus metodas. Galiausiai, išplėtus abstrakčią klasę, išplėstinė klasė turi nepaisyti visų abstrakčių metodų, priešingu atveju pati išplėstinė klasė turi būti paskelbta abstrakčia; kitaip kompiliatorius praneš apie klaidą.
Abstrakti klasė gali deklaruoti laukus, konstruktorius ir ne abstrakčius metodus šalia abstrakčių metodų ar vietoj jų. Pavyzdžiui, abstraktus Transporto priemonė
klasė gali deklaruoti laukus, apibūdinančius jo markę, modelį ir metus. Be to, ji gali paskelbti konstruktorių inicijuoti šiuos laukus ir konkrečius metodus, kad būtų grąžintos jų vertės. Peržiūrėkite 4 sąrašą.
Sąrašas 4. Transporto priemonės santrauka
abstrakti klasė Transporto priemonė {privati styginių markė, modelis; privatus int metai; Transporto priemonė (styginių markė, styginių modelis, metų metai) {this.make = markė; tai.modelis = modelis; tai.metai = metai; } String getMake () {return make; } String getModel () {grąžinimo modelis; } int getYear () {grąžinimo metai; } abstraktus negaliojantis judėjimas (); }
Jūs tai pastebėsite Transporto priemonė
deklaruoja abstraktą perkelti ()
metodas apibūdinti transporto priemonės judėjimą. Pavyzdžiui, automobilis rieda keliu, valtis plaukia per vandenį, o lėktuvas skrenda oru. Transporto priemonė
poklasiai nepaisytų perkelti ()
ir pateikti tinkamą aprašymą. Jie taip pat paveldės metodus, kuriuos paskambins jų konstruktoriai Transporto priemonė
konstruktorius.
Downcasting ir RTTI
Perkėlus klasės hierarchiją aukštyn, reikia prarasti prieigą prie potipio ypatybių. Pavyzdžiui, priskiriant a Apskritimas
prieštarauti Figūra
kintamasis s
reiškia, kad negalite naudoti s
paskambinti Apskritimas
's „getRadius“ ()
metodas. Tačiau galima dar kartą pasiekti Apskritimas
's „getRadius“ ()
metodas atliekant aiški liejimo operacija kaip šis: Apskritimas c = (apskritimas) s;
.
Ši užduotis yra žinoma kaip žeminimas nes jūs išmetate paveldėjimo hierarchiją iš supertipo į potipį (iš Figūra
superklasė į Apskritimas
poklasis). Nors upcastas visada yra saugus (superklasės sąsaja yra poklasio sąsajos pogrupis), downcastas ne visada yra saugus. 5 sąrašas parodo, kokių problemų gali kilti, jei neteisingai naudosite „downcasting“.
5 sąrašas. Sumažinimo problema
class Superclass {} class Subclass prailgina Superclass {void method () {}} public class BadDowncast {public static void main (String [] args) {Superclass superclass = new Superclass (); Poklasio poklasis = (poklasis) superklasė; poklasis.metodas (); }}
5 sąraše pateikiama klasės hierarchija, susidedanti iš Superklasė
ir Poklasis
, kuri tęsiasi Superklasė
. Be to, Poklasis
pareiškia metodas ()
. Trečia klasė pavadinta „BadDowncast“
numato a pagrindinis ()
akimirksniu metodas Superklasė
. „BadDowncast“
tada bando pažeminti šį objektą Poklasis
ir priskirti rezultatą kintamajam poklasis
.
Šiuo atveju kompiliatorius nesiskųs, nes žeminimas iš superklasės į poklasį to paties tipo hierarchijoje yra teisėtas. Vis dėlto, jei būtų leista atlikti užduotį, programa sugestų, kai ji bandė vykdyti poklasis.metodas ();
. Šiuo atveju JVM bandytų iškviesti neegzistuojantį metodą, nes Superklasė
nedeklaruoja metodas ()
. Laimei, JVM prieš atlikdamas aktoriaus patikrinimą patikrina, ar aktoriai yra teisėti. Aptikti tai Superklasė
nedeklaruoja metodas ()
, tai išmestų a „ClassCastException“
objektas. (Išimtis aptarsiu būsimame straipsnyje.)
Sudarykite 5 sąrašą taip:
javac BadDowncast.java
Paleiskite gautą programą:
java BadDowncast