Polimorfizmus Java-ban

1. Áttekintés

Az összes objektumorientált programozási (OOP) nyelvnek négy alapvető jellemzőre van szüksége: absztrakció, kapszulázás, öröklődés és polimorfizmus.

Ebben a cikkben a polimorfizmus két alapvető típusával foglalkozunk: statikus vagy fordítási idejű polimorfizmus és dinamikus vagy futásidejűpolimorfizmus. A statikus polimorfizmus a fordítás idején érvényesül, míg a dinamikus polimorfizmus futás közben valósul meg.

2. Statikus polimorfizmus

A Wikipedia szerint a statikus polimorfizmus az polimorfizmus, amely a fordítás idején feloldódik, és ezzel megszünteti a futási idejű virtuális tábla kereséseket.

Például a mi Szöveges fájl osztálynak egy fájlkezelő alkalmazásban három módszere lehet ugyanazzal az aláírással olvas() módszer:

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

A kódfordítás során a fordító ellenőrzi, hogy a olvas módszer megfelel a fent definiált három módszer legalább egyikének.

3. Dinamikus polimorfizmus

Dinamikus polimorfizmus mellett a A Java virtuális gép (JVM) kezeli a végrehajtáshoz szükséges megfelelő módszer észlelését, amikor egy alosztályt hozzárendelnek a szülő űrlaphoz. Erre azért van szükség, mert az alosztály felülírhatja a szülő osztályban meghatározott módszerek egy részét vagy mindegyikét.

Egy hipotetikus fájlkezelő alkalmazásban határozzuk meg az összes meghívott fájl szülőosztályát GenericFile:

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

Azt is megvalósíthatjuk Képfájl osztály, amely kiterjeszti a GenericFile de felülírja a getFileInfo () módszer és további információkat csatol:

az ImageFile nyilvános osztály kiterjeszti a GenericFile {private int height; privát int szélesség; // ... getters and setters public String getFileInfo () {return "Képfájl Impl"; }}

Amikor létrehozunk egy példányát Képfájl és rendelje hozzá a GenericFile osztályban implicit szereplőgárdát készítenek. A JVM azonban hivatkozást tart a tényleges formára Képfájl.

A fenti konstrukció analóg a módszer felülírására. Ezt megerősíthetjük a getFileInfo () módszer:

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 ("Fájl információ: \ n" + genericFile.getFileInfo ()); }

A várakozásoknak megfelelően genericFile.getFileInfo () kiváltja a getFileInfo () módszere Képfájl osztály az alábbi kimeneten látható:

Fájl információ: Képfájl Impl

4. Egyéb polimorf jellemzők a Java-ban

A Java polimorfizmusának ezen két fő típusán kívül a Java programozási nyelvben más jellemzők is vannak, amelyek polimorfizmust mutatnak. Beszéljünk néhány ilyen jellemzőről.

4.1. Kényszerítés

A polimorf kényszer a fordító implicit típusú konverzióval foglalkozik, hogy megakadályozza a típushibákat. Tipikus példa látható egész számok és karakterláncok összefűzésében:

String str = “string” + 2;

4.2. Üzemeltető túlterhelése

Az operátor vagy a módszer túlterhelése ugyanazon szimbólum vagy operátor polimorf tulajdonságára utal, amelynek a kontextustól függően eltérő jelentése (formája) van.

Például a plusz szimbólum (+) használható matematikai összeadáshoz is Húr összefűzés. Mindkét esetben csak a szövegkörnyezet (azaz argumentumtípusok) határozza meg a szimbólum értelmezését:

String str = "2" + 2; int összeg = 2 + 2; System.out.printf ("str =% s \ n összeg =% d \ n", str, összeg);

Kimenet:

str = 22 összeg = 4

4.3. Polimorf paraméterek

A paraméteres polimorfizmus lehetővé teszi egy osztályban egy paraméter vagy módszer nevének társítását különböző típusokkal. Az alábbiakban van egy tipikus példa, ahol meghatározzuk tartalom mint a Húr később pedig anként Egész szám:

a public class TextFile kiterjeszti a GenericFile {private String tartalmat; public String setContentDelimiter () {int tartalom = 100; this.content = this.content + content; }}

Azt is fontos megjegyezni A polimorf paraméterek deklarálása problémát eredményezhetváltozó rejtőzködés ahol egy paraméter helyi deklarációja mindig felülírja egy másik azonos nevű paraméter globális deklarációját.

A probléma megoldásához gyakran tanácsos globális referenciákat használni, mint pl ez kulcsszó globális változókra mutató helyi kontextusban.

4.4. Polimorf altípusok

A polimorf altípus kényelmesen lehetővé teszi számunkra, hogy egy típushoz több altípust rendeljünk, és elvárjuk, hogy a típus minden invokációja kiváltsa az altípus elérhető definícióit.

Például, ha van egy gyűjteményünk GenericFiles mi meghívjuk a szerezz információt() módszer mindegyikénél elvárhatjuk, hogy a kimenet eltérő legyen attól az altípustól függően, amelyből a gyűjtemény egyes elemei származnak:

