Programovanie

Polymorfizmus Java a jeho typy

Polymorfizmus označuje schopnosť niektorých entít vyskytovať sa v rôznych formách. Ľudovo ho predstavuje motýľ, ktorý sa mení od larvy po kuklu po imago. Polymorfizmus existuje aj v programovacích jazykoch ako technika modelovania, ktorá umožňuje vytvoriť jediné rozhranie pre rôzne operandy, argumenty a objekty. Výsledkom polymorfizmu Java je kód, ktorý je stručnejší a ľahšie sa udržiava.

Aj keď sa tento výukový program zameriava na podtypový polymorfizmus, existuje niekoľko ďalších typov, o ktorých by ste mali vedieť. Začneme s prehľadom všetkých štyroch typov polymorfizmu.

stiahnuť Získajte kód Stiahnite si zdrojový kód napríklad pre aplikácie v tejto príručke. Vytvoril Jeff Friesen pre JavaWorld.

Typy polymorfizmu v Jave

V Jave existujú štyri typy polymorfizmu:

  1. Nátlak je operácia, ktorá slúži viacerým typom prostredníctvom konverzie implicitného typu. Napríklad celé číslo vydelíte iným celým číslom alebo hodnotu s pohyblivou rádovou čiarkou ďalšou hodnotou s pohyblivou rádovou čiarkou. Ak je jeden operand celé číslo a druhý operand je hodnota s pohyblivou rádovou čiarkou, kompilátor donútenia (implicitne prevádza) celé číslo na hodnotu s pohyblivou rádovou čiarkou, aby sa zabránilo chybe typu. (Neexistuje žiadna operácia delenia, ktorá podporuje celočíselný operand a operand s pohyblivou rádovou čiarkou.) Ďalším príkladom je odovzdanie odkazu na objekt podtriedy na parameter nadtriedy metódy. Kompilátor vynúti použitie typu podtriedy na typ nadtriedy, aby obmedzil operácie na operácie nadtriedy.
  2. Preťaženie označuje použitie rovnakého symbolu operátora alebo názvu metódy v rôznych kontextoch. Môžete napríklad použiť + vykonať sčítanie celých čísel, sčítanie pohyblivej rádovej čiarky alebo zreťazenie reťazcov v závislosti od typov jeho operandov. V triede sa môže objaviť aj viac metód s rovnakým názvom (prostredníctvom vyhlásenia a / alebo dedičstva).
  3. Parametrické polymorfizmus stanovuje, že v rámci deklarácie triedy sa názov poľa môže spájať s rôznymi typmi a názov metódy sa môže spájať s rôznymi typmi parametrov a návratov. Pole a metóda potom môžu nadobúdať rôzne typy v každej inštancii triedy (objekte). Napríklad pole môže byť typu Dvojitý (člen štandardnej knižnice triedy Java, ktorá obsahuje a dvojitý hodnota) a metóda môže vrátiť a Dvojitý v jednom objekte a rovnaké pole môže byť typu String a rovnaká metóda by mohla vrátiť a String v inom objekte. Java podporuje parametrický polymorfizmus prostredníctvom generík, o ktorých sa budem rozprávať v budúcom článku.
  4. Podtyp znamená, že typ môže slúžiť ako podtyp iného typu. Keď sa inštancia podtypu objaví v kontexte nadtypu, výsledkom vykonania operácie nadtypu v inštancii podtypu bude verzia tohto typu vykonania operácie podtypu. Zvážte napríklad fragment kódu, ktorý kreslí ľubovoľné tvary. Tento výkresový kód môžete vyjadriť stručnejšie zavedením a Tvar trieda s a kresliť () metóda; zavedením Kruh, Obdĺžnika ďalšie podtriedy, ktoré majú prednosť kresliť (); zavedením poľa typu Tvar ktorých prvky ukladajú odkazy na Tvar inštancie podtried; a zavolaním Tvarje kresliť () v každej inštancii. Keď zavoláš kresliť (), to je Kruhje, Obdĺžnikalebo iné Tvar inštancie kresliť () metóda, ktorá sa volá. Hovoríme, že existuje veľa foriem Tvarje kresliť () metóda.

