Módszer túlterhelés és felülbírálás a Java-ban
1. Áttekintés
A módszer túlterhelése és felülbírálása a Java programozási nyelv kulcsfontosságú fogalma, és mint ilyen, alapos áttekintést érdemelnek.
Ebben a cikkben megismerjük e fogalmak alapjait, és megnézzük, milyen helyzetekben lehetnek hasznosak.
2. Módszer Túlterhelés
A módszer túlterhelése egy erőteljes mechanizmus, amely lehetővé teszi számunkra az összetartó osztály API-k definiálását. Hogy jobban megértsük, miért olyan értékes tulajdonság a módszer túlterhelése, nézzünk meg egy egyszerű példát.
Tegyük fel, hogy írtunk egy naiv segédosztályt, amely különböző módszereket valósít meg két szám, három szám stb.
Ha megadtuk a módszereket félrevezető vagy félreérthető neveket, mint pl szoroz2 (), szoroz3 (), szorozzuk4 (), akkor ez egy rosszul megtervezett osztály API lenne. Itt jön szóba a módszer túlterhelése.
Egyszerűen fogalmazva, a módszer túlterhelését kétféle módon valósíthatjuk meg:
- kettő vagy több megvalósítása metódusok, amelyeknek ugyanaz a neve, de különböző számú argumentumot vesznek fel
- kettő vagy több megvalósítása metódusok, amelyeknek ugyanaz a neve, de különböző típusú argumentumokat vesznek fel
2.1. Különböző számú érvek
A Szorzó osztály dióhéjban bemutatja, hogyan kell túlterhelni a szorozni () módszer két megvalósítás egyszerű meghatározásával, amelyek különböző számú argumentumot tartalmaznak:
public class Multiplier {public int szorozni (int a, int b) {return a * b; } public int szoroz (int a, int b, int c) {return a * b * c; }}
2.2. Különböző típusú érvek
Hasonlóképpen túlterhelhetjük a szorozni () metódust azáltal, hogy különböző típusú argumentumokat fogad el:
public class Multiplier {public int szorozni (int a, int b) {return a * b; } public double szorzás (double a, double b) {return a * b; }}
Ezenkívül jogszerű meghatározni a Szorzó osztály mindkét típusú túlterheléssel:
public class Multiplier {public int szorozni (int a, int b) {return a * b; } public int szoroz (int a, int b, int c) {return a * b * c; } public double szorzás (double a, double b) {return a * b; }}
Érdemes azonban megjegyezni, hogy nem lehetséges két olyan módszer megvalósítás, amelyek csak a visszatérési típusaikban különböznek.
Hogy megértsük, miért - vegyük figyelembe a következő példát:
public int szoroz (int a, int b) {return a * b; } nyilvános kettős szorzás (int a, int b) {return a * b; }
Ebben az esetben, a kód egyszerűen nem fordítható össze a metódus hívásának kétértelműsége miatt - a fordító nem tudná, melyik megvalósítás szorozni () hívni.
2.3. Típusú promóció
A módszer túlterheléséből adódó egyik ügyes tulajdonság az ún típusú promóció, más néven a primitív megtérés szélesítése .
Egyszerűbben fogalmazva, egy adott típust implicit módon előléptetünk egy másikba, ha nincs egyezés a túlterhelt metódusnak átadott argumentumok típusai és egy adott módszer megvalósítása között.
A típus promóció működésének tisztább megértése érdekében vegye fontolóra a szorozni () módszer:
nyilvános kettős szorzás (int a, hosszú b) {return a * b; } public int szoroz (int a, int b, int c) {return a * b * c; }
Most kettővel hívjuk meg a módszert int érvek azt eredményezik, hogy a második érv előléptetésre kerül hosszú, mivel ebben az esetben a módszer nem valósítható meg kettővel int érvek.
Lássunk egy gyors egységtesztet a típus promóció bemutatásához:
@Test public void whenCalledMultiplyAndNoMatching_thenTypePromotion () {assertThat (multiplier.multiply (10, 10)). IsEqualTo (100.0); }
Ezzel szemben, ha a metódust megfelelő megvalósításnak nevezzük, akkor a típus promócióra csak nem kerül sor:
@Test public void whenCalledMultiplyAndMatching_thenNoTypePromotion () {assertThat (multiplier.multiply (10, 10, 10)). IsEqualTo (1000); }
Az alábbiakban összefoglaljuk azokat a típusú promóciós szabályokat, amelyek a módszer túlterhelésére vonatkoznak:
- byte előléptethető rövid, int, hosszú, úszó, vagy kettős
- rövid előléptethető int, hosszú, úszó, vagy kettős
- char előléptethető int, hosszú, úszó, vagy kettős
- int előléptethető hosszú, úszó, vagy kettős
- hosszú előléptethető úszó vagy kettős
- úszó előléptethető kettős
2.4. Statikus kötés
Az a képesség, hogy egy adott módszerhívást hozzárendelünk a módszer testéhez, kötésként ismert.
A módszer túlterhelése esetén a kötést statikusan hajtják végre a fordítás idején, ezért statikus kötésnek hívják.
A fordító hatékonyan beállíthatja a kötést a fordítás idején, egyszerűen ellenőrizve a módszerek aláírását.
3. Módszer felülbírálása
A módszer felülbírálása lehetővé teszi számunkra, hogy az alaposztályban definiált módszereknél aprólékos megvalósításokat biztosítsunk alosztályokban.
Míg a módszer felülírása erőteljes jellemző - tekintve, hogy ez logikus következménye az öröklés használatának, amely az OOP egyik legnagyobb pillére - mikor és hol kell felhasználni, gondosan, felhasználásonként kell elemezni.
Most nézzük meg, hogyan lehet használni a módszer felülbírálását egy egyszerű, öröklésen alapuló („is-a”) kapcsolat létrehozásával.
Itt van az alaposztály:
public class Jármű {public String gyorsulás (hosszú mph) {return "A jármű gyorsul:" + mph + "MPH."; } public String stop () {return "A jármű leállt."; } public String run () {return "A jármű fut."; }}
És itt van egy kitalált alosztály:
közosztályú autó meghosszabbítja a járművet {@Orride public String accelerate (long mph) {return "Az autó gyorsul:" + mph + "MPH."; }}
A fenti hierarchiában egyszerűen felülírtuk a felgyorsít () módszerrel az altípus finomabb megvalósítása érdekében Autó.
Itt egyértelmű ezt látni ha egy alkalmazás a Jármű osztály, akkor működhet a Autó is, mivel a felgyorsít () metódusnak ugyanaz az aláírása és ugyanaz a visszatérési típusa.
Írjunk néhány egység tesztet a Jármű és Autó osztályok:
@Test public void whenCalledAccelerate_thenOneAssertion () {assertThat (vehicle.accelerate (100)) .isEqualTo ("A jármű gyorsul: 100 MPH."); } @Test public void whenCalledRun_thenOneAssertion () {assertThat (vehicle.run ()) .isEqualTo ("A jármű fut."); } @Test public void whenCalledStop_thenOneAssertion () {assertThat (vehicle.stop ()) .isEqualTo ("A jármű leállt."); } @Test public void whenCalledAccelerate_thenOneAssertion () {assertThat (car.accelerate (80)) .isEqualTo ("Az autó gyorsul: 80 MPH."); } @Test public void whenCalledRun_thenOneAssertion () {assertThat (car.run ()) .isEqualTo ("A jármű fut."); } @Test public void whenCalledStop_thenOneAssertion () {assertThat (car.stop ()) .isEqualTo ("A jármű leállt."); }
Most nézzünk meg néhány egységtesztet, amelyek megmutatják, hogyan fuss() és álljon meg() a nem felülírt módszerek egyenlő értékeket adnak vissza mindkét esetben Autó és Jármű:
@Test public void givenVehicleCarInstances_whenCalledRun_thenEqual () {assertThat (vehicle.run ()). IsEqualTo (car.run ()); } @Test public void givenVehicleCarInstances_whenCalledStop_thenEqual () {assertThat (vehicle.stop ()). IsEqualTo (car.stop ()); }
Esetünkben mindkét osztályhoz hozzáférünk a forráskódhoz, így egyértelműen láthatjuk, hogy ezt hívjuk felgyorsít () módszer egy alapon Jármű példány és hívás felgyorsít () rajta Autó példány különböző értékeket ad vissza ugyanazon argumentumhoz.
Ezért a következő teszt azt mutatja, hogy a felülbírált metódust a Autó:
@Test public void whenCalledAccelerateWithSameArgument_thenNotEqual () {assertThat (vehicle.accelerate (100)) .isNotEqualTo (car.accelerate (100)); }
3.1. Típus Helyettesíthetőség
Az OOP egyik alapelve a típus helyettesíthetősége, amely szorosan kapcsolódik a Liskov Substitution Principle-hez (LSP).
Egyszerűen fogalmazva, az LSP ezt állítja ha egy alkalmazás egy adott alaptípussal működik, akkor annak bármely altípusával is együtt kell működnie. Így a típus-helyettesíthetőség megfelelően megmarad.
A módszer felülbírálásának legnagyobb problémája az, hogy a származtatott osztályokban előfordulhat, hogy egyes specifikus módszerek nem teljes mértékben ragaszkodnak az LSP-hez, ezért nem tudják megőrizni a típusok helyettesíthetőségét.
Természetesen helyénvaló egy felülbírált metódust készíteni a különböző típusú argumentumok elfogadására és egy másik típus visszaadására is, de teljes mértékben betartva ezeket a szabályokat:
- Ha az alaposztály egyik módszere egy adott típusú argumentumot (argumentumokat) vesz fel, akkor az felülbírált metódusnak ugyanazt a típust vagy szupertípust kell megadnia (pl. ellentmondásos metódus érvek)
- Ha az alaposztály metódusa visszatér üres, az felülbírált módszernek vissza kell térnie üres
- Ha az alaposztály metódusa primitív értéket ad vissza, akkor az felülbírált metódusnak ugyanazt a primitívet kell visszaadnia
- Ha az alaposztály egyik metódusa egy bizonyos típust ad vissza, akkor a felülbírált metódusnak ugyanazt a típust vagy altípust kell visszaadnia (más néven kovariáns visszatérési típus)
- Ha az alaposztály egyik módszere kivételt vet, akkor a felülbírált módszernek ugyanazt a kivételt vagy az alaposztály kivételének egyik altípusát kell dobnia
3.2. Dinamikus kötés
Figyelembe véve, hogy a felülbírálható módszer csak örökléssel valósítható meg, ahol van egy alaptípus és altípus (ok) hierarchiája, a fordító nem tudja lefordításkor meghatározni, hogy melyik módszert hívja meg, mivel az alaposztály és az alosztályok is meghatározzák ugyanazok a módszerek.
Ennek következtében a fordítónak ellenőriznie kell az objektum típusát, hogy megtudja, milyen metódust kell meghívni.
Mivel ez az ellenőrzés futás közben történik, a dinamikus kötés tipikus példája a módszer felülírása.
4. Következtetés
Ebben az oktatóanyagban megtanultuk a módszer túlterhelésének és a módszer felülbírálásának megvalósítását, és feltártunk néhány tipikus helyzetet, ahol ezek hasznosak.
Szokás szerint a cikkben bemutatott összes kódminta elérhető a GitHubon.