Preopterećenje i poništavanje metoda u Javi

1. Pregled

Preopterećenje i poništavanje metoda ključni su koncepti programskog jezika Java i kao takvi zaslužuju detaljan pregled.

U ovom ćemo članku naučiti osnove tih pojmova i vidjeti u kojim situacijama mogu biti korisni.

2. Preopterećenje metode

Preopterećenje metoda moćan je mehanizam koji nam omogućuje definiranje API-ja kohezivne klase. Da bismo bolje razumjeli zašto je preopterećenje metoda tako vrijedna značajka, pogledajmo jednostavan primjer.

Pretpostavimo da smo napisali naivnu korisnu klasu koja implementira različite metode množenja dva broja, tri broja i tako dalje.

Ako smo metodama dali obmanjujuća ili dvosmislena imena, kao npr pomnoži2 (), pomnoži3 (), pomnoži4 (), onda bi to bio loše dizajniran API klase. Evo gdje preopterećenje metoda dolazi do izražaja.

Jednostavno rečeno, preopterećenje metoda možemo implementirati na dva različita načina:

  • provodeći dvije ili više metode koje imaju isti naziv, ali uzimaju različit broj argumenata
  • provodeći dvije ili više metode koje imaju isti naziv, ali uzimaju argumente različitih vrsta

2.1. Različiti brojevi argumenata

The Multiplikator klasa ukratko pokazuje kako preopteretiti pomnožiti() metodom jednostavnim definiranjem dvije implementacije koje uzimaju različit broj argumenata:

multiplikator javne klase {public int multiply (int a, int b) {return a * b; } public int multiply (int a, int b, int c) {return a * b * c; }}

2.2. Argumenti različitih vrsta

Slično tome, možemo preopteretiti pomnožiti() metoda čineći da prihvaća argumente različitih vrsta:

multiplikator javne klase {public int multiply (int a, int b) {return a * b; } javno dvostruko umnožavanje (dvostruko a, dvostruko b) {povratak a * b; }} 

Nadalje, legitimno je definirati Multiplikator klasa s obje vrste preopterećenja metode:

multiplikator javne klase {public int multiply (int a, int b) {return a * b; } public int multiply (int a, int b, int c) {return a * b * c; } javno dvostruko umnožavanje (dvostruko a, dvostruko b) {povratak a * b; }} 

Vrijedno je, međutim, napomenuti da nije moguće imati dvije implementacije metode koje se razlikuju samo po vrstama povratka.

Da bismo razumjeli zašto - razmotrimo sljedeći primjer:

javni int multiply (int a, int b) {return a * b; } javno dvostruko umnožavanje (int a, int b) {return a * b; }

U ovom slučaju, kod se jednostavno ne bi kompajlirao zbog dvosmislenosti poziva metode - kompajler ne bi znao koju implementaciju pomnožiti() zvati.

2.3. Tip Promocija

Jedna zgodna značajka koju pruža preopterećenje metode je tzv promocija tipa, odnosno širenje primitivne konverzije .

Jednostavno rečeno, jedan se tip implicitno promovira u drugi kada nema podudaranja između vrsta argumenata proslijeđenih preopterećenoj metodi i određene implementacije metode.

Da biste jasnije razumjeli kako funkcionira promocija tipa, razmotrite sljedeće implementacije pomnožiti() metoda:

javno dvostruko umnožavanje (int a, dugo b) {return a * b; } public int multiply (int a, int b, int c) {return a * b * c; } 

Sada, pozivanje metode s dva int Argumenti će rezultirati unapređivanjem drugog argumenta dugo, jer u ovom slučaju ne postoji podudaranje provedbe metode s dvije int argumenti.

Pogledajmo brzi jedinični test za demonstraciju promocije tipa:

@Test public void whenCalledMultiplyAndNoMatching_thenTypePromotion () {assertThat (multiplier.multiply (10, 10)). IsEqualTo (100.0); }

Suprotno tome, ako metodu pozovemo s odgovarajućom implementacijom, promocija tipa jednostavno se ne odvija:

@Test public void whenCalledMultiplyAndMatching_thenNoTypePromotion () {assertThat (multiplier.multiply (10, 10, 10)). IsEqualTo (1000); }

Evo sažetka pravila za promociju tipa koja se primjenjuju na preopterećenje metode:

  • bajt može biti unaprijeđen u kratko, int, dugo, plutaj, ili dvostruko
  • kratak može biti unaprijeđen u int, long, float, ili dvostruko
  • ugljen može biti unaprijeđen u int, long, float, ili dvostruko
  • int može biti unaprijeđen u dugo, plutaj, ili dvostruko
  • dugo može biti unaprijeđen u plutati ili dvostruko
  • plutati može biti unaprijeđen u dvostruko

2.4. Statičko vezanje

Sposobnost povezivanja određenog poziva metode s tijelom metode poznata je kao vezivanje.

U slučaju preopterećenja metode, vezivanje se izvodi statički u vrijeme sastavljanja, pa se to naziva statičkim vezivanjem.

Prevoditelj može učinkovito postaviti povezivanje u vrijeme prevođenja jednostavnim provjeravanjem potpisa metoda.

3. Nadjačavanje metode

Nadjačavanje metoda omogućuje nam pružanje finozrnatih implementacija u podrazredima za metode definirane u osnovnoj klasi.

Iako je nadjačavanje metode moćna značajka - s obzirom na to da je logična posljedica korištenja nasljeđivanja, jednog od najvećih stupova OOP-a - kada i gdje upotrijebiti, treba pažljivo analizirati, za svaki slučaj pojedinačno.