Tento výukový program predstavuje polymorfizmus podtypu. Dozviete sa o upcastingu a neskorej väzbe, abstraktných triedach (ktoré sa nedajú vytvoriť inštanciami) a abstraktných metódach (ktoré sa nedajú vyvolať). Dozviete sa tiež o downcastingu a identifikácii typu runtime a získate prvý pohľad na kovariantné návratové typy. Uložím si parametrický polymorfizmus pre budúci tutoriál.

Ad-hoc vs univerzálny polymorfizmus

Rovnako ako mnoho vývojárov klasifikujem nátlak a preťaženie ako ad hoc polymorfizmus a parametrický a podtyp ako univerzálny polymorfizmus. Aj keď sú cenné techniky, neverím, že nátlak a preťaženie sú skutočným polymorfizmom; Sú to skôr typové konverzie a syntaktický cukor.

Polymorfizmus podtypu: Upcasting a neskorá väzba

Polymorfizmus podtypu závisí od upcastingu a neskorej väzby. Pripravovaný je forma castingu, pri ktorej hierarchicky dedíte hierarchiu z podtypu na supertyp. Nie je zahrnutý žiadny operátor obsadenia, pretože podtyp je špecializáciou supertypu. Napríklad, Tvar s = nový kruh (); vyvrheľ z Kruh do Tvar. To dáva zmysel, pretože kruh má akýsi tvar.

Po vykašliavaní Kruh do Tvar, nemôžete volať Kruh- špecifické metódy, ako napríklad a getRadius () metóda, ktorá vráti polomer kruhu, pretože Kruh- špecifické metódy nie sú súčasťou Tvarrozhranie používateľa. Strata prístupu k vlastnostiam podtypov po zúžení podtriedy na jej nadtriedu sa javí ako nezmyselná, je však nevyhnutná na dosiahnutie polymorfizmu podtypov.

Predpokladajme, že Tvar vyhlasuje a kresliť () metóda, jej Kruh podtrieda prepíše túto metódu, Tvar s = nový kruh (); práve vykonal a ďalší riadok určuje s.draw ();. Ktorý kresliť () metóda sa volá: Tvarje kresliť () metóda alebo Kruhje kresliť () metóda? Kompilátor nevie ktorý kresliť () spôsob volania. Všetko, čo môže urobiť, je overiť, či metóda existuje v nadtriede, a overiť, či sa zoznam argumentov volania metódy a návratový typ zhodujú s deklaráciou metódy nadtriedy. Kompilátor však tiež vloží do kompilovaného kódu inštrukciu, ktorá za behu načíta a použije akýkoľvek odkaz, v ktorom je s zavolať správne kresliť () metóda. Táto úloha je známa ako neskorá väzba.

Neskoré viazanie vs skoré viazanie

Neskorá väzba sa používa pre volania nakonečné inštančné metódy. Pre všetky ostatné volania metód kompilátor vie, ktorú metódu má volať. Vloží do kompilovaného kódu inštrukciu, ktorá volá metódu spojenú s typom premennej a nie jej hodnotu. Táto technika je známa ako skorá väzba.

Vytvoril som aplikáciu, ktorá demonštruje podtypový polymorfizmus z hľadiska upcastingu a neskorej väzby. Táto aplikácia pozostáva z Tvar, Kruh, Obdĺžnika Tvary triedy, kde je každá trieda uložená vo svojom vlastnom zdrojovom súbore. Zoznam 1 predstavuje prvé tri triedy.

Výpis 1. Deklarovanie hierarchie tvarov

