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.