Polymorfizmus v Jave

1. Prehľad

Všetky objektovo orientované programovacie jazyky (OOP) musia vykazovať štyri základné charakteristiky: abstrakciu, zapuzdrenie, dedičnosť a polymorfizmus.

V tomto článku sa venujeme dvom základným typom polymorfizmu: statický polymorfizmus alebo kompilačný čas a dynamický alebo runtimepolymorfizmus. Statický polymorfizmus sa vynucuje v čase kompilácie, zatiaľ čo dynamický polymorfizmus sa realizuje za behu programu.

2. Statický polymorfizmus

Podľa Wikipédie je statický polymorfizmus napodobeninou polymorfizmus, ktorý je vyriešený v čase kompilácie, a tak odpadá vyhľadávanie za behu virtuálnej tabuľky.

Napríklad náš TextFile trieda v aplikácii správcu súborov môže mať tri metódy s rovnakým podpisom súboru čítať() metóda:

public class TextFile extends 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); }}

Počas kompilácie kódu kompilátor overí, či sú všetky vyvolania súboru čítať zodpovedajú najmenej jednej z troch vyššie definovaných metód.

3. Dynamický polymorfizmus

S dynamickým polymorfizmom Java Virtual Machine (JVM) spracováva detekciu vhodnej metódy na vykonanie, keď je podtrieda priradená k jej nadradenému formuláru. Je to nevyhnutné, pretože podtrieda môže prepísať niektoré alebo všetky metódy definované v nadradenej triede.

V hypotetickej aplikácii na správu súborov definujme nadradenú triedu pre všetky volané súbory GenericFile:

public class GenericFile {private String name; // ... public String getFileInfo () {return "Generic File Impl"; }}

Môžeme tiež implementovať ImageFile trieda, ktorá rozširuje GenericFile ale prepíše getFileInfo () metóda a pripája viac informácií:

verejná trieda ImageFile rozširuje GenericFile {private int výška; súkromná int šírka; // ... getre a setre public String getFileInfo () {return "Image File Impl"; }}

Keď vytvoríme inštanciu ImageFile a priradiť ho k a GenericFile triedy, je implicitné obsadenie hotové. JVM si však ponecháva odkaz na skutočnú podobu ImageFile.

Vyššie uvedený konštrukt je analogický s prepísaním metódy. Môžeme to potvrdiť odvolaním sa na getFileInfo () metóda:

public static void main (String [] args) {GenericFile genericFile = new ImageFile ("SampleImageFile", 200, 100, new BufferedImage (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0" ); logger.info ("Informácie o súbore: \ n" + genericFile.getFileInfo ()); }

Podľa očakávania, genericFile.getFileInfo () spúšťa getFileInfo () metóda ImageFile triedy, ako je vidieť na výstupe nižšie:

Informácie o súbore: Impl. Obrazového súboru

4. Ostatné polymorfné charakteristiky v Jave

Okrem týchto dvoch hlavných typov polymorfizmu v Jave existujú v programovacom jazyku Java aj ďalšie charakteristiky, ktoré vykazujú polymorfizmus. Poďme diskutovať o niektorých z týchto charakteristík.

4.1. Nátlak

Polymorfné donútenie sa zaoberá implicitnou konverziou typov, ktorú vykonáva kompilátor, aby sa zabránilo chybám typu. Typickým príkladom je zreťazenie celého čísla a reťazca:

Reťazec str = “reťazec” + 2;

4.2. Preťaženie operátora

Preťaženie operátora alebo metódy označuje polymorfnú charakteristiku rovnakého symbolu alebo operátora, ktorá má rôzne významy (formy) v závislosti od kontextu.

Napríklad symbol plus (+) možno použiť na matematické sčítanie aj ako String zreťazenie. V obidvoch prípadoch interpretáciu symbolu určuje iba kontext (tj. Typy argumentov):

Reťazec str = "2" + 2; int suma = 2 + 2; System.out.printf ("str =% s \ n suma =% d \ n", str, suma);

Výkon:

str = 22 súčet = 4

4.3. Polymorfné parametre

Parametrický polymorfizmus umožňuje, aby bol názov parametra alebo metódy v triede spojený s rôznymi typmi. Nižšie máme typický príklad, kde definujeme obsah ako String a neskôr ako Celé číslo:

verejná trieda TextFile rozširuje GenericFile {súkromný obsah reťazca; public String setContentDelimiter () {int obsah = 100; this.content = this.content + obsah; }}

Je tiež dôležité si to uvedomiť deklarácia polymorfných parametrov môže viesť k problému známemu akovariabilné skrývanie kde lokálna deklarácia parametra vždy potlačí globálnu deklaráciu iného parametra s rovnakým názvom.

Na vyriešenie tohto problému je často vhodné použiť globálne referencie ako napr toto kľúčové slovo, aby ukazovalo na globálne premenné v lokálnom kontexte.

4.4. Polymorfné podtypy

Polymorfný podtyp nám pohodlne umožňuje priradiť typu viac podtypov a očakávať, že všetky vyvolania typu spustia dostupné definície v podtype.

Napríklad, ak máme zbierku GenericFiles a dovolávame sa dostať informácie() metódou na každom z nich môžeme očakávať, že sa výstup bude líšiť v závislosti od podtypu, z ktorého bola každá položka v kolekcii odvodená:

Súbory GenericFile [] = {new ImageFile ("SampleImageFile", 200, 100, new BufferedImage (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0"), nový TextFile ("SampleTextFile") , „Toto je ukážkový textový obsah“, „v1.0.0“)}; for (int i = 0; i <files.length; i ++) {files [i] .getInfo (); }

Polymorfizmus podtypu je možný kombináciouupcasting a neskorá väzba. Upcasting zahŕňa odovzdanie hierarchie dedičstva z nadtypu do podtypu:

ImageFile imageFile = nový ImageFile (); Súbor GenericFile = imageFile;

Výsledný efekt vyššie uvedeného je ten ImageFile-na nový upcast nemožno použiť konkrétne metódy GenericFile. Metódy v podtype však majú prednosť pred podobnými metódami definovanými v supertype.

Aby sme vyriešili problém, že pri prenose na supertyp nemôžeme vyvolať metódy špecifické pre subtyp, môžeme urobiť downcasting dedičnosti z supertypu na podtyp. To sa deje pomocou:

ImageFile imageFile = (ImageFile) súbor;

Neskoré viazaniestratégia pomáha kompilátoru vyriešiť, ktorej metódu má po upcastingu spustiť. V prípade imageFile # getInfo vs súbor # getInfo vo vyššie uvedenom príklade kompilátor uchováva odkaz na ImageFile‘S dostať informácie metóda.

5. Problémy s polymorfizmom

Pozrime sa na niektoré nejasnosti v polymorfizme, ktoré by mohli viesť k chybám za behu, ak nebudú správne skontrolované.

5.1. Identifikácia typu počas downcastingu

Pripomeňme, že sme po vykonaní procesu upcast skôr stratili prístup k niektorým metódam špecifickým pre podtypy. Aj keď sme to dokázali vyriešiť s poklesom, to nezaručuje skutočnú kontrolu typu.

Napríklad, ak vykonávame vyhrnutie a následné zhrnutie:

Súbor GenericFile = nový GenericFile (); ImageFile imageFile = súbor (ImageFile); System.out.println (imageFile.getHeight ());

Všimli sme si, že kompilátor umožňuje downcast a GenericFile do ImageFile, aj keď v skutočnosti trieda je GenericFile a nie ImageFile.

Ak sa teda pokúsime vyvolať getHeight () metóda na imageFile triedy, dostaneme a ClassCastException ako GenericFile nedefinuje getHeight () metóda:

Výnimka vo vlákne „main“ java.lang.ClassCastException: GenericFile nie je možné odovzdať do ImageFile

Na vyriešenie tohto problému JVM vykoná kontrolu informácií o type behu (RTTI). Môžeme sa tiež pokúsiť o explicitnú identifikáciu typu pomocou znaku inštancia kľúčové slovo takto:

ImageFile imageFile; if (súbor instanceof ImageFile) {imageFile = súbor; }

Vyššie uvedené pomáha vyhnúť sa a ClassCastException výnimka za behu. Ďalšou možnosťou, ktorú možno použiť, je zabalenie obsadenia do a skús a chytiť blok a chytanie ClassCastException.

Je potrebné poznamenať, že Kontrola RTTI je drahá z dôvodu času a zdrojov potrebných na efektívne overenie správnosti typu. Okrem toho časté používanie inštancia kľúčové slovo takmer vždy znamená zlý dizajn.

5.2. Krehký problém základnej triedy

Podľa Wikipédie sú základné alebo nadtriedy považované za krehké, ak zdanlivo bezpečné úpravy základnej triedy môžu spôsobiť poruchu odvodených tried.

Uvažujme o deklarácii nadtriedy s názvom GenericFile a jej podtrieda TextFile:

public class GenericFile {private String content; void writeContent (obsah reťazca) {this.content = obsah; } void toString (String str) {str.toString (); }}
verejná trieda TextFile rozširuje GenericFile {@Override void writeContent (obsah reťazca) {toString (obsah); }}

Keď upravíme GenericFile trieda:

verejná trieda GenericFile {// ... void toString (String str) {writeContent (str); }}

Pozorujeme, že vyššie uvedená modifikácia opúšťa TextFile v nekonečnej rekurzii v writeContent () metóda, ktorá nakoniec vyústi do pretečenia zásobníka.

Na riešenie krehkého problému základnej triedy môžeme použiť konečné kľúčové slovo, aby sa zabránilo prepísaniu podtriedy nad writeContent () metóda. Pomôcť môže aj správna dokumentácia. A v neposlednom rade by sa malo všeobecne uprednostniť zloženie pred dedičstvom.

6. Záver

V tomto článku sme diskutovali o základnom koncepte polymorfizmu so zameraním na výhody aj nevýhody.

Zdrojový kód tohto článku je ako vždy k dispozícii na serveri GitHub.


$config[zx-auto] not found$config[zx-overlay] not found