Metode Overbelastning og overstyring i Java

1. Oversikt

Metodeoverbelastning og overstyring er sentrale begreper i Java-programmeringsspråket, og som sådan fortjener de et grundig utseende.

I denne artikkelen vil vi lære det grunnleggende om disse konseptene og se i hvilke situasjoner de kan være nyttige.

2. Metodeoverbelastning

Metodeoverbelastning er en kraftig mekanisme som lar oss definere sammenhengende klasse APIer. For å bedre forstå hvorfor metodeoverbelastning er en så verdifull funksjon, la oss se et enkelt eksempel.

Anta at vi har skrevet en naiv nytteklasse som implementerer forskjellige metoder for å multiplisere to tall, tre tall og så videre.

Hvis vi har gitt metodene misvisende eller tvetydige navn, for eksempel multipliser2 (), multiplisere3 (), multipliser4 (), da ville det være et dårlig designet klasse API. Her er hvor overbelastning av metoden spiller inn.

Enkelt sagt, vi kan implementere metodeoverbelastning på to forskjellige måter:

  • implementere to eller flere metoder som har samme navn, men som tar forskjellige antall argumenter
  • implementere to eller flere metoder som har samme navn, men som tar argumenter av forskjellige typer

2.1. Ulike antall argumenter

De Multiplikator klasse viser, i et nøtteskall, hvordan du overbelaster multiplisere() metode ved ganske enkelt å definere to implementeringer som tar forskjellige antall argumenter:

offentlig klasse Multiplikator {offentlig int multiplisere (int a, int b) {return a * b; } offentlig int multiplisere (int a, int b, int c) {return a * b * c; }}

2.2. Argumenter av forskjellige typer

På samme måte kan vi overbelaste multiplisere() metode ved å få den til å godta argumenter av forskjellige typer:

offentlig klasse Multiplikator {offentlig int multiplisere (int a, int b) {return a * b; } offentlig dobbel multipliser (dobbel a, dobbel b) {return a * b; }} 

Videre er det legitimt å definere Multiplikator klasse med begge typer metodeoverbelastning:

offentlig klasse Multiplikator {offentlig int multiplisere (int a, int b) {return a * b; } offentlig int multiplisere (int a, int b, int c) {return a * b * c; } offentlig dobbel multipliser (dobbel a, dobbel b) {return a * b; }} 

Det er imidlertid verdt å merke seg det det er ikke mulig å ha to metodeimplementeringer som bare skiller seg i returtypene.

For å forstå hvorfor - la oss vurdere følgende eksempel:

offentlig int multiplisere (int a, int b) {return a * b; } offentlig dobbel multiplisere (int a, int b) {return a * b; }

I dette tilfellet, koden ville rett og slett ikke kompilere på grunn av metoden ring tvetydighet - kompilatoren ville ikke vite hvilken implementering av multiplisere() å ringe.

2.3. Type kampanje

En pen funksjon som tilbys ved overbelastning av metoden er den såkalte type promotering, aka utvidende primitiv konvertering .

Enkelt sagt promoteres en gitt type implisitt til en annen når det ikke samsvarer mellom typene av argumentene som sendes til den overbelastede metoden og en spesifikk metodeimplementering.

For å forstå klarere hvordan type promotering fungerer, bør du vurdere følgende implementeringer av multiplisere() metode:

offentlig dobbel multiplisere (int a, lang b) {return a * b; } offentlig int multiplisere (int a, int b, int c) {return a * b * c; } 

Nå, kaller metoden med to int argumenter vil føre til at det andre argumentet blir fremmet til lang, som i dette tilfellet er det ikke en samsvarende implementering av metoden med to int argumenter.

La oss se en rask enhetstest for å demonstrere typeopprykk:

@Test offentlig ugyldig nårCalledMultiplyAndNoMatching_thenTypePromotion () {assertThat (multiplier.multiply (10, 10)). IsEqualTo (100.0); }

Omvendt, hvis vi kaller metoden med en samsvarende implementering, skriver kampanjen bare ikke sted:

@Test offentlig ugyldig nårCalledMultiplyAndMatching_thenNoTypePromotion () {assertThat (multiplier.multiply (10, 10, 10)). IsEqualTo (1000); }

Her er et sammendrag av typene markedsføringsregler som gjelder for overbelastning av metoder:

  • byte kan forfremmes til kort, int, lang, flyte, eller dobbelt
  • kort kan forfremmes til int, lang, flyte, eller dobbelt
  • røye kan forfremmes til int, lang, flyte, eller dobbelt
  • int kan forfremmes til lang, flyte, eller dobbelt
  • lang kan forfremmes til flyte eller dobbelt
  • flyte kan forfremmes til dobbelt

2.4. Statisk binding

Evnen til å knytte en spesifikk metodeanrop til metodens kropp er kjent som bindende.

I tilfelle overbelastning av metoden blir bindingen utført statisk ved kompileringstidspunktet, og kalles derfor statisk binding.

Kompilatoren kan effektivt stille bindingen ved kompileringstid ved ganske enkelt å sjekke metodens signaturer.

3. Overstyring av metoden

Metodeoverstyring lar oss tilby finkornede implementeringer i underklasser for metoder definert i en basisklasse.

Mens metodeoverstyring er en kraftig funksjon - med tanke på at det er en logisk konsekvens av å bruke arv, en av de største søylene i OOP - når og hvor den skal brukes, skal den analyseres nøye, etter en per-case-basis.

