Polimorfizam u Javi

1. Pregled

Svi jezici objektno orijentiranog programiranja (OOP) moraju pokazivati ​​četiri osnovne karakteristike: apstrakcija, inkapsulacija, nasljeđivanje i polimorfizam.

U ovom članku pokrivamo dvije osnovne vrste polimorfizma: statički polimorfizam ili vrijeme kompajliranja i dinamički ili vrijeme izvođenjapolimorfizam. Statički polimorfizam se primjenjuje u vrijeme sastavljanja, dok se dinamički polimorfizam ostvaruje tijekom izvođenja.

2. Statički polimorfizam

Prema Wikipediji, statični polimorfizam je imitacija polimorfizam koji se rješava u vrijeme kompajliranja i na taj način ukida traženje virtualnih tablica tijekom izvođenja.

Na primjer, naš TextFile klasa u aplikaciji za upravljanje datotekama može imati tri metode s istim potpisom čitati() metoda:

javna klasa TextFile proširuje GenericFile {// ... public String read () {return this.getContent () .toString (); } public String read (int limit) {return this.getContent () .toString () .substring (0, limit); } public String read (int start, int stop) {return this.getContent () .toString () .substring (start, stop); }}

Tijekom kompilacije koda, kompajler provjerava jesu li svi pozivi čitati metode odgovaraju barem jednoj od tri gore definirane metode.

3. Dinamički polimorfizam

S dinamičkim polimorfizmom, Java virtualni stroj (JVM) obrađuje otkrivanje odgovarajuće metode za izvršavanje kada je podrazred dodijeljen roditeljskom obliku. To je neophodno jer podrazred može nadjačati neke ili sve metode definirane u nadređenoj klasi.

U hipotetskoj aplikaciji za upravljanje datotekama definirajmo roditeljsku klasu za sve datoteke koje se zovu GenericFile:

javna klasa GenericFile {naziv privatnog niza; // ... public String getFileInfo () {return "Generička datoteka Impl"; }}

Također možemo implementirati ImageFile klasa koja proširuje GenericFile ali poništava getFileInfo () metoda i dodaje više informacija:

javna klasa ImageFile proširuje GenericFile {private int height; privatna int širina; // ... getteri i postavljači public String getFileInfo () {return "Impl slikovne datoteke"; }}

Kada stvorimo instancu ImageFile i dodijelite ga a GenericFile klase, radi se implicitna glumačka postava. Međutim, JVM zadržava referencu na stvarni oblik ImageFile.

Gornja konstrukcija analogna je nadjačavanju metode. To možemo potvrditi pozivanjem na getFileInfo () metoda:

javna statička void glavna (String [] args) {GenericFile genericFile = new ImageFile ("SampleImageFile", 200, 100, new BufferedImage (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0" ); logger.info ("Podaci o datoteci: \ n" + genericFile.getFileInfo ()); }

Očekivano, genericFile.getFileInfo () pokreće getFileInfo () metoda ImageFile klase kao što se vidi u donjem izlazu:

Podaci o datoteci: Impl. Slikovne datoteke

4. Ostale polimorfne značajke u Javi

Pored ove dvije glavne vrste polimorfizma u Javi, postoje i druge karakteristike u programskom jeziku Java koje pokazuju polimorfizam. Razmotrimo neke od ovih karakteristika.

4.1. Prisila

Polimorfna prisila bavi se implicitnom pretvorbom tipa koju je izvršio prevoditelj kako bi spriječio pogreške tipa. Tipičan primjer vidi se u spajanju cijelih brojeva i nizova:

Niz str = "niz" + 2;

4.2. Preopterećenje operatera

Preopterećenje operatora ili metode odnosi se na polimorfne karakteristike istog simbola ili operatora koji imaju različita značenja (oblike) ovisno o kontekstu.

Na primjer, simbol plus (+) može se koristiti i za matematički zbrajanje Niz spajanje. U oba slučaja, samo kontekst (tj. Vrste argumenata) određuje tumačenje simbola:

Niz str = "2" + 2; int zbroj = 2 + 2; System.out.printf ("str =% s \ n zbroj =% d \ n", str, zbroj);

Izlaz:

str = 22 zbroj = 4

4.3. Polimorfni parametri

Parametarski polimorfizam omogućuje pridruživanje naziva parametra ili metode u klasi različitim vrstama. U nastavku imamo tipičan primjer gdje definiramo sadržaj kao Niz a kasnije kao an Cijeli broj:

javna klasa TextFile proširuje GenericFile {sadržaj privatnog niza; javni String setContentDelimiter () {int content = 100; this.content = this.content + content; }}

Također je važno napomenuti da deklaracija polimorfnih parametara može dovesti do problema poznatog kaovarijabilno skrivanje pri čemu lokalna deklaracija parametra uvijek nadjačava globalnu deklaraciju drugog parametra s istim imenom.

Da bi se riješio ovaj problem, često je preporučljivo koristiti globalne reference poput ovaj ključna riječ koja usmjerava na globalne varijable unutar lokalnog konteksta.

4.4. Polimorfne podvrste

Polimorfni podtip pogodno omogućuje da dodijelimo više podtipova tipu i očekujemo da svi pozivi na tipu pokreću dostupne definicije u podtipu.

Na primjer, ako imamo zbirku GenericFiles i pozivamo na dobiti informacije() metodu na svakom od njih, možemo očekivati ​​da će izlaz biti različit, ovisno o podtipu iz kojeg je izvedena svaka stavka u zbirci:

GenericFile [] files = {new ImageFile ("SampleImageFile", 200, 100, new BufferedImage (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0"), new TextFile ("SampleTextFile" , "Ovo je uzorak tekstualnog sadržaja", "v1.0.0")}; za (int i = 0; i <datoteke.duljina; i ++) {datoteke [i] .getInfo (); }

Podtip polimorfizma omogućen je kombinacijomnadogradnja i kasno vezivanje. Ažuriranje uključuje lijevanje hijerarhije nasljeđivanja iz nadtipa u podtip:

ImageFile imageFile = novi ImageFile (); Datoteka generičke datoteke = imageFile;

Rezultirajući učinak gore navedenog je taj Datoteka slike-posebne se metode ne mogu pozivati ​​na novom ažuriranju GenericFile. Međutim, metode u podtipu nadjačavaju slične metode definirane u nadtipu.

Da bismo riješili problem nemogućnosti pozivanja metoda specifičnih za podtip prilikom nadogradnje na supertip, možemo obaviti spuštanje nasljedstva iz nadtipa na podtip. To čini:

ImageFile imageFile = (ImageFile) datoteka;

Kasno vezivanjestrategija pomaže prevoditelju da riješi čiju će se metodu pokrenuti nakon nadogradnje. U slučaju imageFile # getInfo nasuprot datoteka # getInfo u gornjem primjeru, sastavljač zadržava referencu na ImageFile‘S dobiti informacije metoda.

5. Problemi s polimorfizmom

Pogledajmo neke nejasnoće u polimorfizmu koje bi potencijalno mogle dovesti do pogrešaka u izvođenju ako se ne provjere pravilno.

5.1. Identifikacija vrste tijekom prenosa

Sjetimo se da smo ranije izgubili pristup nekim metodama specifičnim za podtipove nakon izvođenja nadogradnje. Iako smo to uspjeli riješiti srozavanjem, to ne garantira stvarnu provjeru tipa.

Na primjer, ako izvodimo nadogradnju i naknadno spuštanje:

Datoteka GenericFile = nova GenericFile (); ImageFile imageFile = (ImageFile) datoteka; System.out.println (imageFile.getHeight ());

Primjećujemo da kompajler dopušta spuštanje a GenericFile u an ImageFile, iako je razred zapravo a GenericFile a ne an ImageFile.

Slijedom toga, ako pokušamo pozvati getHeight () metoda na imageFile razred, dobivamo a ClassCastException kao GenericFile ne definira getHeight () metoda:

Iznimka u niti "main" java.lang.ClassCastException: GenericFile ne može se prebaciti u ImageFile

Da bi riješio ovaj problem, JVM izvodi provjeru podataka o vremenu izvođenja (RTTI). Također možemo pokušati eksplicitnu identifikaciju tipa pomoću instanceof ključna riječ upravo ovako:

ImageFile imageFile; if (datoteka instanceof ImageFile) {imageFile = datoteka; }

Gore navedeno pomaže u izbjegavanju a ClassCastException iznimka za vrijeme izvođenja. Druga opcija koja se može upotrijebiti je zamatanje gipsa unutar a probati i ulov blok i hvatanje ClassCastException.

Treba napomenuti da RTTI provjera je skupa zbog vremena i resursa potrebnih za učinkovitu provjeru ispravnosti tipa. Osim toga, česta uporaba instanceof ključna riječ gotovo uvijek podrazumijeva loš dizajn.

5.2. Krhki problem osnovne klase

Prema Wikipediji, osnovna ili superklasa smatra se krhkom ako naizgled sigurne izmjene osnovne klase mogu uzrokovati kvar izvedenih klasa.

Razmotrimo deklaraciju superklase tzv GenericFile i njegov podrazred TextFile:

javna klasa GenericFile {sadržaj privatnog niza; void writeContent (sadržaj niza) {this.content = content; } void toString (String str) {str.toString (); }}
javna klasa TextFile proširuje GenericFile {@Override void writeContent (sadržaj niza) {toString (sadržaj); }}

Kada izmijenimo GenericFile razred:

javna klasa GenericFile {// ... void toString (String str) {writeContent (str); }}

Primjećujemo da gornja preinaka odlazi TextFile u beskonačnoj rekurziji u writeContent () metoda, koja na kraju rezultira preljevom stoga.

Za rješavanje krhkog problema osnovne klase možemo koristiti konačni ključna riječ da spriječi potklase da nadjačaju writeContent () metoda. Pravilna dokumentacija također može pomoći. I posljednje, ali ne najmanje važno, sastav bi općenito trebao biti bolji od nasljedstva.

6. Zaključak

U ovom smo članku raspravljali o temeljnom konceptu polimorfizma, usredotočujući se i na prednosti i na nedostatke.

Kao i uvijek, izvorni kod za ovaj članak dostupan je na GitHubu.