class Shape {void draw () {}} class Circle extends Shape {private int x, y, r; Kruh (int x, int y, int r) {this.x = x; this.y = y; this.r = r; } // Pre stručnosť som vynechal metódy getX (), getY () a getRadius (). @Override void draw () {System.out.println ("Kreslenie kruhu (" + x + "," + y + "," + r + ")"); }} class Rectangle extends Shape {private int x, y, w, h; Obdĺžnik (int x, int y, int w, int h) {this.x = x; this.y = y; this.w = w; this.h = h; } // Pre stručnosť som vynechal metódy getX (), getY (), getWidth () a getHeight () //. @Override void draw () {System.out.println ("Kreslenie obdĺžnika (" + x + "," + y + "," + w + "," + h + ")"); }}

Zoznam 2 predstavuje súbor Tvary aplikačná trieda, ktorej hlavný() metóda riadi aplikáciu.

Zoznam 2. Upcasting a neskorá väzba v podtype polymorfizmu

class Shapes {public static void main (String [] args) {Shape [] tvary = {nový kruh (10, 20, 30), nový obdĺžnik (20, 30, 40, 50)}; pre (int i = 0; i <tvary.dĺžka; i ++) tvary [i] .draw (); }}

Vyhlásenie tvary pole demonštruje upcasting. The Kruh a Obdĺžnik odkazy sú uložené v tvary [0] a tvary [1] a sú zadaní na písanie Tvar. Každý z tvary [0] a tvary [1] sa považuje za Tvar inštancia: tvary [0] sa nepovažuje za Kruh; tvary [1] sa nepovažuje za Obdĺžnik.

Neskorú väzbu preukazuje tvary [i] .draw (); výraz. Kedy i rovná sa 0, spôsobí inštrukcia vygenerovaná kompilátorom Kruhje kresliť () metóda, ktorá sa má volať. Kedy i rovná sa 1, však tento pokyn spôsobuje Obdĺžnikje kresliť () metóda, ktorá sa má volať. Toto je podstata podtypu polymorfizmu.

Za predpokladu, že všetky štyri zdrojové súbory (Tvary.java, Tvar.java, Obdĺžnik.javaa Circle.java) sa nachádzajú v aktuálnom adresári, kompilujte ich pomocou jedného z nasledujúcich príkazových riadkov:

javac * .java javac Tvary.java

Spustite výslednú aplikáciu:

java tvary

Mali by ste dodržiavať nasledujúci výstup:

Kresliaci kruh (10, 20, 30) Kresliaci obdĺžnik (20, 30, 40, 50)

Abstraktné triedy a metódy

Pri navrhovaní hierarchií tried zistíte, že triedy bližšie k vrchu týchto hierarchií sú všeobecnejšie ako triedy nižšie. Napríklad a Vozidlo nadtrieda je všeobecnejšia ako a Nákladné auto podtrieda. Podobne a Tvar nadtrieda je všeobecnejšia ako a Kruh alebo a Obdĺžnik podtrieda.

Nemá zmysel vytvoriť všeobecnú triedu. Koniec koncov, čo by a Vozidlo objekt opísať? Podobne, aký tvar predstavuje a Tvar objekt? Namiesto kódovania prázdneho miesta kresliť () metóda v Tvar, môžeme zabrániť volaniu tejto metódy a vytváraniu inštancií tejto triedy vyhlásením oboch entít za abstraktné.

Java poskytuje abstraktné vyhradené slovo na vyhlásenie triedy, ktorú nemožno vytvoriť inštanciou. Kompilátor nahlási chybu, keď sa pokúsite vytvoriť inštanciu tejto triedy. abstraktné sa tiež používa na deklaráciu metódy bez tela. The kresliť () metóda nepotrebuje telo, pretože nedokáže nakresliť abstraktný tvar. Zoznam 3 ukazuje.

Zoznam 3. Abstrahovanie triedy Tvar a jej metódy draw ()

abstraktná trieda Tvar {abstrakt void draw (); // je povinný bodkočiarka}

Abstraktné varovania

Kompilátor nahlási chybu, keď sa pokúsite vyhlásiť triedu abstraktné a konečné. Napríklad sa kompilátor sťažuje abstraktná záverečná trieda Tvar pretože abstraktnú triedu nemožno vytvoriť inštanciu a výslednú triedu nemožno rozšíriť. Kompilátor tiež hlási chybu, keď deklarujete metódu abstraktné ale nedeklaruj jeho triedu abstraktné. Odstraňujú sa abstraktné z Tvar hlavička triedy v zozname 3 by napríklad spôsobila chybu. To by bola chyba, pretože neabstraktnú (konkrétnu) triedu nemožno vytvoriť, ak obsahuje abstraktnú metódu. Nakoniec, keď rozšírite abstraktnú triedu, musí rozširujúca trieda prepísať všetky abstraktné metódy, inak musí byť samotná rozširujúca trieda vyhlásená za abstraktnú; inak kompilátor nahlási chybu.

Abstraktná trieda môže okrem abstraktných metód alebo namiesto nich deklarovať polia, konštruktory a neabstrahové metódy. Napríklad abstrakt Vozidlo trieda môže deklarovať polia popisujúce jej značku, model a rok. Tiež by sa mohlo deklarovať, že konštruktor inicializuje tieto polia a konkrétne metódy, aby vrátil ich hodnoty. Pozrite sa na zoznam 4.

Zoznam 4. Abstrahovanie vozidla

abstraktná trieda Vehicle {private String make, model; súkromný int rok; Vozidlo (značka, model reťazca, int rok) {this.make = značka; this.model = model; this.year = year; } String getMake () {return make; } String getModel () {návratový model; } int getYear () {rok návratu; } abstrakt void move (); }

To si všimneš Vozidlo vyhlasuje abstrakt presunúť () metóda na opísanie pohybu vozidla. Napríklad auto sa skotúľa po ceste, loď pláva po vode a vzduchom letí lietadlo. VozidloPodtriedy by boli prepísané presunúť () a poskytne vhodný popis. Tiež by zdedili metódy a ich konštruktéri by volali Vozidlokonštruktér.

Downcasting a RTTI

Posun nahor v hierarchii tried, prostredníctvom upcastingu, znamená stratu prístupu k funkciám podtypu. Napríklad priradenie a Kruh namietať proti Tvar premenná s znamená, že nemôžete použiť s zavolať Kruhje getRadius () metóda. Je však možné znova získať prístup Kruhje getRadius () metóda vykonaním operácia explicitného obsadenia ako tento: Kruh c = (Kruh) s;.

Toto zadanie je známe ako vykašliavanie pretože dedíte hierarchiu dedičstva z nadtypu na podtyp (z Tvar nadtrieda do Kruh podtrieda). Aj keď je upcast vždy bezpečný (rozhranie nadtriedy je podmnožinou rozhrania podtriedy), downcast nie je vždy bezpečný. Zoznam 5 ukazuje, aké druhy problémov môžu nastať, ak použijete downcasting nesprávne.

Zoznam 5. Problém so znižovaním počtu zamestnancov

class Superclass {} class Subclass extends Superclass {void method () {}} public class BadDowncast {public static void main (String [] args) {Superclass superclass = new Superclass (); Podtrieda podtrieda = (Podtrieda) nadtrieda; podtrieda.metoda (); }}

Výpis 5 predstavuje hierarchiu tried pozostávajúcu z Nadtrieda a Podtrieda, ktorá sa rozširuje Nadtrieda. Ďalej Podtrieda vyhlasuje metóda (). Tretia trieda menom BadDowncast poskytuje a hlavný() metóda, ktorá vytvára inštancie Nadtrieda. BadDowncast potom sa pokúsi tento objekt zosadiť Podtrieda a výsledok priraďte k premennej podtrieda.

V takom prípade sa kompilátor nebude sťažovať, pretože downcasting z nadtriedy do podtriedy v hierarchii rovnakého typu je legálny. To znamená, že ak by bolo priradenie povolené, aplikácia by sa pri pokuse o spustenie zrútila podtrieda.metoda ();. V takom prípade by sa JVM pokúsilo zavolať neexistujúcu metódu, pretože Nadtrieda nedeklaruje metóda (). Pred vykonaním operácie obsadenia našťastie JVM overí, či je obsadenie legálne. Zistiť to Nadtrieda nedeklaruje metóda (), vyhodilo by to a ClassCastException objekt. (Výnimkám sa budem venovať v budúcom článku.)

Zostavte zoznam 5 takto:

javac BadDowncast.java

Spustite výslednú aplikáciu:

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