Programmering

Arv i Java, del 1: Nøkkelordet utvider

Java støtter gjenbruk av klasser gjennom arv og komposisjon. Denne todelte opplæringen lærer deg hvordan du bruker arv i Java-programmene dine. I del 1 lærer du hvordan du bruker strekker nøkkelord for å utlede en underordnet klasse fra en overordnet klasse, påkalle foreldreklassekonstruktører og -metoder og overstyre metoder. I del 2 turnerer du java.lang.Objekt, som er Javas superklasse som alle andre klasser arver fra.

For å fullføre læringen om arv, må du sjekke ut Java-tipset mitt som forklarer når du skal bruke komposisjon vs arv. Du lærer hvorfor komposisjon er et viktig supplement til arv, og hvordan du bruker den til å beskytte mot problemer med innkapsling i Java-programmene dine.

last ned Få koden Last ned kildekoden for eksempel applikasjoner i denne opplæringen. Skapt av Jeff Friesen for JavaWorld.

Java-arv: To eksempler

Arv er en programmeringskonstruksjon som programvareutviklere bruker for å etablere er-et forhold mellom kategorier. Arv gjør det mulig for oss å utlede mer spesifikke kategorier fra mer generiske. Den mer spesifikke kategorien er en slags den mer generiske kategorien. For eksempel er en brukskonto en slags konto der du kan gjøre innskudd og uttak. På samme måte er en lastebil et slags kjøretøy som brukes til å hale store gjenstander.

Arv kan synke gjennom flere nivåer, noe som fører til stadig mer spesifikke kategorier. Som et eksempel viser figur 1 bil og lastebil som arver fra kjøretøy; stasjonsvogn som arver fra bil; og søppelbil som arver fra lastebil. Pilene peker fra mer spesifikke "underordnede" kategorier (lavere ned) til mindre spesifikke "overordnede" kategorier (høyere opp).

Jeff Friesen

Dette eksemplet illustrerer enkelt arv der en underordnet kategori arver tilstand og atferd fra en umiddelbar foreldrekategori. I motsetning, flere arv gjør det mulig for en underordnet kategori å arve tilstand og atferd fra to eller flere umiddelbare foreldrekategorier. Hierarkiet i figur 2 illustrerer flere arv.

Jeff Friesen

Kategorier er beskrevet av klasser. Java støtter enkelt arv gjennom klasseutvidelse, der en klasse direkte arver tilgjengelige felt og metoder fra en annen klasse ved å utvide den. Java støtter imidlertid ikke flere arv gjennom klasseutvidelser.

Når du ser på et arvshierarki, kan du enkelt oppdage flere arv ved tilstedeværelsen av et diamantmønster. Figur 2 viser dette mønsteret i sammenheng med kjøretøy, landbil, vannbil og luftfartøy.

Søkeordet utvider

Java støtter klasseutvidelse via strekker nøkkelord. Når det er tilstede, strekker spesifiserer et foreldre-barn-forhold mellom to klasser. Nedenfor bruker jeg strekker å etablere et forhold mellom klassene Kjøretøy og Bil, og deretter mellom Regnskap og Sparekonto:

Oppføring 1. The strekker nøkkelord angir et forhold mellom foreldre og barn

