Programování

Polymorfismus Java a jeho typy

Polymorfismus označuje schopnost některých entit vyskytovat se v různých formách. To je populárně reprezentováno motýlem, který se mění z larvy na kuklu na imago. Polymorfismus existuje také v programovacích jazycích jako metoda modelování, která umožňuje vytvořit jedno rozhraní pro různé operandy, argumenty a objekty. Výsledkem polymorfismu Java je kód, který je stručnější a snadněji se udržuje.

I když se tento kurz zaměřuje na podtyp polymorfismu, existuje několik dalších typů, o kterých byste měli vědět. Začneme s přehledem všech čtyř typů polymorfismu.

stáhnout Získat kód Stáhněte si zdrojový kód například pro aplikace v tomto výukovém programu. Vytvořil Jeff Friesen pro JavaWorld.

Typy polymorfismu v Javě

V Javě existují čtyři typy polymorfismu:

  1. Donucování je operace, která slouží více typů prostřednictvím převodu implicitního typu. Například vydělíte celé číslo jiným celým číslem nebo hodnotu s plovoucí desetinnou čárkou jinou hodnotou s plovoucí desetinnou čárkou. Pokud je jeden operand celé číslo a druhý operand je hodnota s plovoucí desetinnou čárkou, kompilátor nátlaky (implicitně převede) celé číslo na hodnotu s plovoucí desetinnou čárkou, aby se zabránilo chybě typu. (Neexistuje žádná operace dělení, která podporuje celočíselný operand a operand s plovoucí desetinnou čárkou.) Dalším příkladem je předání odkazu na objekt podtřídy na parametr nadtřídy metody. Kompilátor vynucuje typ podtřídy na typ nadtřídy, aby omezil operace na nadtřídu.
  2. Přetížení odkazuje na použití stejného symbolu operátora nebo názvu metody v různých kontextech. Můžete například použít + provádět sčítání celých čísel, sčítání s plovoucí desetinnou čárkou nebo zřetězení řetězců v závislosti na typech jeho operandů. Ve třídě se také může objevit více metod se stejným názvem (prostřednictvím deklarace a / nebo dědičnosti).
  3. Parametrické polymorfismus stanoví, že v rámci deklarace třídy se název pole může přidružit k různým typům a název metody se může přidružit k různým typům parametrů a návratů. Pole a metoda pak mohou nabývat různých typů v každé instanci třídy (objektu). Například pole může být typu Dvojnásobek (člen standardní knihovny tříd Java, která zabalí a dvojnásobek hodnota) a metoda může vrátit a Dvojnásobek v jednom objektu a stejné pole může být typu Tětiva a stejná metoda může vrátit a Tětiva v jiném objektu. Java podporuje parametrický polymorfismus prostřednictvím generik, o kterých pojednám v budoucím článku.
  4. Podtyp znamená, že typ může sloužit jako podtyp jiného typu. Když se instance podtypu objeví v kontextu nadtypu, provedení operace supertypu v instanci podtypu způsobí provedení této operace podtypu. Zvažte například fragment kódu, který kreslí libovolné tvary. Tento výkresový kód můžete vyjádřit výstižněji zavedením a Tvar třída s a kreslit() metoda; zavedením Kruh, Obdélníka další podtřídy, které mají přednost kreslit(); zavedením pole typu Tvar jejichž prvky ukládají odkazy na Tvar instance podtřídy; a voláním Tvarje kreslit() metoda pro každou instanci. Když zavoláš kreslit(), to je Kruhje, Obdélníknebo jiné Tvar instance kreslit() metoda, která se volá. Říkáme, že existuje mnoho forem Tvarje kreslit() metoda.

Tento kurz představuje podtyp polymorfismu. Dozvíte se o upcastingu a pozdní vazbě, abstraktních třídách (které nelze vytvořit instanci) a abstraktních metodách (které nelze vyvolat). Dozvíte se také o downcastingu a identifikaci typu runtime a získáte první pohled na typy kovariantních návratů. Uložím parametrický polymorfismus pro budoucí výukový program.

Ad-hoc vs univerzální polymorfismus

Stejně jako mnoho vývojářů klasifikuji nátlak a přetížení jako ad hoc polymorfismus a parametrický a podtyp jako univerzální polymorfismus. I když hodnotné techniky nevěřím, že nátlak a přetížení jsou opravdovým polymorfismem; spíš jako konverze typu a syntaktický cukr.

Polymorfismus podtypu: Upcasting a pozdní vazba

Polymorfismus podtypu závisí na upcastingu a pozdní vazbě. Upcasting je forma castingu, při které sesíláte hierarchii dědičnosti z podtypu na supertyp. Není zahrnut žádný operátor přetypování, protože podtyp je specializací supertypu. Například, Tvar s = nový kruh (); upcasts z Kruh na Tvar. To dává smysl, protože kruh má určitý tvar.

