Polymorfismi viittaa joidenkin entiteettien kykyyn esiintyä eri muodoissa. Sitä edustaa yleisesti perhonen, joka morfoi toukasta pupaan imagoon. Polymorfismi esiintyy myös ohjelmointikielissä mallintamistekniikkana, jonka avulla voit luoda yhden käyttöliittymän useille operandeille, argumenteille ja objekteille. Java-polymorfismi johtaa koodiin, joka on ytimekkäämpi ja helpompi ylläpitää.
Vaikka tämä opetusohjelma keskittyy alatyypin polymorfismiin, sinun on tiedettävä useita muita tyyppejä. Aloitamme yleiskatsauksella kaikista neljästä polymorfismin tyypistä.
lataa Hanki koodi Lataa lähdekoodi esimerkiksi sovelluksiin tässä opetusohjelmassa. Luonut Jeff Friesen JavaWorldille.Javan polymorfismin tyypit
Javalassa on neljän tyyppistä polymorfismia:
- Pakko on operaatio, joka palvelee useita tyyppejä implisiittisen tyypin muunnoksen avulla. Voit esimerkiksi jakaa kokonaisluvun toisella kokonaisluvulla tai liukulukuarvon toisella liukulukuarvolla. Jos yksi operandi on kokonaisluku ja toinen operandi on liukuluku, kääntäjä pakotteet (muuntaa implisiittisesti) kokonaisluvun liukulukuarvoksi tyypin virheen estämiseksi. (Ei ole yhtään jakamisoperaatiota, joka tukisi kokonaislukuoperandia ja liukulukuoperandia.) Toinen esimerkki on aliluokan objektiviitteen välittäminen menetelmän superluokan parametriin. Kääntäjä pakottaa aliluokan tyypin yliluokkatyyppiin rajoittaakseen toiminnot yliluokan toimintoihin.
- Ylikuormitus viittaa saman operaattorisymbolin tai menetelmän nimen käyttämiseen eri tilanteissa. Voit esimerkiksi käyttää
+
suorittaa kokonaisluku-, liukuluku- tai merkkijono-ketjutus sen operandityypistä riippuen. Myös useita menetelmiä, joilla on sama nimi, voi esiintyä luokassa (ilmoituksen ja / tai perinnön kautta). - Parametrinen polymorfismi määrää, että luokan ilmoituksessa kentän nimi voi liittyä eri tyyppiin ja menetelmän nimi voi liittyä erilaisiin parametri- ja palautustyyppeihin. Kenttä ja menetelmä voivat sitten ottaa erilaisia tyyppejä kussakin luokan ilmentymässä (objektissa). Esimerkiksi kenttä voi olla tyypiltään
Kaksinkertainen
(Java: n tavallisen luokan kirjaston jäsen, joka kääri akaksinkertainen
arvo) ja menetelmä saattaa palauttaa aKaksinkertainen
yhdessä objektissa, ja sama kenttä voi olla tyypiltäänMerkkijono
ja sama menetelmä saattaa palauttaa aMerkkijono
toisessa objektissa. Java tukee parametrista polymorfismia geneeristen lääkkeiden kautta, josta keskustelen tulevassa artikkelissa. - Alatyyppi tarkoittaa, että tyyppi voi toimia toisen tyypin alatyyppinä. Kun alatyypin ilmentymä ilmestyy supertyyppikontekstissa, supertyyppitoiminnon suorittaminen alatyyppiesimerkissä johtaa kyseisen aliohjelman suorittavan version. Harkitse esimerkiksi fragmenttia koodista, joka piirtää mielivaltaisia muotoja. Voit ilmaista tämän piirustuskoodin tiiviimmin ottamalla käyttöön a
Muoto
luokka apiirtää ()
menetelmä; esittelemälläYmpyrä
,Suorakulmio
ja muut ohittavat alaluokatpiirtää ()
; ottamalla käyttöön tyypin taulukkoMuoto
jonka elementit tallentavat viittauksiaMuoto
alaluokan esiintymät; ja soittamallaMuoto
onpiirtää ()
menetelmä. Kun soitatpiirtää ()
, se onYmpyrä
,Suorakulmio
tai muutaMuoto
esimerkiksipiirtää ()
menetelmä, jota kutsutaan. Sanomme, että on monia muotojaMuoto
onpiirtää ()
menetelmä.
Tämä opetusohjelma esittelee alatyypin polymorfismin. Opit päivittämisestä ja myöhäisestä sitomisesta, abstrakteista luokista (joita ei voida instantisoida) ja abstrakteista menetelmistä (joita ei voida kutsua). Opit myös downcastingista ja ajonaikaisesta tyyppitunnistuksesta ja saat ensin katsauksen kovariaanisiin palautustyyppeihin. Tallennan parametrisen polymorfismin tulevaa opetusohjelmaa varten.
Ad-hoc vs universaali polymorfismi
Kuten monet kehittäjät, luokittelen pakottamisen ja ylikuormituksen ad-hoc-polymorfismiksi ja parametri- ja alatyypin universaaliksi polymorfismiksi. Vaikka arvokkaat tekniikat, en usko, että pakko ja ylikuormitus ovat todellista polymorfiaa; ne ovat enemmän tyyppimuunnoksia ja syntaktista sokeria.
Alatyypin polymorfismi: Murtuminen ja myöhäinen sitoutuminen
Alatyypin polymorfismi perustuu nousuun ja myöhään sitoutumiseen. Ylitys on suoratoistomuoto, jossa perimishierarkia asetetaan alatyypistä supertyypiksi. Valitusoperaattoria ei ole mukana, koska alatyyppi on erikoistyyppi supertyyppiin. Esimerkiksi, Muoto s = uusi ympyrä ();
Uutiset lähteestä Ympyrä
että Muoto
. Tämä on järkevää, koska ympyrä on eräänlainen muoto.
Ylöslaskun jälkeen Ympyrä
että Muoto
, et voi soittaa Ympyrä
- spesifiset menetelmät, kuten a getRadius ()
menetelmä, joka palauttaa ympyrän säteen, koska Ympyrä
- spesifiset menetelmät eivät ole osa Muoto
käyttöliittymä. Alatyypin ominaisuuksien menettäminen sen jälkeen, kun aliluokka on kavennettu sen yläluokkaan, näyttää turhalta, mutta on välttämätöntä alatyypin polymorfismin saavuttamiseksi.
Olettaa, että Muoto
julistaa a piirtää ()
menetelmä, sen Ympyrä
alaluokka ohittaa tämän menetelmän, Muoto s = uusi ympyrä ();
on juuri suoritettu, ja seuraava rivi määrittää s.piirrä ();
. Mikä piirtää ()
menetelmää kutsutaan: Muoto
on piirtää ()
menetelmä tai Ympyrä
on piirtää ()
menetelmä? Kääntäjä ei tiedä mikä piirtää ()
tapa soittaa. Ainoa mitä se voi tehdä, on varmistaa, että menetelmä on olemassa yliluokassa, ja varmistaa, että metodikutsun argumenttiluettelo ja palautustyyppi vastaavat superluokan menetelmäilmoitusta. Kääntäjä lisää kuitenkin käännettyyn koodiin myös käskyn, joka ajon aikana hakee ja käyttää mitä tahansa viitteitä s
kutsua oikeita piirtää ()
menetelmä. Tämä tehtävä tunnetaan nimellä myöhäinen sitominen.
Myöhäinen sitoutuminen vs. varhainen sitoutuminen
Myöhäistä sidontaa käytetään puheluihinlopullinen
esiintymämenetelmät. Kaikissa muissa menetelmäpuheluissa kääntäjä tietää minkä menetelmän kutsutaan. Se lisää käännetyn koodin käskyn, joka kutsuu muuttujan tyyppiin liittyvän menetelmän eikä sen arvoon. Tämä tekniikka tunnetaan nimellä varhainen sitominen.
Olen luonut sovelluksen, joka osoittaa alatyypin polymorfismin upcastingissa ja myöhässä sitoutumisessa. Tämä sovellus koostuu Muoto
, Ympyrä
, Suorakulmio
ja Muodot
luokat, joissa jokainen luokka on tallennettu omaan lähdetiedostoonsa. Luettelossa 1 esitetään kolme ensimmäistä luokkaa.
Luettelointi 1. Muotojen hierarkian julistaminen
luokan muoto {void draw () {}} luokan piiri laajentaa muotoa {yksityinen int x, y, r; Ympyrä (int x, int y, int r) {tämä.x = x; tämä.y = y; tämä.r = r; } // Lyhyesti sanottuna olen jättänyt pois getX () -, getY () - ja getRadius () -menetelmät. @Override void draw () {System.out.println ("Piirustusympyrä (" + x + "," + y + "," + r + ")"); }} luokan Suorakulmio ulottuu Muoto {yksityinen int x, y, w, h; Suorakulmio (int x, int y, int w, int h) {tämä.x = x; tämä.y = y; tämä.w = w; tämä. h = h; } // Lyhyesti sanottuna olen jättänyt pois getX (), getY (), getWidth () ja getHeight () // -metodit. @Override void draw () {System.out.println ("Piirustus suorakulmio (" + x + "," + y + "," + w + "," + h + ")"); }}
Luettelossa 2 esitetään Muodot
sovellusluokka, jonka main ()
menetelmä ajaa sovellusta.
Listaus 2. Murtuminen ja myöhäinen sitoutuminen alatyyppipolymorfismissa
luokan muodot {public static void main (String [] args) {Shape [] shape = {uusi ympyrä (10, 20, 30), uusi suorakulmio (20, 30, 40, 50)}; for (int i = 0; i <muodot.pituus; i ++) -muodoille [i] .piirrä (); }}
Ilmoitus muodot
taulukko osoittaa ylivirtauksen. Ympyrä
ja Suorakulmio
viitteet tallennetaan muodot [0]
ja muodot [1]
ja ovat ylöspäin kirjoitettavia Muoto
. Jokainen muodot [0]
ja muodot [1]
pidetään a Muoto
ilmentymä: muodot [0]
ei pidetä Ympyrä
; muodot [1]
ei pidetä Suorakulmio
.
Myöhäinen sitoutuminen osoitetaan muodot [i] .piirrä ();
ilmaisu. Kun i
on yhtä suuri 0
, kääntäjän luoma käsky aiheuttaa Ympyrä
on piirtää ()
menetelmä kutsutaan. Kun i
on yhtä suuri 1
tämä ohje kuitenkin aiheuttaa Suorakulmio
on piirtää ()
menetelmä kutsutaan. Tämä on alatyyppisen polymorfismin ydin.
Olettaen, että kaikki neljä lähdetiedostoa (Shapes.java
, Muoto.java
, Suorakulmio. Java
ja Circle.java
) sijaitsevat nykyisessä hakemistossa, käännä ne jommallakummalla seuraavista komentoriveistä:
javac * .java javac Shapes.java
Suorita tuloksena oleva sovellus:
java-muodot
Ota huomioon seuraava tulos:
Piirrosympyrä (10, 20, 30) Piirustus suorakulmio (20, 30, 40, 50)
Abstraktit luokat ja menetelmät
Suunnitellessasi luokkahierarkioita huomaat, että luokat, jotka ovat lähempänä näiden hierarkioiden yläosaa, ovat yleisempiä kuin luokat, jotka ovat alempana. Esimerkiksi a Ajoneuvo
superluokka on yleisempi kuin a Kuorma-auto
alaluokka. Samoin a Muoto
superluokka on yleisempi kuin a Ympyrä
tai a Suorakulmio
alaluokka.
Geneerisen luokan luominen ei ole järkevää. Loppujen lopuksi, mitä a Ajoneuvo
esine kuvaa? Samoin, minkälaista muotoa edustaa a Muoto
esine? Sen sijaan, että koodisit tyhjän piirtää ()
menetelmä Muoto
, voimme estää tämän menetelmän kutsumisen ja tämän luokan ilmentämisen julistamalla molemmat entiteetit abstrakteiksi.
Java tarjoaa abstrakti
varattu sana ilmoittaakseen luokan, jota ei voida instantisoida. Kääntäjä ilmoittaa virheestä, kun yrität instantisoida tätä luokkaa. abstrakti
käytetään myös julistamaan menetelmä ilman runkoa. piirtää ()
menetelmä ei tarvitse runkoa, koska se ei pysty piirtämään abstraktia muotoa. Listaus 3 osoittaa.
Listaus 3. Shape-luokan ja sen draw () -menetelmän tiivistäminen
abstrakti luokka Muoto {abstract void draw (); // vaaditaan puolipiste
Tiivistelmä varoitukset
Kääntäjä ilmoittaa virheestä, kun yrität julistaa luokkaa abstrakti
ja lopullinen
. Esimerkiksi kääntäjä valittaa abstrakti viimeisen luokan muoto
koska abstraktia luokkaa ei voida instantisoida ja lopullista luokkaa ei voida pidentää. Kääntäjä ilmoittaa myös virheestä, kun ilmoitat menetelmän abstrakti
mutta älä ilmoita sen luokkaa abstrakti
. Poistaminen abstrakti
alkaen Muoto
luokan otsikko luettelossa 3 johtaisi esimerkiksi virheeseen. Tämä olisi virhe, koska ei-abstraktia (konkreettista) luokkaa ei voida instantisoida, kun se sisältää abstraktin menetelmän. Lopuksi, kun laajennat abstraktia luokkaa, laajentavan luokan on korvattava kaikki abstraktit menetelmät, tai muuten laajentava luokka on itse julistettava abstraktiksi; muuten kääntäjä ilmoittaa virheestä.
Abstrakti luokka voi ilmoittaa kentät, konstruktorit ja ei-abstraktit menetelmät abstraktien menetelmien lisäksi tai sijaan. Esimerkiksi abstrakti Ajoneuvo
luokka saattaa ilmoittaa kentät, jotka kuvaavat sen merkkiä, mallia ja vuotta. Se voi myös julistaa rakentajan aloittamaan nämä kentät ja konkreettiset menetelmät palauttamaan arvot. Tutustu luetteloon 4.
Luettelo 4. Ajoneuvon tiivistelmä
abstrakti luokka Ajoneuvo {yksityinen Jousimerkki, malli; yksityinen kansainvälinen vuosi; Ajoneuvo (merkkijono, kielimalli, vuosimalli) {this.make = merkki; tämä malli = malli; tämä.vuosi = vuosi; } Merkkijono getMake () {return make; } String getModel () {palautusmalli; } int getYear () {paluuvuosi; } abstrakti tyhjä siirto (); }
Huomaa se Ajoneuvo
julistaa tiivistelmän liikkua()
menetelmä ajoneuvon liikkeen kuvaamiseksi. Esimerkiksi auto rullaa tietä pitkin, vene purjehtii veden yli ja kone lentää ilman läpi. Ajoneuvo
alaluokat kumoaisivat liikkua()
ja antaa asianmukainen kuvaus. He perivät myös menetelmät ja niiden rakentajat kutsuvat Ajoneuvo
rakentaja.
Downcasting ja RTTI
Luokkahierarkian ylöspäin siirtäminen upcastingin kautta merkitsee alityypin ominaisuuksien menettämistä. Esimerkiksi a Ympyrä
objekti Muoto
muuttuja s
tarkoittaa, että et voi käyttää s
soittaa Ympyrä
on getRadius ()
menetelmä. Pääsy on kuitenkin mahdollista uudelleen Ympyrä
on getRadius ()
menetelmä suorittamalla nimenomainen suoratoisto niinkuin tämä: Ympyrä c = (ympyrä) s;
.
Tämä tehtävä tunnetaan nimellä alentaminen koska heität perintöhierarkian supertyypistä alatyypiksi ( Muoto
superluokka Ympyrä
alaluokka). Vaikka upcast on aina turvallinen (yliluokan käyttöliittymä on alaluokan käyttöliittymän osajoukko), downcast ei ole aina turvallista. Luettelosta 5 näkyy, millaista ongelmaa voi syntyä, jos käytät downcastingia väärin.
Listaus 5. Downcasting-ongelma
class Superclass {} class Subclass laajentaa Superclass {void method () {}} public class BadDowncast {public static void main (String [] args) {Superclass superclass = new Superclass (); Aliluokan alaluokka = (aliluokka) yliluokka; alaluokka.menetelmä (); }}
Luettelossa 5 on luokkahierarkia, joka koostuu Superclass
ja Alaluokka
, joka ulottuu Superclass
. Lisäksi, Alaluokka
julistaa menetelmä()
. Kolmas luokka nimeltä BadDowncast
tarjoaa a main ()
menetelmä, joka ilmentää Superclass
. BadDowncast
yrittää sitten alittaa tämän kohteen Alaluokka
ja määritä tulos muuttujalle alaluokka
.
Tällöin kääntäjä ei valittaa, koska supistaminen yliluokasta aliluokkaan samantyyppisessä hierarkiassa on laillista. Tästä huolimatta, jos tehtävän antaminen sallitaan, sovellus kaatuu, kun se yritti suorittaa alaluokka.menetelmä ();
. Tässä tapauksessa JVM yrittää kutsua olematonta menetelmää, koska Superclass
ei julista menetelmä()
. Onneksi JVM varmistaa, että näyttelijät ovat laillisia, ennen kuin he suorittavat heittotoiminnon. Sen havaitseminen Superclass
ei julista menetelmä()
, se heittäisi a ClassCastException
esine. (Keskustelen poikkeuksista tulevassa artikkelissa.)
Koosta Listaus 5 seuraavasti:
javac BadDowncast.java
Suorita tuloksena oleva sovellus:
java BadDowncast