Programavimas

Java polimorfizmas ir jo tipai

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:

  1. 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.
  2. 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).
  3. 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., narys dvigubai vertė) ir metodas gali grąžinti a Dvigubai viename objekte ir tas pats laukas gali būti tipo Stygos ir tas pats metodas gali pateikti a Stygos kitame objekte. „Java“ palaiko parametrinį polimorfizmą per generikus, kuriuos aptarsiu būsimame straipsnyje.
  4. 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 a piešti () metodas; įvedant Apskritimas, Stačiakampisir kiti poklasiai, kurie viršija piešti (); įvedant tipo masyvą Figūra kurio elementuose saugomos nuorodos Figūra poklasio egzemplioriai; ir paskambinus Figūra's piešti () metodas kiekvienam atvejui. Kai paskambinsite piešti (), tai Apskritimas, Stačiakampisar kt Figūra egzempliorių piešti () metodas, kuris yra vadinamas. Mes sakome, kad yra daugybė formų Figūra's pieš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 Apskritimasspecifiniai metodai nėra Figūrasą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čiakampisir 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 1tač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.javair 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