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:
- 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.
- 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). - 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 advojitý
hodnota) a metóda môže vrátiť aDvojitý
v jednom objekte a rovnaké pole môže byť typuString
a rovnaká metóda by mohla vrátiť aString
v inom objekte. Java podporuje parametrický polymorfizmus prostredníctvom generík, o ktorých sa budem rozprávať v budúcom článku. - 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 akresliť ()
metóda; zavedenímKruh
,Obdĺžnik
a ďalšie podtriedy, ktoré majú prednosťkresliť ()
; zavedením poľa typuTvar
ktorých prvky ukladajú odkazy naTvar
inštancie podtried; a zavolanímTvar
jekresliť ()
v každej inštancii. Keď zavoláškresliť ()
, to jeKruh
je,Obdĺžnik
alebo inéTvar
inštanciekresliť ()
metóda, ktorá sa volá. Hovoríme, že existuje veľa foriemTvar
jekresliť ()
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 Tvar
rozhranie 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á: Tvar
je kresliť ()
metóda alebo Kruh
je 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ĺžnik
a 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 Kruh
je kresliť ()
metóda, ktorá sa má volať. Kedy i
rovná sa 1
, však tento pokyn spôsobuje Obdĺžnik
je 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.java
a 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. Vozidlo
Podtriedy by boli prepísané presunúť ()
a poskytne vhodný popis. Tiež by zdedili metódy a ich konštruktéri by volali Vozidlo
konš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ť Kruh
je getRadius ()
metóda. Je však možné znova získať prístup Kruh
je 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