Po upcastingu Kruh na Tvar, nemůžete volat Kruh- specifické metody, jako je a getRadius () metoda, která vrací poloměr kruhu, protože Kruh- specifické metody nejsou součástí Tvarrozhraní. Ztráta přístupu k funkcím podtypů po zúžení podtřídy na její nadtřídu se zdá zbytečná, ale je nezbytná pro dosažení polymorfismu podtypu.

Předpokládejme to Tvar prohlašuje a kreslit() metoda, její Kruh podtřída přepíše tuto metodu, Tvar s = nový kruh (); právě provedeno a určuje další řádek s.draw ();. Který kreslit() metoda se nazývá: Tvarje kreslit() metoda nebo Kruhje kreslit() metoda? Kompilátor neví který kreslit() způsob volání. Jediné, co může udělat, je ověřit, zda metoda existuje v nadtřídě, a ověřit, že seznam argumentů volání metody a návratový typ odpovídají deklaraci metody nadtřídy. Kompilátor však také vloží instrukci do kompilovaného kódu, který za běhu načte a použije jakýkoli odkaz v s volat správné kreslit() metoda. Tento úkol je známý jako pozdní vazba.

Pozdní vazba vs časná vazba

Pozdní vazba se používá pro volání nafinále instance metody. Pro všechna ostatní volání metod kompilátor ví, kterou metodu volat. Vloží do kompilovaného kódu instrukci, která volá metodu přidruženou k typu proměnné a ne její hodnotu. Tato technika je známá jako časná vazba.

Vytvořil jsem aplikaci, která demonstruje podtypový polymorfismus, pokud jde o upcasting a pozdní vazbu. Tato aplikace se skládá z Tvar, Kruh, Obdélník, a Tvary třídy, kde je každá třída uložena ve vlastním zdrojovém souboru. Výpis 1 představuje první tři třídy.

Výpis 1. Deklarace hierarchie tvarů