Pogledajmo sada kako koristiti nadjačavanje metoda stvaranjem jednostavnog odnosa zasnovan na nasljeđivanju (“is-a”).

Evo osnovne klase:

vozilo javne klase {ubrzanje javnog niza (dugi mph) {return "Vozilo ubrzava za:" + mph + "MPH."; } public String stop () {return "Vozilo se zaustavilo."; } public String run () {return "Vozilo radi."; }}

I evo izmišljene podklase:

javna klasa Automobil produžava Vozilo {@Preuzmi javni niz ubrzanje (dugi mph) {return "Auto ubrzava za:" + mph + "MPH."; }}

U gornjoj hijerarhiji jednostavno smo nadjačali ubrzati() metoda kako bi se omogućila preciznija implementacija za podtip Automobil.

Evo, jasno je to vidjeti ako aplikacija koristi instance Vozilo razreda, tada može raditi s instancama Automobil također, kao obje implementacije ubrzati() metoda imaju isti potpis i isti tip povrata.

Napišimo nekoliko jediničnih testova za provjeru Vozilo i Automobil klase:

@Test public void whenCalledAccelerate_thenOneAssertion () {assertThat (vehicle.accelerate (100)) .isEqualTo ("Vozilo ubrzava za: 100 MPH."); } @Test public void whenCalledRun_thenOneAssertion () {assertThat (vehicle.run ()) .isEqualTo ("Vozilo radi."); } @Test public void whenCalledStop_thenOneAssertion () {assertThat (vehicle.stop ()) .isEqualTo ("Vozilo se zaustavilo."); } @Test public void whenCalledAccelerate_thenOneAssertion () {assertThat (car.accelerate (80)) .isEqualTo ("Automobil ubrzava za: 80 MPH."); } @Test public void whenCalledRun_thenOneAssertion () {assertThat (car.run ()) .isEqualTo ("Vozilo radi."); } @Test public void whenCalledStop_thenOneAssertion () {assertThat (car.stop ()) .isEqualTo ("Vozilo se zaustavilo."); } 

Sada, pogledajmo neke jedinične testove koji pokazuju kako trčanje() i Stop() metode, koje nisu nadjačane, vraćaju jednake vrijednosti za obje Automobil i Vozilo:

@Test javna praznina givenVehicleCarInsistance_whenCalledRun_thenEqual () {assertThat (vehicle.run ()). IsEqualTo (car.run ()); } @Test javna praznina givenVehicleCarInsistance_whenCalledStop_thenEqual () {assertThat (vehicle.stop ()). IsEqualTo (car.stop ()); }

U našem slučaju imamo pristup izvornom kodu za obje klase, tako da možemo jasno vidjeti da je pozivanje ubrzati() metoda na bazi Vozilo instanca i pozivanje ubrzati() na a Automobil instanca vratit će različite vrijednosti za isti argument.

Stoga sljedeći test pokazuje da se nadjačana metoda poziva za instancu Automobil:

@Test public void whenCalledAccelerateWithSameArgument_thenNotEqual () {assertThat (vehicle.accelerate (100)) .isNotEqualTo (car.accelerate (100)); }

3.1. Zamjenjivost tipa

Osnovno načelo OOP-a je zamjena tipa, koja je usko povezana s načelom zamjene Liskova (LSP).

Jednostavno rečeno, LSP to navodi ako aplikacija radi s danim osnovnim tipom, tada bi trebala raditi i s bilo kojim od njenih podtipova. Na taj se način zamjenjivost tipa pravilno čuva.

Najveći problem nadjačavanja metode je taj što se neke specifične implementacije metoda u izvedenim klasama možda neće u potpunosti pridržavati LSP-a i stoga neće uspjeti sačuvati zamjenjivost tipa.

Naravno, valja napraviti nadjačanu metodu za prihvaćanje argumenata različitih vrsta i vraćanje druge vrste, ali uz potpuno poštivanje ovih pravila:

  • Ako metoda u osnovnoj klasi uzima argumente (da) datog tipa, nadjačana metoda treba uzeti isti tip ili supertip (a.k.a. kontravarijantna argumenti metode)
  • Ako se metoda u osnovnoj klasi vrati poništiti, nadjačana metoda trebala bi se vratiti poništiti
  • Ako metoda u osnovnoj klasi vrati primitiv, nadjačana metoda treba vratiti isti primitiv
  • Ako metoda u osnovnoj klasi vrati određeni tip, nadjačana metoda treba vratiti isti tip ili podtip (tzv. kovarijantni tip povratka)
  • Ako metoda u osnovnoj klasi baca iznimku, nadjačana metoda mora baciti istu iznimku ili podtip iznimke osnovne klase

3.2. Dinamičko povezivanje

Uzimajući u obzir da se nadjačavanje metode može implementirati samo s nasljeđivanjem, gdje postoji hijerarhija osnovnog tipa i podtipova, kompajler ne može odrediti u vrijeme kompajliranja koju metodu pozvati, jer i osnovna klasa i potklase definiraju iste metode.

Kao posljedica toga, prevoditelj mora provjeriti vrstu objekta kako bi znao koju metodu treba pozvati.

Kako se ovo provjeravanje događa tijekom izvođenja, poništavanje metoda tipičan je primjer dinamičkog vezanja.

4. Zaključak

U ovom smo tutorijalu naučili kako implementirati preopterećenje metoda i nadjačavanje metoda te istražili neke tipične situacije u kojima su korisne.

Kao i obično, svi uzorci koda prikazani u ovom članku dostupni su na GitHubu.