klasse Kjøretøy {// medlemserklæringer} klasse Bil utvider kjøretøy {// arver tilgjengelige medlemmer fra kjøretøy // gir egne medlemserklæringer} klassekonto {// medlemserklæringer} klasse SavingsAccount utvider konto {// arver tilgjengelige medlemmer fra konto // gir egne medlemserklæringer}

De strekker nøkkelord er spesifisert etter kursnavnet og før et annet kursnavn. Klassenavnet før strekker identifiserer barnet og klassenavnet etter strekker identifiserer foreldrene. Det er umulig å spesifisere flere klassenavn etter strekker fordi Java ikke støtter klassebasert flere arv.

Disse eksemplene kodifiserer is-a-forhold: Biler en spesialisert Kjøretøy og Sparekontoer en spesialisert Regnskap. Kjøretøy og Regnskap er kjent som baseklasser, foreldrekurs, eller superklasser. Bil og Sparekonto er kjent som avledede klasser, barneklasser, eller underklasser.

Avsluttende klasser

Du kan erklære en klasse som ikke skal utvides; for eksempel av sikkerhetsmessige årsaker. I Java bruker vi endelig nøkkelord for å forhindre at noen klasser utvides. Bare prefiks en klasseoverskrift med endelig, som i siste klasse Passord. Gitt denne erklæringen, vil kompilatoren rapportere en feil hvis noen prøver å utvide Passord.

Barneklasser arver tilgjengelige felt og metoder fra foreldreklassene og andre forfedre. De arver imidlertid ikke konstruktører. I stedet erklærer barneklasser sine egne konstruktører. Videre kan de erklære sine egne felt og metoder for å skille dem fra foreldrene. Vurder oppføring 2.

Oppføring 2. An Regnskap foreldreklasse

klassekonto {privat strengnavn; privat langt beløp; Konto (strengnavn, langt beløp) {this.name = name; setAmount (beløp); } ugyldig innskudd (langt beløp) {this.beløp + = beløp; } String getName () {return name; } lang getAmount () {returbeløp; } ugyldig setAmount (langt beløp) {this.amount = beløp; }}

Oppføring 2 beskriver en generell bankkontoklasse som har et navn og et startbeløp, som begge er satt i konstruktøren. Det lar brukerne også gjøre innskudd. (Du kan gjøre uttak ved å sette inn negative beløp, men vi vil ignorere denne muligheten.) Merk at kontonavnet må angis når en konto opprettes.

Representerer valutaverdier

antall øre. Du foretrekker kanskje å bruke en dobbelt eller a flyte å lagre pengeverdier, men å gjøre det kan føre til unøyaktigheter. For en bedre løsning, vurder BigDecimal, som er en del av Java sitt standard klassebibliotek.

Oppføring 3 presenterer a Sparekonto barneklasse som utvider sin Regnskap foreldreklasse.

Oppføring 3. A Sparekonto barneklasse utvider sitt Regnskap foreldreklasse

klasse SavingsAccount utvider konto {SavingsAccount (langt beløp) {super ("besparelser", beløp); }}

De Sparekonto klasse er trivielt fordi den ikke trenger å erklære flere felt eller metoder. Det erklærer imidlertid en konstruktør som initialiserer feltene i sin Regnskap superklasse. Initialisering skjer når Regnskapsin konstruktør kalles via Java super nøkkelord, etterfulgt av en parentes-argumentliste.

Når og hvor du skal ringe super ()

Akkurat som dette() må være det første elementet i en konstruktør som kaller en annen konstruktør i samme klasse, super() må være det første elementet i en konstruktør som kaller en konstruktør i sin superklasse. Hvis du bryter denne regelen, rapporterer kompilatoren en feil. Kompilatoren vil også rapportere en feil hvis den oppdager en super() ring inn en metode; bare ringe noen gang super() i en konstruktør.

Oppføring 4 utvides ytterligere Regnskap med en Kontrollerer konto klasse.

Oppføring 4. A Kontrollerer konto barneklasse utvider sin Regnskap foreldreklasse

klasse CheckingAccount utvider konto {CheckingAccount (langt beløp) {super ("sjekker", beløp); } ugyldig uttak (langt beløp) {setAmount (getAmount () - beløp); }}

Kontrollerer konto er litt mer omfattende enn Sparekonto fordi det erklærer en ta ut() metode. Legg merke til at denne metoden ringer til setAmount () og getAmount (), hvilken Kontrollerer konto arver fra Regnskap. Du har ikke direkte tilgang til beløp felt i Regnskap fordi dette feltet er erklært privat (se Oppføring 2).

super () og konstruktøren uten argument

Hvis super() er ikke spesifisert i en underklassekonstruktør, og hvis superklassen ikke erklærer en nei-argument , vil kompilatoren rapportere en feil. Dette er fordi underklassekonstruktøren må ringe a nei-argument superklassekonstruktør når super() er ikke til stede.

Eksempel på klassehierarki

Jeg har opprettet en AccountDemo applikasjonsklasse som lar deg prøve Regnskap klassehierarki. Ta først en titt på AccountDemokildekoden.

Oppføring 5. AccountDemo viser kontoklassens hierarki

klasse AccountDemo {public static void main (String [] args) {SavingsAccount sa = new SavingsAccount (10000); System.out.println ("kontonavn:" + sa.getName ()); System.out.println ("initial beløp:" + sa.getAmount ()); sa. innskudd (5000); System.out.println ("nytt beløp etter innskudd:" + sa.getAmount ()); CheckingAccount ca = ny CheckingAccount (20000); System.out.println ("kontonavn:" + ca.getName ()); System.out.println ("initial beløp:" + ca.getAmount ()); ca innskudd (6000); System.out.println ("nytt beløp etter innskudd:" + ca.getAmount ()); ca. med trekk (3000); System.out.println ("nytt beløp etter uttak:" + ca.getAmount ()); }}

De hoved() metoden i Listing 5 viser først Sparekonto, deretter Kontrollerer konto. Forutsatt Konto.java, SavingsAccount.java, CheckingAccount.java, og AccountDemo.java kildefiler er i samme katalog, utfør en av følgende kommandoer for å kompilere alle disse kildefilene:

javac AccountDemo.java javac * .java

Utfør følgende kommando for å kjøre applikasjonen:

java AccountDemo

Du bør følge følgende utdata:

kontonavn: besparelse opprinnelig beløp: 10000 nytt beløp etter innskudd: 15000 kontonavn: sjekke opprinnelig beløp: 20000 nytt beløp etter innskudd: 26000 nytt beløp etter uttak: 23000

Overstyring av metoden (og overbelastning av metoden)

En underklasse kan overstyring (erstatt) en arvet metode slik at underklassens versjon av metoden kalles i stedet. En overstyrende metode må angi samme navn, parameterliste og returtype som metoden som overstyres. For å demonstrere har jeg erklært en skrive ut() metoden i Kjøretøy klasse nedenfor.

Oppføring 6. Erklæring om a skrive ut() metoden som skal overstyres

klasse Kjøretøy {private strengmerker; privat streng modell; privat int år; Vehicle (String make, String model, int year) {this.make = make; this.model = modell; dette.år = år; } String getMake () {return make; } String getModel () {returmodell; } int getYear () {returår; } ugyldig utskrift () {System.out.println ("Make:" + make + ", Model:" + model + ", Year:" + year); }}

Deretter overstyrer jeg skrive ut() i Lastebil klasse.

Oppføring 7. Overstyring skrive ut() i en Lastebil underklasse

klasse Truck utvider kjøretøy {privat dobbel tonnasje; Lastebil (strengmerke, strengmodell, int år, dobbel tonnasje) {super (merke, modell, år); this.tonnage = tonnasje; } dobbel getTonnage () {retur tonnasje; } ugyldig utskrift () {super.print (); System.out.println ("Tonnage:" + tonnage); }}

Lastebils skrive ut() metoden har samme navn, returtype og parameterliste som Kjøretøys skrive ut() metode. Legg også merke til det Lastebils skrive ut() metoden første samtaler Kjøretøys skrive ut() metode ved å prefiks super. til metodens navn. Det er ofte en god ide å utføre superklasselogikken først og deretter utføre underklasselogikken.

Kaller superklassemetoder fra underklassemetoder

For å kalle en superklassemetode fra den overordnede underklassemetoden, prefikser du metodenavnet med det reserverte ordet super og medlemstilgangsoperatøren. Ellers vil du ende opp med å ringe til underklassens overordnede metode. I noen tilfeller vil en underklasse maskere ikke-privat superklassefelt ved å erklære felt med samme navn. Du kan bruke super og medlemstilgangsoperatøren for å få tilgang til ikke-privat superklassefelt.

For å fullføre dette eksemplet har jeg utdraget a VehicleDemo klassen hoved() metode:

Lastebil = ny lastebil ("Ford", "F150", 2008, 0.5); System.out.println ("Make =" + truck.getMake ()); System.out.println ("Model =" + truck.getModel ()); System.out.println ("Year =" + truck.getYear ()); System.out.println ("Tonnage =" + truck.getTonnage ()); truck.print ();

Den endelige linjen, truck.print ();, samtaler lastebils skrive ut() metode. Denne metoden kaller først Kjøretøys skrive ut() å levere lastebilens merke, modell og årstall; da gir den lastebilens tonnasje. Denne delen av utgangen er vist nedenfor:

Merke: Ford, Modell: F150, År: 2008 Tonnage: 0.5

Bruk final for å blokkere metodeoverstyring

Noen ganger kan det hende du trenger å erklære en metode som av sikkerhetsgrunner eller av en annen grunn ikke bør overstyres. Du kan bruke endelig nøkkelord for dette formålet. For å forhindre overstyring, bare prefiks en metodetittel med endelig, som i endelig String getMake (). Kompilatoren vil da rapportere en feil hvis noen prøver å overstyre denne metoden i en underklasse.

Metodeoverbelastning vs overstyring

Anta at du byttet ut skrive ut() metode i liste 7 med den nedenfor:

ugyldig utskrift (streng eier) {System.out.print ("Eier:" + eier); super.print (); }

Den modifiserte Lastebil klassen har nå to skrive ut() metoder: den forrige eksplisitt erklærte metoden og metoden arvet fra Kjøretøy. De ugyldig utskrift (streng eier) metoden overstyrer ikke Kjøretøys skrive ut() metode. I stedet for det overbelastning den.

Du kan oppdage et forsøk på å overbelaste i stedet for å overstyre en metode ved kompileringstidspunktet ved å prefiksere en underklasses metodehode med @Overstyring kommentar:

@Override ugyldig utskrift (streng eier) {System.out.print ("Eier:" + eier); super.print (); }

Spesifisering @Overstyring forteller kompilatoren at den gitte metoden overstyrer en annen metode. Hvis noen forsøkte å overbelaste metoden i stedet, ville kompilatoren rapportere en feil. Uten denne kommentaren rapporterer ikke kompilatoren en feil fordi metodeoverbelastning er lovlig.

Når skal du bruke @Override

Utvikle vanen med å prefiksere overordnede metoder med @Overstyring. Denne vanen vil hjelpe deg med å oppdage feil ved overbelastning mye raskere.