super
Object
-klassenEn klassedefinisjon er definisjon av en ny datatype. Klassen beskriver hvilke egenskaper objekter av den nye typen har:
Objektenes data er bestemt av klassens datafelter, mens objektenes operasjoner er bestemt av klassens metoder.
I naturen og samfunnet er det svært vanlig at vi kan klassifisere ting i form av hierarkier.
Alle medlemmer av en undergruppe har alle egenskapene til gruppa den er avledet fra. I tillegg har medlemmene av undergruppa noen spesielle egenskaper som gjør at dens medlemmer til sammen vil utgjøre en delmengde av den gruppa de er avledet fra. For eksempel er alle pattedyr dyr, men det er ikke alle dyr som er pattedyr. Alle romaner er bøker, men det er ikke alle bøker som er romaner, og så videre.
Et programmeringsspråk brukes til å modellere virkeligheten. Da er det
nyttig at språket inneholder mekanismer som kan brukes til å gjenspeile slike
hierarkier som virkeligheten består av. Javas mekanisme for dette er
muligheten til å definere subklasser, eller avledete klasser
som de også kalles. Dersom vi for eksempel har definert en klasse
Bok
som datatype for bøker generelt, vil vi da for fagbøker
definere en subklasse Fagbok
som datatype for fagbøker.
For å definere en subklasse av en gitt klasse, bruker vi
nøkkelordet extends
.
Vi kjenner dette nøkkelordet allerede fra
vindusklasser, der vi har definert en subklasse til klassen
JFrame
som vi importerte fra klassebiblioteket. Nå skal vi
se nærmere på hva dette egentlig innebærer, og vi skal definere subklasser til
klasser som vi selv har definert.
Anta at klassen Ansatt
allerede er definert og skal representere
en ansatt i en eller annen virksomhet, slik at det i denne klassen er lagt inn
datafelter for navn, adresse og andre data vi ønsker å registrere for hver
enkelt ansatt, slik vi gjorde i
kapittel 8.
Vi skal definere en klasse FagligAnsatt
som
representerer en faglig ansatt. De faglig ansatte er, til forskjell fra ansatte
ellers, knyttet til et eller annet fag. Dette faget ønsker vi skal registreres
for de faglig ansatte, i tillegg til de data som skal registreres for alle
ansatte. Istedenfor å definere klassen FagligAnsatt
som en
selvstendig klasse der vi skriver inn alt vi har skrevet i klassen
Ansatt
og i tillegg tilføyer et datafelt for fag, kan vi og bør
vi da definere FagligAnsatt
som en subklasse til
Ansatt
ved at vi skriver
class FagligAnsatt extends Ansatt { private String fag; // alle datafeltene i Ansatt blir arvet og //skal derfor ikke skrives om igjen. . . . }
Ansatt
er nå superklassen til
FagligAnsatt
. Vi kan definere så mange subklasser vi vil til
én og samme klasse, som dermed blir superklasse til alle disse.
Virkningen av å definere en subklasse er at alt som er definert i superklassen også vil være definert for subklassen, uten at definisjonen for superklassen blir gjentatt. (Det er et unntak for konstruktører, se nedenfor.) Vi sier at det som er definert i superklassen blir arvet til subklassen. I tillegg kan vi i subklassen legge inn tilleggsdefinisjoner (datafelter og metoder) for ting som er spesielle for objekter som faller inn under subklassen, slik vi har eksempel på ovenfor: En faglig ansatt er knyttet til et fag, noe som ikke er tilfelle for alle ansatte. For øvrig vil det i en subklasse ofte være behov for å redefinere metoder som blir arvet fra superklassen, slik at vi får tilpasset dem til subklasseobjektene. På engelsk brukes ordet 'override' for slik redefinering av metoder.
Når vi skriver programmer, og definerer klasser, vil følgende spørsmål ofte melde seg: Skal klassen være en subklasse til en annen klasse, eller ikke? Rettesnoren bør da alltid være dette: Vil subklasseobjektene være en naturlig undergruppe av superklasseobjektene? Dersom svaret er ja, definerer vi en subklasse, ellers ikke. (Legg merke til at i eksemplet ovenfor stemmer dette: de faglig ansatte er en naturlig undergruppe av samlingen av alle ansatte.)
En annen rettesnor kan være dette: Definer ikke en subklasse uten at alle de metodene som dermed blir arvet er meningsfulle for subklasseobjektene.
super
En konstruktørs oppgave er å reservere plass i memory for et nytt objekt,
samt å foreta diverse initialisering. En konstruktør blir ikke arvet
på samme måte som det andre klasseinnholdet i superklassen. Det er imidlertid
absolutt nødvendig at en konstruktør utfører sin initialiseringsjobb.
Systemet krever at alle datafelter i superklassen initialiseres før
det foretas initialisering av subklassens datafelter. Derfor må kall på
en av superklassens konstruktører være første instruksjon i alle
konstruktører for subklasser. En superklassekonstruktør får vi kalt opp
ved bruk av nøkkelordet super
, sammen med eventuelle parametre:
super(); // kall på superklassens defaultkonstruktør super( < aktuelle parametre > ); // kall på en annen av // superklassens konstruktører
Merk deg at dersom kall på en av superklassens konstruktører ikke står som første instruksjon i en konstruktør for subklassen, så vil systemet automatisk foreta kall på superklassens default-konstruktør. Dersom en slik ikke finnes (fordi det bare er definert konstruktør(er) med parametre), vil programmet ikke la seg kompilere.
Initialisering av datafelter kan skje på tre måter:
class Initialiseringseksempel { private int nr; private int antall = 0; private String navn; public Initialiseringseksempel( String n ) { navn = n; } . . . } class Test { private Initialiseringseksempel e = new Initialiseringseksempel( "eksempel" ); // e.nr får default-verdi 0 // e.antall får verdi 0 // e.navn får verdi "eksempel" . . . }
Som nevnt foran, er det i en subklasse ofte behov for å modifisere
metoder som blir arvet fra superklassen, slik at de blir tilpasset
subklasseobjektene. Metodene må da redefineres. Men ofte er det da nok å
tilføye noe kode til den som allerede er skrevet i superklassens definisjon
av metoden, uten at den eksisterende koden endres. For å slippe å skrive alt
om igjen, kan vi da ved hjelp av nøkkelordet super
gjøre kall
på superklassens versjon av den metode som redefineres.
Vi ønsker å redefinere metoden toString
som blir arvet fra
superklassen. Subklassens versjon av metoden skal tilføye noe ekstra tekst
i tillegg til den tekst som blir returnert av superklassens versjon av
metoden. Da kan vi skrive slik (uttrykt i pseudokode):
public String toString() { String s = super.toString(); //kall på superklassens toString-metode s += < ekstra tekst >; //tilføyer den ekstra teksten return s; }
Vi tar utgangspunkt i klassen
Ansatt
som ble definert og
brukt i Programeksempel 4 i kapittel 8.
Klassen er gjengitt nedenfor. Klassen gjør bruk av klassen
Dato
som du kan finne i
nevnte eksempel i kapittel 8.
Det skal til klassen Ansatt
defineres en subklasse FagligAnsatt
, med et datafelt
for fag. Dessuten skal toString
-metoden tilpasses, slik at den
returnerer faget i tillegg til de andre dataene.
Faglig ansatte skal sikres en lønnsplassering på minimum lønnstrinn 45.
Det skal også til klassen Ansatt
defineres en subklasse
Administrator
. For administratorer skal det, i tillegg til det som er
felles for alle ansatte, registreres seksjon og stilling, slik at det
i subklassen Administrator
må være datafelter for dette. Også
for denne subklassen skal toString
-metoden tilpasses, slik at
den returnerer seksjon og stilling i tillegg til de andre dataene.
Administrativt ansatte skal sikres en lønnsplassering på minimum lønnstrinn 41.
For begge subklasser skal alle datafelter initialiseres ved bruk av konstruktører og nødvendige konstruktørparametre. Det skal skrives et lite program som tester ut klassene ved å opprette to objekter av hver klasse og skrive ut deres data.
Alle klasser som brukes av programmet, unntatt klassen Dato
,
er gjengitt nedenfor.
1 public class Ansatt //ingen endringer i forhold til tidligere 2 { 3 private String navn; 4 private Dato født, tiltrådt; 5 private int lønnstrinn; 6 private final int MIN = 30, MAX = 80; 7 8 public Ansatt( String n, Dato f, Dato t ) 9 { 10 navn = n; 11 født = f; 12 tiltrådt = t; 13 lønnstrinn = MIN; 14 } 15 16 public String getNavn() 17 { 18 return navn; 19 } 20 21 public int getLønnstrinn() 22 { 23 return lønnstrinn; 24 } 25 26 public void setLønnstrinn( int trinn ) 27 { 28 if ( trinn > lønnstrinn && trinn <= MAX ) 29 lønnstrinn = trinn; 30 else if ( trinn > MAX ) 31 lønnstrinn = MAX; 32 } 33 34 public String toString() 35 { 36 return navn + ", født: " + 37 født.toString() + ", tiltrådt: " + 38 tiltrådt.toString(); 39 } 40 } 1 public class FagligAnsatt extends Ansatt 2 { 3 private String fag; 4 private final int FAGLIGMINIMUM = 45; 5 6 public FagligAnsatt( String n, Dato f, Dato t, String fg ) 7 { 8 super( n, f, t ); 9 fag = fg; 10 setLønnstrinn( FAGLIGMINIMUM ); 11 } 12 13 public String toString() 14 { 15 return super.toString() + ", fagområde: " + fag; 16 } 17 18 public void setLønnstrinn( int trinn ) 19 { 20 if ( trinn < FAGLIGMINIMUM ) 21 trinn = FAGLIGMINIMUM; 22 super.setLønnstrinn( trinn ); 23 } 24 } 1 public class Administrator extends Ansatt 2 { 3 private String seksjon, stilling; 4 private final int ADMINMINIMUM = 41; 5 6 public Administrator( String n, Dato f, Dato t, String seksj, 7 String st ) 8 { 9 super( n, f, t ); 10 seksjon = seksj; 11 stilling = st; 12 setLønnstrinn( ADMINMINIMUM ); 13 } 14 15 public String toString() 16 { 17 return super.toString() + ", seksjon: " + seksjon + 18 ", stilling: " + stilling; 19 } 20 21 public void setLønnstrinn( int trinn ) 22 { 23 if ( trinn < ADMINMINIMUM ) 24 trinn = ADMINMINIMUM; 25 super.setLønnstrinn( trinn ); 26 } 27 }
I de to subklassene FagligAnsatt
og Administrator
merker vi oss følgende:
Ansatt
.setLønnstrinn
, som arves fra superklassen, blir
i subklassen redefinert slik at den sikrer en minimumslønn i samsvar med
det som er beskrevet for stillingstypen som subklassen gjelder for.
I den redefinerte metoden gjøres det kall på superklassens tilsvarende metode.
Denne vil sikre at lønnstrinnet ikke settes høyere enn den grense som gjelder for
alle ansatte. I subklassens konstruktør er det subklassens metode
setLønnstrinn
som vil bli kalt opp.toString
-metoden. Denne gjør
kall på superklassens toString
-metode og tilføyer deretter den
informasjon som bare gjelder for subklasseobjekter.private
aksess.
For å tildele dem verdier bruker vi dels superklassens konstruktør, dels
superklassens public
metode
setLønnstrinn
.I klassen Ansatte
som er gjengitt nedenfor, blir det opprettet
to objekter av hver av de definerte subklassetypene. For å teste ut metoden
setLønnstrinn
, forsøker vi for noen av objektene å sette et
lønnstrinn som er utenfor de tillatte grensene.
1 //Testklasse som oppretter to objekter av hver av de 2 //definerte subklassetypene. 3 public class Ansatte 4 { 5 private Administrator direktør, sekretær; 6 private FagligAnsatt matematikklærer, programmeringslærer; 7 8 public Ansatte() 9 { 10 direktør = new Administrator( "Siri Ledestjerne", 11 new Dato( 13, 1, 1983 ), new Dato( 1, 1, 2009 ), 12 "sentraladministrasjon", "direktør" ); 13 direktør.setLønnstrinn( 85 ); 14 sekretær = new Administrator( "Per Skrivekarl", 15 new Dato( 17, 5, 1989 ), new Dato( 1, 4, 2010 ), 16 "papirmølla", "sekretær" ); 17 sekretær.setLønnstrinn( 40 ); 18 matematikklærer = new FagligAnsatt( "Tone Regnevik", 19 new Dato( 20, 6, 1985 ), new Dato( 1, 8, 2008 ), 20 "matematikk" ); 21 programmeringslærer = new FagligAnsatt( "Else Fortun", 22 new Dato( 29, 2, 1990 ), new Dato( 1, 8, 2006 ), "data" ); 23 programmeringslærer.setLønnstrinn( 57 ); 24 } 25 26 public String toString() 27 { 28 String ansatte = "ANSATTE\n"; 29 ansatte += direktør.toString() + "\n"; 30 ansatte += sekretær.toString() + "\n"; 31 ansatte += matematikklærer.toString() + "\n"; 32 ansatte += programmeringslærer.toString(); 33 return ansatte; 34 } 35 36 public String getLønnsoversikt() 37 { 38 String tabell = "Navn\t\tLønnstrinn\n"; 39 tabell += direktør.getNavn() + "\t\t" + 40 direktør.getLønnstrinn() + "\n"; 41 tabell += sekretær.getNavn() + "\t\t" + 42 sekretær.getLønnstrinn() + "\n"; 43 tabell += matematikklærer.getNavn() + "\t\t" + 44 matematikklærer.getLønnstrinn() + "\n"; 45 tabell += programmeringslærer.getNavn() + "\t\t" + 46 programmeringslærer.getLønnstrinn(); 47 return tabell; 48 } 49 } 1 import javax.swing.JTextArea; 2 import javax.swing.JOptionPane; 3 4 public class Ansattest 5 { 6 public static void main( String[] args ) 7 { 8 Ansatte s = new Ansatte(); 9 String utskrift = s.toString(); 10 utskrift += "\n\nLØNNSOVERSIKT\n" + s.getLønnsoversikt(); 11 JTextArea tavle = new JTextArea( utskrift ); 12 JOptionPane.showMessageDialog( null, tavle, "Ansatte", 13 JOptionPane.PLAIN_MESSAGE ); 14 } 15 }
Når vi kjører programmet, vil vi få fram følgende vindu:
Vi merker oss her følgende:
De fire nivåene for aksessibilitet av datafelter, metoder, etc. ble
beskrevet på slutten av kapittel 8.
Som vi har vært inne på tidligere, er det for datafelter vanlig å begrense
den direkte tilgangen til dem til den klassen som datafeltene ligger i,
ved å gi dem private
aksess. Med tanke på at det skal være mulig
å nå dem fra subklasser, kan det noen ganger vurderes å gi dem protected
aksess, slik at det også i subklasser er mulig å referere direkte til dem.
Hensikten kunne være å øke effektiviteten, siden det er litt mer effektivt å
gjøre direkte aksess på datafelter enn å gjøre det via set
- og
get
-metoder.
Men som det går fram av beskrivelsen i kapittel 8, vil konsekvensen bli at vi da kan referere
direkte til dem også fra alle andre klasser som tilhører samme pakken.
Aksessnivået protected
er med andre ord ganske "åpent", det gir
dårlig beskyttelse. For å gi en liten illustrasjon av dette, går vi tilbake til
de tre klassene Ansatt, FagligAnsatt
og Administrator
i
programeksempel 1 ovenfor. Som alternativ tenker vi oss at vi i klassen
Ansatt
brukte protected
aksess på datafeltene, slik
at klassen så ut som følger:
public class Ansatt { protected String navn; protected Dato født, tiltrådt; protected int lønnstrinn; public static final int MIN = 30, MAX = 80; < resten av klassen som før > }
Klassen Administrator
kunne vi da ha skrevet på følgende måte:
public class Administrator extends Ansatt { private String seksjon, stilling; public static final int ADMINMINIMUM = 41; public Administrator( String n, Dato f, Dato t, String seksj, String st ) { super( n, f, t ); seksjon = seksj; stilling = st; lønnstrinn = ADMINMINIMUM; // kan referere direkte til lønnstrinn } public String toString() { return super.toString() + ", seksjon: " + seksjon + ", stilling: " + stilling; } public void setLønnstrinn( int trinn ) { if ( trinn < ADMINMINIMUM ) trinn = ADMINMINIMUM; lønnstrinn = trinn; // kan referere direkte til lønnstrinn } }
På tilsvarende måte kunne vi endret klassen FagligAnsatt
.
Dersom vi nå i klassen Ansatte
hadde opprettet objektene på samme
måte som før, med instruksjonene
direktør = new Administrator( "Siri Ledestjerne", new Dato( 13, 1, 1983 ), new Dato( 1, 1, 2009 ), "sentraladministrasjon", "direktør" ); direktør.setLønnstrinn( 85 ); sekretær = new Administrator( "Per Skrivekarl", new Dato( 17, 5, 1989 ), new Dato( 1, 4, 2010 ), "papirmølla", "sekretær" ); sekretær.setLønnstrinn( 40 ); matematikklærer = new FagligAnsatt( "Tone Regnevik", new Dato( 20, 6, 1985 ), new Dato( 1, 8, 2008 ), "matematikk" ); programmeringslærer = new FagligAnsatt( "Else Fortun", new Dato( 29, 2, 1990 ), new Dato( 1, 8, 2006 ), "data" ); programmeringslærer.setLønnstrinn( 57 );
ville følgende ha skjedd: Direktøren ville nå ha blitt tildelt
lønnstrinn 85, i strid med virksomhetens begrensninger, for i
Administrator
-klassens metode setLønnstrinn
er det
ingen test på maksimalverdi. (Det var det heller ikke i vår opprinnelige
Administrator
-klasse.)
En annen sak er at dersom klassen Ansatte
ligger i samme pakke
som klassen Ansatt
, så har vi i klassen Ansatte
mulighet for å opprette objekter for eksempel på denne måte:
direktør = new Administrator( "Siri Ledestjerne", new Dato( 13, 1, 1983 ), new Dato( 1, 1, 2009 ), "sentraladministrasjon", "direktør" ); direktør.lønnstrinn = 90; // direkte tilgang til datafeltet, // ingen kontroll med tilordnet verdi sekretær = new Administrator( "Per Skrivekarl", new Dato( 17, 5, 1989 ), new Dato( 1, 4, 2010 ), "papirmølla", "sekretær" ); sekretær.lønnstrinn = 30; // direkte tilgang til datafeltet, // ingen kontroll med tilordnet verdi
Når vi bruker protected
aksess, gir vi altså mulighet til
direkte aksess til datafeltene ikke bare fra subklasser, men fra alle andre
klasser i samme pakke, forutsatt at klassene har tilgang til et objekt av
vedkommende klassetype. Eksemplet ovenfor viste hvordan vi da kunne tilordne
verdier utenfor det spesifiserte gyldighetsintervallet. Enda mer alvorlig er
det at det da kunne være mulig å tilordne verdier som ville være helt meningsløse,
slik som for eksempel en negativ verdi for lønnstrinn. Vi kunne risikere å ende
opp med data helt uten mening, data som medførte at hele systemet brøt sammen.
Et annet problem som kan oppstå i tilfelle vi bruker
protected
aksess og dermed åpner for muligheten til å gjøre direkte aksess på datafeltene
fra subklasser, er at vi inviterer til å programmere subklassene slik at de
avhenger av hvordan superklassen er implementert. Dette kan ha som konsekvens
at dersom vi gjør en liten endring i implementasjonen av superklassen, så må
også alle subklasser endres. Det trenger ikke være større endring enn at vi
gjør en liten navneendring i et datafelt. Vi får kode som er lite robust,
siden en liten endring ett sted kan få store konsekvenser mange andre steder.
Dessuten kan det resultere i funksjonelle feil som kan være vanskelige å oppdage.
Generelt bør vi programmere slik at det til en klasse ikke kan defineres
subklasser som avhenger av hvordan klassen har implementert sine data.
Subklasser bør bare være avhengig av det vi kan kalle
superklassens tjenester, det vil si av dens public
metoder,
ikke av hvordan superklassen har implementert sine data. Følger vi denne
regelen, kan vi endre implementasjonen av superklassen uten at det får noen
konsekvenser for subklassene, forutsatt at superklassen fortsatt tilbyr de
samme tjenestene, det vil si har metoder med samme signatur og virkemåte som
tidligere.
Object
-klassenAlle javaklasser er direkte eller indirekte subklasse til klassebibliotekets
klasse Object
. Det betyr at dersom vi skriver
class Klassenavn { < hva som helst av klasseinnhold > }
så er dette likeverdig med at vi hadde skrevet
class Klassenavn extends Object { < hva som helst av klasseinnhold > }
Det betyr også at dersom vi definerer en subklasse til en annen klasse
enn Object
, så blir subklassen vår en indirekte subklasse til
Object
. Alle klasser vil altså arve de metodene som er definert i
Object
. Klassen ligger i pakken java.lang
og blir derfor
automatisk importert til alle javaprogrammer. Dokumentasjon av klassen
Object
kan du finne på adressen
http://download.oracle.com/javase/8/docs/api/java/lang/Object.html.
Den viktigste av metodene som arves er kanskje toString
-metoden.
Alle klasser vi skriver har altså en toString
-metode. Dersom
vi ikke eksplisitt skriver noen slik metode i klassen vår, er det da den
som arves fra Object
-klassen som vil bli utført i tilfelle kall.
(Husk at dersom vi skjøter sammen et objekt med en streng ved å bruke
pluss-operatoren, så vil det automatisk bli kall på objektets
toString
-metode.) Det sier seg selv at Object
-klassens
toString
-metode må returnere ganske generelle opplysninger, siden
det i Object
-klassen er umulig å vite noe om hva som måtte befinne
seg i dens utallige subklasser. Som eksempel kan vi ta for oss klassen
Klokke
som ble brukt i
programeksempel 3 i kapittel 8. Dersom vi
i dette programmets vindusklasse legger inn utskriftsinstruksjonen
System.out.println( ur.toString() );
(der ur
er et Klokke
-objekt), så vil vi få en utskrift som
Klokke@ec16a4
Det eneste vi drar kjensel på her er klassenavnet. Det som følger etter
er krøllalfa-tegnet etterfulgt av objektets såkalte hashkode uttrykt som et
heksadesimalt tall uten fortegn. Dette er noe som vi svært sjelden trenger å
ha kjennskap til. (For øvrig vil denne hashkoden endre seg fra kjøring til
kjøring, siden den er avhengig av objektets memoryadresse.)
Det sier seg derfor selv at for de aller fleste klasser som
vi skriver, så vil det være fornuftig å redefinere toString
-metoden
slik at den returnerer forståelige og informative opplysninger om det objektet
som brukes til å gjøre kall på metoden.
En annen metode fra Object
-klassen som det er nyttig å ha
kjennskap til, er equals
-metoden. Dersom vi har to objekter
a
og b
av en eller annen klassetype, kan vi alltid
sammenlikne dem ved å skrive
if ( a.equals( b ) ) // rekkefølgen for a og b spiller ingen rolle ...
Implementasjonen av equals
-metoden i Object
-klassen
virker på den måten at den sjekker om a
og b
refererer
til samme fysiske objekt, altså om pekerverdiene er lik hverandre. I mange tilfeller
er det ikke dette vi er interessert i, men om to forskjellige objekter har samme
innhold. Derfor vil det i mange tilfeller være aktuelt å redefinere
equals
-metoden slik at den utfører sammenlikning på den måten
vi ønsker det. Slik redefinering er blant annet gjort i klassebibliotektets
String
-klasse, slik at metoden returnerer true
i tilfelle
to String
-objekter har samme innhold, og returnerer
false
ellers.
I følgende eksempel er det programmert klasser som representerer de geometrinske figurene i følgende hierarki:
For å begrense størrelsen på eksemplet, er det for hver figurtype bare registrert navn og relevante
dimensjoner. Det er programmert metoder for beregning av areal og eventuelt volum, samt
en toString
-metode som returnerer en beskrivelse av figuren og dens data.
I tillegg er det lagt inn nødvendige get
-metoder. Siden alle figurklassene er lagt
inn i samme fil, kan de ikke defineres som public
-klasser. En javafil kan som kjent
bare inneholde én klasse som er public
. Figurklassene finnes i fila
Figurtyper.java
.
Klassen Figur
på toppen av hierarkiet har bare som oppgave å inneholde det som er felles for alle
figurtypene. Dens subklasse TodimensjonalFigur
har som oppgave å inneholde det som i tillegg er felles
for alle todimensjonale figurer, mens subklassen TredimensjonalFigur
har tilsvarende oppgave
for tredimensjonale figurer. Disse tre klassene representerer altså ikke konkrete figurtyper. Det normale
ville derfor være å definere dem som såkalt abstrakte klasser, men slike blir først
omtalt i neste kapittel.
Todimensjonale figurer har nettopp to dimensjoner. I klassen er disse kalt bredde og høyde.
Det er vel dette som er mest vanlig i dagligspråket når vi omtaler for eksempel trekanter og firkanter.
I klassen for tredimensjonale figurer er de to tilsvarende dimensjonene kalt lengde og bredde,
mens dimensjonen høyde nettopp angir høyden. Dette er også i samsvar med dagligspråket,
der vi for eksempel snakker om lengden, bredden og høyden til et hus. Når vi programmerer,
må vi passe på at konstruktørene for subklassene overfører de riktige verdiene til sine
superklasser når vi bruker referansen super
. Der spiller det ingen rolle hvilket navn
parametrene og datafeltene har. Det avgjørende er parameterrekkefølgen i konstruktøren.
De aktuelle parametrene til super
blir overført som parametre til superklassens
konstruktør i den rekkefølge de har som parametre til super
, uavhengig av hva de heter.
For en sirkel må vi for øvrig huske på at både bredden og høyden er det dobbelte av radien.
For en kule er både lengde, bredde og høyde det dobbelte av radien.
1 //Figurtyper.java 2 //Eksempel på klassehierarki 3 import java.text.DecimalFormat; 4 5 class Figur 6 { 7 private String navn; 8 DecimalFormat formatterer = new DecimalFormat("0.00"); 9 //skal brukes i subklassene 10 11 public Figur(String n) 12 { 13 navn = n; 14 } 15 16 public String getNavn() 17 { 18 return navn; 19 } 20 } 21 22 class TodimensjonalFigur extends Figur 23 { 24 private double bredde, høyde; 25 26 public TodimensjonalFigur(String n, double b, double h) 27 { 28 super(n); 29 bredde = b; 30 høyde = h; 31 } 32 33 public double getBredde() 34 { 35 return bredde; 36 } 37 38 public double getHøyde() 39 { 40 return høyde; 41 } 42 43 public String toString() 44 { 45 String beskrivelse = getNavn(); 46 beskrivelse += "\nBredde: " + bredde; 47 beskrivelse += "\nHøyde: " + høyde; 48 return beskrivelse; 49 } 50 } 51 52 class TredimensjonalFigur extends Figur 53 { 54 private double lengde, bredde, høyde; 55 56 public TredimensjonalFigur(String n, double l, double b, double h) 57 { 58 super(n); 59 lengde = l; 60 bredde = b; 61 høyde = h; 62 } 63 64 public double getLengde() 65 { 66 return lengde; 67 } 68 69 public double getBredde() 70 { 71 return bredde; 72 } 73 74 public double getHøyde() 75 { 76 return høyde; 77 } 78 79 public String toString() 80 { 81 String beskrivelse = getNavn(); 82 beskrivelse += "\nLengde: " + lengde; 83 beskrivelse += "\nBredde: " + bredde; 84 beskrivelse += "\nHøyde: " + høyde; 85 return beskrivelse; 86 } 87 } 88 89 class Rektangel extends TodimensjonalFigur 90 { 91 public Rektangel(double bredde, double høyde) 92 { 93 super("Rektangel", bredde, høyde); 94 } 95 96 public double getAreal() 97 { 98 return getBredde() * getHøyde(); 99 } 100 101 public String toString() 102 { 103 String beskrivelse = super.toString(); 104 double areal = getAreal(); 105 beskrivelse += "\nAreal: " + formatterer.format(areal); 106 return beskrivelse; 107 } 108 } 109 110 class Kvadrat extends TodimensjonalFigur 111 { 112 public Kvadrat(double sidekant) 113 { 114 super("Kvadrat", sidekant, sidekant); 115 } 116 117 public double getAreal() 118 { 119 return getBredde() * getHøyde(); 120 } 121 122 public String toString() 123 { 124 String beskrivelse = getNavn(); 125 beskrivelse += "\nSidekant: " + getBredde(); 126 double areal = getAreal(); 127 beskrivelse += "\nAreal: " + formatterer.format(areal); 128 return beskrivelse; 129 } 130 } 131 132 class Sirkel extends TodimensjonalFigur 133 { 134 public Sirkel(double radius) 135 { 136 super("Sirkel", 2*radius, 2*radius); 137 } 138 139 public double getAreal() 140 { 141 double radius = getBredde()/2; 142 return Math.PI * radius * radius; 143 } 144 145 public String toString() 146 { 147 String beskrivelse = getNavn(); 148 double radius = getBredde()/2; 149 beskrivelse += "\nRadius: " + formatterer.format(radius); 150 double areal = getAreal(); 151 beskrivelse += "\nAreal: " + formatterer.format(areal); 152 return beskrivelse; 153 } 154 } 155 156 class Terning extends TredimensjonalFigur 157 { 158 public Terning(double sidekant) 159 { 160 super("Terning", sidekant, sidekant, sidekant); 161 } 162 163 public double getVolum() 164 { 165 double side = getBredde(); 166 return side * side * side; 167 } 168 169 public double getOverflate() 170 { 171 double sidekant = getBredde(); 172 return 6 * sidekant * sidekant; 173 } 174 175 public String toString() 176 { 177 String beskrivelse = getNavn(); 178 beskrivelse += "\nSidekant: " + getBredde(); 179 double areal = getOverflate(); 180 beskrivelse += "\nOverflate: " + formatterer.format(areal); 181 double volum = getVolum(); 182 beskrivelse += "\nVolum: " + formatterer.format(volum); 183 return beskrivelse; 184 } 185 } 186 187 class Kule extends TredimensjonalFigur 188 { 189 public Kule(double radius) 190 { 191 super("Kule", 2*radius, 2*radius, 2*radius); 192 } 193 194 public double getOverflate() 195 { 196 double radius = getBredde()/2; 197 return 4 * Math.PI * radius * radius; 198 } 199 200 public double getVolum() 201 { 202 double radius = getBredde()/2; 203 return 4 * Math.PI * radius * radius * radius / 3; 204 } 205 206 public String toString() 207 { 208 String beskrivelse = getNavn(); 209 double radius = getBredde()/2; 210 beskrivelse += "\nRadius: " + formatterer.format(radius); 211 double areal = getOverflate(); 212 beskrivelse += "\nOverflate: " + formatterer.format(areal); 213 double volum = getVolum(); 214 beskrivelse += "\nVolum: " + formatterer.format(volum); 215 return beskrivelse; 216 } 217 }
For å kunne prøve ut figurklassene, er det definert en vindusklasse
Figurvindu
som er gjengitt nedenfor.
Den inneholder en array som kan inneholde Figur
-objekter og som dermed kan inneholde
alle de forskjellige figurtypene som er definert. (Nærmere omtale og forklaring av virkemåte for arrayer som inneholder
objekter tilhørende samme klassehierarki finner du i neste kapittel.)
Vinduet inneholder funksjonalitet som gjør det
mulig for brukeren å opprette objekter av de forskjellige figurtypene, legge dem inn i arrayen,
samt få skrevet ut en oversikt over hvilke figurer som er lagt inn. Arrayens kapasitet er begrenset
til 10 figurer, men dette er det lett å endre på.
1 import javax.swing.*; 2 import java.awt.*; 3 import java.awt.event.*; 4 5 public class Figurvindu extends JFrame implements ActionListener 6 { 7 public static final int MAXANTALL = 10; 8 public static final int SIRKEL = 1, KVADRAT = 2, REKTANGEL = 3, 9 KULE = 4, TERNING = 5; 10 private Figur[] figurliste = new Figur[MAXANTALL]; 11 private int antFigurer = 0; 12 private JTextField typefelt, breddefelt, høydefelt; 13 private JButton ny, oversikt; 14 private JTextArea utskriftsområde; 15 16 public Figurvindu() 17 { 18 super("Registrering av geometriske figurer"); 19 typefelt = new JTextField(2); 20 breddefelt = new JTextField(5); 21 høydefelt = new JTextField(5); 22 ny = new JButton("Registrer figur"); 23 ny.addActionListener(this); 24 oversikt = new JButton("Skriv figuroversikt"); 25 oversikt.addActionListener(this); 26 utskriftsområde = new JTextArea(25, 40); 27 utskriftsområde.setEditable(false); 28 Container c = getContentPane(); 29 c.setLayout(new FlowLayout()); 30 c.add(new JLabel("Velg først figurtype, deretter dimensjon(er), " + 31 "og klikk Registrer figur.")); 32 String figurtyper = SIRKEL + ": sirkel, " + KVADRAT + 33 ": kvadrat, " + REKTANGEL + ": rektangel, " + 34 KULE + ": kule, " + TERNING + ": terning"; 35 c.add(new JLabel("Figurtype (" + figurtyper + ")")); 36 c.add(typefelt); 37 c.add(new JLabel("Bredde/sidekant/radius:")); 38 c.add(breddefelt); 39 c.add(new JLabel("Høyde")); 40 c.add(høydefelt); 41 c.add(ny); 42 c.add(oversikt); 43 c.add(new JScrollPane(utskriftsområde)); 44 } 45 46 public void registrerFigur() 47 { 48 if (antFigurer < MAXANTALL) 49 { 50 Figur nyFigur; 51 int figurtype = Integer.parseInt(typefelt.getText()); 52 double dimensjon1 = Double.parseDouble(breddefelt.getText()); 53 if (dimensjon1 < 0.1) 54 dimensjon1 = 0.0; 55 56 switch(figurtype) 57 { 58 case REKTANGEL: double dimensjon2 = 59 Double.parseDouble(høydefelt.getText()); 60 if (dimensjon2 < 0.1) 61 dimensjon2 = 0.0; 62 nyFigur = new Rektangel(dimensjon1, 63 dimensjon2); 64 break; 65 case SIRKEL: nyFigur = new Sirkel(dimensjon1); 66 break; 67 case KVADRAT: nyFigur = new Kvadrat(dimensjon1); 68 break; 69 case KULE: nyFigur = new Kule(dimensjon1); 70 break; 71 case TERNING: nyFigur = new Terning(dimensjon1); 72 break; 73 default: nyFigur = null; 74 } 75 figurliste[antFigurer++] = nyFigur; 76 } 77 else 78 JOptionPane.showMessageDialog(this, 79 "Kan ikke registrere flere figurer!", 80 "Registeret er fullt!", JOptionPane.WARNING_MESSAGE); 81 } 82 83 public void skrivOversikt() 84 { 85 utskriftsområde.setText("REGISTRERTE FIGURER\n"); 86 for (int i = 0; i < antFigurer; i++) 87 utskriftsområde.append(figurliste[i].toString() + "\n\n"); 88 } 89 90 public void actionPerformed(ActionEvent e) 91 { 92 if (e.getSource() == ny) 93 registrerFigur(); 94 else if (e.getSource() == oversikt) 95 skrivOversikt(); 96 } 97 }
Klassen Figurregistrering
er driverklasse for programmet. Nedenfor er det gjengitt et bilde fra en kjøring av programmet.
1 import javax.swing.JFrame; 2 3 public class Figurregistrering 4 { 5 public static void main(String[] args) 6 { 7 Figurvindu vindu = new Figurvindu(); 8 vindu.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 9 vindu.setSize(500, 560); 10 vindu.setVisible(true); 11 } 12 }
Copyright © Kjetil Grønning og Eva Hadler Vihovde, revidert 2014