La oss nå se hvordan vi bruker metodeoverstyring ved å opprette et enkelt, arvbasert forhold ("is-a").

Her er basisklassen:

public class Vehicle {public String accelerate (long mph) {return "Kjøretøyet akselererer ved:" + mph + "MPH."; } offentlig strengstopp () {retur "Kjøretøyet har stoppet."; } public String run () {return "Kjøretøyet går."; }}

Og her er en konstruert underklasse:

offentlig klasse Bil utvider kjøretøy {@Override public Streng akselerere (lang mph) {retur "Bilen akselererer ved:" + mph + "MPH."; }}

I hierarkiet ovenfor har vi ganske enkelt overstyrt akselerere() metode for å gi en mer raffinert implementering av undertypen Bil.

Her er det klart å se det hvis et program bruker forekomster av Kjøretøy klasse, så kan det fungere med forekomster av Bil også, som begge implementeringer av akselerere() metoden har samme signatur og samme returtype.

La oss skrive noen få enhetstester for å sjekke Kjøretøy og Bil klasser:

@Test offentlig ugyldig nårCalledAccelerate_thenOneAssertion () {assertThat (vehicle.accelerate (100)) .isEqualTo ("Vehicle accelerates at: 100 MPH."); } @Test offentlig ugyldig nårCalledRun_thenOneAssertion () {assertThat (vehicle.run ()) .isEqualTo ("Vehicle running."); } @Test offentlig ugyldig nårCalledStop_thenOneAssertion () {assertThat (vehicle.stop ()) .isEqualTo ("Vehicle has stop."); } @Test offentlig ugyldig nårCalledAccelerate_thenOneAssertion () {assertThat (car.accelerate (80)) .isEqualTo ("Bilen akselererer ved: 80 MPH."); } @Test offentlig ugyldig nårCalledRun_thenOneAssertion () {assertThat (car.run ()) .isEqualTo ("Vehicle running."); } @Test offentlig ugyldig nårCalledStop_thenOneAssertion () {assertThat (car.stop ()) .isEqualTo ("Kjøretøyet har stoppet."); } 

La oss nå se noen enhetstester som viser hvordan løpe() og Stoppe() metoder, som ikke overstyres, returnerer like verdier for begge Bil og Kjøretøy:

@Test public void givenVehicleCarInstances_whenCalledRun_thenEqual () {assertThat (vehicle.run ()). IsEqualTo (car.run ()); } @Test offentlig ugyldighet gittVehicleCarInstances_whenCalledStop_thenEqual () {assertThat (vehicle.stop ()). IsEqualTo (car.stop ()); }

I vårt tilfelle har vi tilgang til kildekoden for begge klassene, slik at vi tydelig kan se at det å ringe til akselerere() metode på en base Kjøretøy forekomst og ringer akselerere() på en Bil forekomst vil returnere forskjellige verdier for samme argument.

Derfor demonstrerer følgende test at den overstyrte metoden påkalles for en forekomst av Bil:

@Test offentlig ugyldig nårCalledAccelerateWithSameArgument_thenNotEqual () {assertThat (vehicle.accelerate (100)) .isNotEqualTo (car.accelerate (100)); }

3.1. Type erstatningsevne

Et kjerneprinsipp i OOP er typen av substituerbarhet, som er nært knyttet til Liskov Substitution Principle (LSP).

Enkelt sagt sier LSP det hvis et program fungerer med en gitt basetype, bør det også fungere med noen av dets undertyper. På den måten bevares type substituerbarhet riktig.

Det største problemet med metodeoverstyring er at noen spesifikke metodeimplementeringer i de avledede klassene kanskje ikke helt overholder LSP, og derfor ikke klarer å bevare typeutskiftning.

Det er selvfølgelig gyldig å lage en overstyrt metode for å godta argumenter av forskjellige typer og returnere en annen type også, men med full overholdelse av disse reglene:

  • Hvis en metode i baseklassen tar argument (er) av en gitt type, bør den overstyrte metoden ta samme type eller en supertype (a.k.a. motstridende metode argumenter)
  • Hvis en metode i basisklassen returnerer tomrom, den overstyrte metoden skal returnere tomrom
  • Hvis en metode i basisklassen returnerer en primitiv, bør den overstyrte metoden returnere den samme primitiven
  • Hvis en metode i basisklassen returnerer en bestemt type, bør den overstyrte metoden returnere samme type eller en undertype (aka kovariant retur type)
  • Hvis en metode i basisklassen kaster et unntak, må den overstyrte metoden kaste det samme unntaket eller en undertype av grunnklass unntaket

3.2. Dynamisk binding

Tatt i betraktning at overstyring av metoden bare kan implementeres med arv, der det er et hierarki av en basetype og undertype, kan ikke kompilatoren på kompileringstidspunktet bestemme hvilken metode som skal kalles, da både baseklassen og underklassene definerer samme metoder.

Som en konsekvens må kompilatoren sjekke typen objekt for å vite hvilken metode som skal påberopes.

Ettersom denne kontrollen skjer ved kjøretid, er metodeoverstyring et typisk eksempel på dynamisk binding.

4. Konklusjon

I denne opplæringen lærte vi hvordan vi implementerer metodeoverbelastning og metodeoverstyring, og vi utforsket noen typiske situasjoner der de er nyttige.

Som vanlig er alle kodeeksemplene vist i denne artikkelen tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found