třída Tvar {void draw () {}} třída Kruh rozšiřuje Tvar {soukromý int x, y, r; Kruh (int x, int y, int r) {this.x = x; this.y = y; this.r = r; } // Pro stručnost jsem vynechal metody getX (), getY () a getRadius (). @Override void draw () {System.out.println ("Kreslení kruhu (" + x + "," + y + "," + r + ")"); }} class Rectangle extends Shape {private int x, y, w, h; Obdélník (int x, int y, int w, int h) {this.x = x; this.y = y; this.w = w; this.h = h; } // Pro stručnost jsem vynechal metody getX (), getY (), getWidth () a getHeight () //. @Override void draw () {System.out.println ("Kreslení obdélníku (" + x + "," + y + "," + w + "," + h + ")"); }}

Výpis 2 představuje Tvary třída aplikace, jejíž hlavní() metoda řídí aplikaci.

Výpis 2. Upcasting a pozdní vazba v podtypu polymorfismu

class Shapes {public static void main (String [] args) {Shape [] tvary = {nový kruh (10, 20, 30), nový obdélník (20, 30, 40, 50)}; pro (int i = 0; i <tvary.délka; i ++) tvary [i] .draw (); }}

Prohlášení tvary pole ukazuje upcasting. The Kruh a Obdélník odkazy jsou uloženy v tvary [0] a tvary [1] a jsou upcast na psaní Tvar. Každý z tvary [0] a tvary [1] je považován za Tvar instance: tvary [0] není považován za Kruh; tvary [1] není považován za Obdélník.

Pozdní vazba je prokázána tvary [i] .draw (); výraz. Když i se rovná 0, způsobí instrukce generovaná překladačem Kruhje kreslit() metoda, která se má volat. Když i se rovná 1však tato instrukce způsobí Obdélníkje kreslit() metoda, která se má volat. To je podstata podtypu polymorfismu.

Za předpokladu, že všechny čtyři zdrojové soubory (Shapes.java, Shape.java, Obdélník.java, a Circle.java) jsou umístěny v aktuálním adresáři, zkompilujte je pomocí některého z následujících příkazových řádků:

javac * .java javac Shapes.java

Spusťte výslednou aplikaci:

java tvary

Měli byste dodržovat následující výstup:

Kreslící kruh (10, 20, 30) Kreslící obdélník (20, 30, 40, 50)

Abstraktní třídy a metody

Při navrhování hierarchií tříd zjistíte, že třídy blíže k horní části těchto hierarchií jsou obecnější než třídy, které jsou níže. Například a Vozidlo superclass je obecnější než a Kamion podtřída. Podobně, a Tvar superclass je obecnější než a Kruh nebo a Obdélník podtřída.

Nemá smysl vytvořit instanci generické třídy. Koneckonců, co by a Vozidlo popis objektu? Podobně jaký tvar představuje a Tvar objekt? Spíše než kódovat prázdný kreslit() metoda v Tvar, můžeme zabránit volání této metody a vytvoření instance této třídy deklarováním obou entit jako abstraktních.

Java poskytuje abstraktní vyhrazené slovo pro deklaraci třídy, kterou nelze vytvořit instancí. Kompilátor nahlásí chybu, když se pokusíte vytvořit instanci této třídy. abstraktní se také používá k deklaraci metody bez těla. The kreslit() metoda nepotřebuje tělo, protože nedokáže nakreslit abstraktní tvar. Výpis 3 ukazuje.

Výpis 3. Abstrahování třídy Shape a její metody draw ()

abstraktní třída Tvar {abstract void draw (); // je povinný středník}

Abstraktní upozornění

Při pokusu o deklaraci třídy hlásí kompilátor chybu abstraktní a finále. Například si stěžovatel stěžuje abstraktní závěrečná třída Tvar protože abstraktní třídu nelze vytvořit instanci a konečnou třídu nelze rozšířit. Kompilátor také hlásí chybu, když deklarujete metodu abstraktní ale nedeklarujte jeho třídu abstraktní. Odstraňování abstraktní z Tvar záhlaví třídy v seznamu 3 by například vedlo k chybě. To by byla chyba, protože neabstrahující (konkrétní) třídu nelze vytvořit, pokud obsahuje abstraktní metodu. Nakonec, když rozšíříte abstraktní třídu, rozšiřující třída musí přepsat všechny abstraktní metody, jinak musí být samotná rozšiřující třída deklarována jako abstraktní; jinak kompilátor nahlásí chybu.

Abstraktní třída může deklarovat pole, konstruktory a jiné než abstraktní metody kromě nebo místo abstraktních metod. Například abstrakt Vozidlo třída může deklarovat pole popisující její značku, model a rok. Může také deklarovat konstruktor, který inicializuje tato pole a konkrétní metody, aby vrátil jejich hodnoty. Podívejte se na výpis 4.

Výpis 4. Abstrahování vozidla

abstraktní třída Vehicle {private String make, model; soukromý int rok; Vehicle (String make, String model, int year) {this.make = make; this.model = model; this.year = rok; } String getMake () {return make; } String getModel () {návratový model; } int getYear () {rok návratu; } abstract void move (); }

To si všimnete Vozidlo deklaruje abstrakt hýbat se() metoda k popisu pohybu vozidla. Například auto se valí po silnici, loď pluje po vodě a letadlo letí vzduchem. VozidloPodtřídy by byly přepsány hýbat se() a poskytnout odpovídající popis. Také by zdědili metody a jejich konstruktéři by volali Vozidlokonstruktor.

Downcasting a RTTI

Přesunutí hierarchie tříd pomocí upcastingu znamená ztrátu přístupu k funkcím podtypu. Například přiřazení a Kruh vznést námitku Tvar proměnná s znamená, že nemůžete použít s zavolat Kruhje getRadius () metoda. Je však možné znovu získat přístup Kruhje getRadius () metoda provedením operace explicitního obsazení jako tento: Kruh c = (Kruh) s;.

Tento úkol je znám jako vylidňování protože sesouváte hierarchii dědičnosti ze supertypu na podtyp (z Tvar nadtřída do Kruh podtřída). Ačkoli upcast je vždy bezpečný (rozhraní nadtřídy je podmnožinou rozhraní podtřídy), downcast není vždy bezpečný. Výpis 5 ukazuje, jaké potíže by mohly nastat, pokud použijete nesprávné seskupování.

Výpis 5. Problém s downcastingem

class Superclass {} class Subclass extends Superclass {void method () {}} public class BadDowncast {public static void main (String [] args) {Superclass superclass = new Superclass (); Podtřída podtřída = (Podtřída) nadtřída; subclass.method (); }}

Výpis 5 představuje hierarchii tříd skládající se z Nadtřída a Podtřída, který se rozšiřuje Nadtřída. Dále Podtřída prohlašuje metoda(). Třetí třída s názvem BadDowncast poskytuje a hlavní() metoda, která vytváří instanci Nadtřída. BadDowncast pak se pokusí tento objekt seskočit na Podtřída a přiřadit výsledek proměnné podtřída.

V tomto případě si kompilátor nebude stěžovat, protože downcasting z nadtřídy do podtřídy ve stejné hierarchii typů je legální. To znamená, že pokud bylo přiřazení povoleno, aplikace by se při pokusu o spuštění zhroutila subclass.method ();. V tomto případě by se JVM pokoušelo zavolat neexistující metodu, protože Nadtřída nedeklaruje metoda(). Naštěstí JVM před provedením operace obsazení ověří, zda je obsazení legální. Zjistit to Nadtřída nedeklaruje metoda(), to by hodilo ClassCastException objekt. (O výjimkách pojednám v budoucím článku.)

Sestavte výpis 5 následujícím způsobem:

javac BadDowncast.java

Spusťte výslednou aplikaci:

java BadDowncast