GenericFile [] files = {új ImageFile ("SampleImageFile", 200, 100, új BufferedImage (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0"), új TextFile ("SampleTextFile" , "Ez egy minta szöveges tartalom", "v1.0.0")}; for (int i = 0; i <fájlok.hossz; i ++) {fájlok [i] .getInfo (); }

Az altípus polimorfizmusát a következők kombinációja teszi lehetővéupcasting és késői kötés. A felminősítés magában foglalja az öröklési hierarchia öntését egy típustól egy altípusig:

ImageFile imageFile = új ImageFile (); GenericFile fájl = imageFile;

A fentiek következménye az, hogy Képfájl-specifikus módszerek nem hivatkozhatnak az új előremenő verzióra GenericFile. Az altípusú módszerek azonban felülírják a szupertípusban meghatározott hasonló módszereket.

Annak érdekében, hogy megoldjuk azt a problémát, hogy nem lehet altípus-specifikus módszereket meghívni, amikor felcseréljük egy szupertípusra, elvégezhetjük az öröklődés lecsökkentését a szupertípusról az altípusra. Ezt:

ImageFile imageFile = (ImageFile) fájl;

Késői kötésA stratégia segíti a fordítót annak felderítésében, hogy kinek a módját indítsa el a feltöltés után. Az imageFile # getInfo vs. file # getInfo a fenti példában a fordító hivatkozást tart Képfájl’S szerezz információt módszer.

5. A polimorfizmus problémái

Vizsgáljuk meg a polimorfizmus néhány olyan kétértelműségét, amelyek, ha nincs megfelelően ellenőrizve, futásidejű hibákhoz vezethetnek.

5.1. Típusazonosítás lecsökkentés közben

Emlékezzünk arra, hogy korábban egy upcast végrehajtása után elvesztettük egyes altípus-specifikus módszerek hozzáférését. Bár ezt egy lesüllyesztéssel sikerült megoldanunk, ez nem garantálja a tényleges típusellenőrzést.

Például, ha egy előremenő és egy későbbi lefutást hajtunk végre:

GenericFile fájl = új GenericFile (); ImageFile imageFile = (ImageFile) fájl; System.out.println (imageFile.getHeight ());

Észrevesszük, hogy a fordító lehetővé teszi a GenericFile egy an Képfájl, annak ellenére, hogy az osztály valójában a GenericFile és nem egy Képfájl.

Következésképpen, ha megpróbáljuk a getHeight () módszer a Képfájl osztályban kapunk egy ClassCastException mint GenericFile nem határozza meg getHeight () módszer:

Kivétel a "main" szálban java.lang.ClassCastException: A GenericFile nem adható át az ImageFile fájlba

A probléma megoldása érdekében a JVM végrehajt egy futási idő típusú információkat (RTTI). Megpróbálhatunk explicit típusazonosítást is a Például az kulcsszó, mint ez:

ImageFile imageFile; if (az ImageFile fájl példánya) {imageFile = fájl; }

A fentiek segítenek elkerülni a ClassCastException futásidejű kivétel. Egy másik lehetőség, amelyet fel lehet használni, a gipsz beburkolása a próbáld ki és fogás blokkolja és elkapja a ClassCastException.

meg kell jegyezni, hogy Az RTTI-ellenőrzés drága a típus helyességének hatékony ellenőrzéséhez szükséges idő és erőforrások miatt. Ezenkívül a Például az a kulcsszó szinte mindig rossz kialakítást jelent.

5.2. Törékeny alaposztályi probléma

A Wikipedia szerint az alap vagy a felső osztályok törékenynek számítanak, ha az alaposztály látszólag biztonságos módosítása a származtatott osztályok meghibásodását okozhatja.

Vizsgáljuk meg egy úgynevezett szuperosztály deklarációját GenericFile és annak alosztálya Szöveges fájl:

public class GenericFile {private String content; void writeContent (String content) {this.content = content; } void toString (String str) {str.toString (); }}
a public class TextFile kiterjeszti a GenericFile {@Override void writeContent (String content) {toString (content); }}

Amikor módosítjuk a GenericFile osztály:

public class GenericFile {// ... void toString (String str) {writeContent (str); }}

Megfigyeljük, hogy a fenti módosítás elhagyja Szöveges fájl végtelen rekurzióban a writeContent () módszer, amely végül verem túlcsordulást eredményez.

Egy törékeny alaposztályprobléma kezelésére használhatjuk a végső kulcsszó annak megakadályozására, hogy az alosztályok felülírják a writeContent () módszer. A megfelelő dokumentáció is segíthet. Végül, de nem utolsósorban az összetételt általában előnyben kell részesíteni az öröklés helyett.

6. Következtetés

Ebben a cikkben megvitattuk a polimorfizmus alapfogalmát, az előnyökre és hátrányokra egyaránt összpontosítva.

Mint mindig, a cikk forráskódja is elérhető a GitHubon.