Indhold:
Definere egne klasser (typer af objekter)
Definere konstruktører, felter og metoder
Definition af klasserne Boks, Terning, Raflebæger, Person og Konto
Nøgleordet this
Kapitlet forudsættes i resten af bogen.
Forudsætter kapitel Fejl: Henvisningskilde ikke fundet, Fejl: Henvisningskilde ikke fundet.
Er man i gang med et større program, vil man have brug for at definere sine egne specialiserede klasser. Et regnskabsprogram kunne f.eks. definere en Konto-klasse. Ud fra Konto-klassen ville der blive skabt et antal konto-objekter, svarende til de konti, der skulle administreres.
I dette kapitel ser vi på, hvordan man selv definerer sine egne klasser. Vi minder om, at
En klasse er en skabelon, som man kan danne objekter ud fra
Klassen beskriver variabler og metoder, der kendetegner objekterne
Et objekt er en konkret forekomst (instans) af klassen
Når man programmerer objektorienteret, samler man data i selvstændige objekter og definerer metoder, som arbejder på disse data i objekterne.
Lad os tage et eksempel på en klassedefinition.
Vi definerer klassen Boks, som indeholder tre variabler, nemlig længde, bredde og højde.
Derudover definerer vi metoden volumen(), som arbejder på disse data. Metoden returnerer en double og har ingen parametre.
public class Boks
{
double længde;
double bredde;
double højde;
double volumen()
{
double vol;
vol = længde*bredde*højde;
return vol;
}
}
Variablerne længde, bredde og højde kaldes også felter eller objektvariabler, fordi hvert Boks-objekt har én af hver.
Felter erklæres direkte i klassen uden for metoderne1
Vi kan lave et Boks-objekt, boksobjekt med new:
Boks boksobjekt;
boksobjekt = new Boks();
Nu er der oprettet et Boks-objekt i lageret, der således har en længde-, en bredde- og en højde-variabel (hver med værdien 0).
Variablen vol kaldes en lokal variabel, fordi den er erklæret lokalt i volumen()-metoden.
En variabel erklæret inde i en metode kaldes en lokal variabel
Lokale variabler eksisterer kun, så længe metoden, hvori de er erklæret, udføres
Modsat længde, bredde og højde begynder vol-variabler altså ikke at eksistere, bare fordi vi har skabt en Boks2.
Objekter af klassen Boks kan f.eks. benyttes på følgende måde:
public class BenytBoks
{
public static void main(String[] arg)
{
double rumfang;
Boks boksobjekt;
boksobjekt = new Boks();
boksobjekt.længde = 2.0;
boksobjekt.bredde = 2.5;
boksobjekt.højde = 1.5;
rumfang = boksobjekt.volumen();
System.out.println("Boksens volumen: "+ rumfang);
}
}
Boksens volumen: 7.5
Som det ses, er det klassen BenytBoks, der indeholder main()-metoden. Der skal være én og kun én klasse med en main()-metode i et program. En sådan "main-klasse" bruges ikke til at definere objekttyper med – kun til at angive, hvor programmet skal startes3.
I følgende sætninger (i klassen BenytBoks) sættes det nyoprettede Boks-objekts variabler:
boksobjekt.længde = 2.0;
boksobjekt.bredde = 2.5;
boksobjekt.højde = 1.5;
I den efterfølgende sætning:
rumfang = boksobjekt.volumen();
kaldes metoden volumen() i Boks-objektet, der udregner rumfanget ud fra variablerne, som er blevet tilført data i linjerne ovenfor. Metoden returnerer en double – denne lægges over i variablen rumfang, som udskrives.
Når vi definerer en metode, giver vi den et hoved og en krop.
Hovedet ligner den måde, vi tidligere har set metoder opremset på. Metodehovedet fortæller metodens navn, returtype og hvilke parametre metoden eventuelt har:
double volumen()
Kroppen kommer lige under hovedet:
{
double vol;
vol = længde*bredde*højde;
return vol;
}
I kroppen står der, hvad der skal ske, når metoden kaldes. Her står altså, at når metoden volumen() kaldes, bliver der først oprettet en lokal variabel, vol. Denne bliver tildelt produktet af de tre variabler længde, bredde og højde. Den sidste linje i kroppen fortæller, at værdien af vol bliver givet tilbage (returneret) til der, hvorfra metoden blev kaldt.
En metodekrop udføres, når metoden kaldes
Variablerne længde, bredde og højde, som kroppen bruger, er dem, der findes i netop det objekt, som volumen()-metoden blev kaldt på.
Lad os kigge på en stump af BenytBoks:
boksobjekt.længde = 2.0;
boksobjekt.bredde = 2.5;
boksobjekt.højde = 1.5;
rumfang = boksobjekt.volumen();
Her får det Boks-objekt, som boksobjekt refererer til, sat sine variabler og når volumen() derefter kaldes på objektet, vil længde, bredde og højde have disse værdier. rumfang bliver sat til den værdi, vol har i return-linjen og vol nedlægges (da den er en lokal variabel).
En return-sætning afslutter udførelsen af metodekroppen og leverer en værdi tilbage til kalderen
Herunder opretter vi to bokse og udregner deres forskel i rumfang. Hver boks er et aftryk af Boks-klassen forstået på den måde, at de hver indeholder deres egne sæt variabler. Variablen bredde kan således have forskellige værdier i hvert objekt.
public class BenytBokse
{
public static void main(String[] arg)
{
Boks enBoks, enAndenBoks;
enBoks = new Boks();
enAndenBoks = new Boks();
enBoks.længde = 1.5;
enBoks.bredde = 1.0;
enBoks.højde = 1.0;
enAndenBoks.længde = 0.5;
enAndenBoks.bredde = 0.5;
enAndenBoks.højde = 0.5;
double v1, v2;
v1 = enBoks.volumen();
v2 = enAndenBoks.volumen();
System.out.println("Samlet rumfang: "+ (v2 + v1));
}
}
Samlet rumfang: 1.625
Når vi kalder volumen() på enBoks og enAndenBoks, er det således to forskellige sæt længde-, højde- og bredde-variabler, der bliver brugt til beregningen, når volumen()'s krop udføres.
Indkapsling (eng.: encapsulation) af data og metoder i objekter betyder, at man ikke lader andre bruge objekterne helt efter eget forgodtbefindende. Man gør visse dele af objekterne utilgængelige uden for klassens metoder. Herved sætter man nogle regler op for, hvordan man kan benytte objekterne.
Hvorfor overhovedet indkapsle (skjule) variabler?
Indkapsling i klasser er vigtig, når programmerne bliver store og komplekse. Hvis det er muligt at ændre data i en klasse, kan det føre til situationer, som kommer ud af kontrol i store komplekse systemer. Når data er indkapslet kan de ikke ændres direkte udefra, og man må i stedet definere metoder til at ændre i data på. I metoderne kan man sikre sig mod vanvittige overgreb på data ved at tilføre logik, der sikrer, at variablerne er konsistente.
I ovenstående eksempel kan man for eksempel sætte højden af en boks til et negativt tal. Spørger man derefter på volumen(), vil man få et negativt svar! Det kræver ikke meget fantasi at forestille sig, hvordan sådanne fejl kunne gøre et program ubrugeligt. Tænk for eksempel på pakkepost-omdeling, hvis et af Post Danmarks programmer påstod, at der nemt kunne være 10000 pakker på hver minus en kubikmeter og 10001 pakker på hver plus en kubikmeter i én postvogn... endda med flere kubikmeter til overs til anden post!
Med indkapsling opnår man, at objekterne altid er konsistente, fordi objekterne selv sørger for, at deres variabler har fornuftige værdier.
Man styrer adgangen til en variabel eller metode med nøgleordene public og private4:
public betyder "adgang for alle"
private betyder "kun adgang fra samme klasse"
Herunder ses en modificeret version af eksemplet med Boks- og BenytBoks-klassen, men nu er variablerne erklæret private.
public class Boks2
{
private double længde;
private double bredde;
private double højde;
public void sætMål(double lgd, double b, double h)
{
if (lgd>0 && b>0 && h>0)
{
længde = lgd;
bredde = b;
højde = h;
} else {
System.out.println("Ugyldige mål. Bruger standardmål.");
længde = 10.0;
bredde = 10.0;
højde = 10.0;
}
}
public double volumen()
{
double vol;
vol = længde*bredde*højde;
return vol;
}
}
Figuren ovenfor illustrerer klassen i UML-notationen. Bemærk, at variablerne er private, så de har et - foran, mens metoderne, som kan ses udefra (public), har et + foran.
Nu da variablerne længde, bredde og højde er erklæret private, er det ulovligt at ændre dem "udefra" i vores BenytBoks-program.
Til gengæld har vi defineret metoden sætMål(), som man skal kalde for at sætte variablerne.
Da den eneste måde at ændre data på er ved at kalde metoden sætMål(), kan vi der indlægge ønsket logik – for eksempel sikre os mod 0 (nul) eller negative værdier5.
public class BenytBoks2
{
public static void main(String[] arg)
{
Boks2 enBoks = new Boks2();
//ulovligt: enBoks.længde = 1.5;
//ulovligt: enBoks.bredde = 1.0;
//ulovligt: enBoks.højde = 1.0;
enBoks.sætMål( 1.5, 1.0, 1.0);
System.out.println("Volumen er: "+ enBoks.volumen());
Boks2 enAndenBoks = new Boks2();
enAndenBoks.sætMål( 0.0,-0.5, 0.5);
System.out.println("Volumen er: "+ enAndenBoks.volumen());
enAndenBoks.sætMål( 0.5, 0.5, 0.5);
System.out.println("Volumen er: "+ enAndenBoks.volumen());
System.out.println("Samlet: " +(enBoks.volumen() + enAndenBoks.volumen()));
}
}
Volumen er: 1.5
Ugyldige mål. Bruger standardmål.
Volumen er: 1000.0
Volumen er: 0.125
Samlet: 1.625
En anden fordel ved indkapsling er, at man bliver uafhængig af, hvordan data er repræsenteret internt. Man kunne f.eks. senere ændre Boks-klassen, så den kun lagrede volumen (beregnet allerede i sætMål()) i stedet for længde, bredde og højde.
Ret Boks2 til også at have variablen massefylde og definér metoder til at sætte massefylden, sætMassefylde(double m), og udregne vægten, vægt(). Afprøv, om det virker (test din klasse med en ændret udgave af BenytBoks2).
Definér klassen Pyramide. Objekterne
skal have variablerne side
og højde
og en metode til at udregne volumen (side*side*højde/4).
Skriv en BenytPyramider, som opretter 3 pyramider og udregner
volumen af dem.
En konstruktør (eng.: constructor) er en speciel metode, der har samme navn som klassen. Den kaldes automatisk ved oprettelse af et objekt med 'new'-operatoren og benyttes oftest til at klare forskellige former for initialisering af det nye objekt.
Som vi så i forrige kapitel (i tilfældet med Rectangle, Point og Date), kan man have flere konstruktører for en klasse, bare parameterlisterne er forskellige.
Her kommer et eksempel6 med nogle konstruktører:
/** En boks med en længde, bredde og højde */
public class Boks3
{
private double længde;
private double bredde;
private double højde;
/** konstruktør der opretter en standardboks */
public Boks3()
{
System.out.println("Standardboks oprettes");
længde = 10.0;
bredde = 10.0;
højde = 10.0;
}
/** en anden konstruktør der får bredde, højde og længde */
public Boks3(double lgd, double b, double h)
{
if (lgd>0 && b>0 && h>0)
{
System.out.println("Boks oprettes med lgd="+lgd+" b="+b+" h="+h);
længde = lgd;
bredde = b;
højde = h;
} else {
System.out.println("Ugyldige mål. Bruger standardmål.");
længde = 10.0;
bredde = 10.0;
højde = 10.0;
}
}
/** udregner boksens rumfang */
public double volumen()
{
double vol = længde*bredde*højde;
return vol;
}
}
Bemærk:
En konstruktør erklæres som en metode med samme navn som klassen
En konstruktør har ingen returtype – ikke engang 'void'
I ovenstående eksempel er der defineret to konstruktører:
public Boks3()
public Boks3(double lgd, double b, double h)
Vi kan afprøve Boks3 med:
public class BenytBoks3
{
public static void main(String[] arg)
{
Boks3 enBoks = new Boks3(); // brug konstruktøren uden parametre
System.out.println("Volumen er: "+ enBoks.volumen());
Boks3 enAndenBoks = new Boks3(5, 5, 10); // brug den anden konstruktør
System.out.println("Volumen er: "+ enAndenBoks.volumen());
}
}
Standardboks oprettes
Volumen er: 1000.0
Boks oprettes med lgd=5.0 b=5.0 h=10.0
Volumen er: 250.0
Når vi i de foregående eksempler (f.eks. Boks2) ikke har benyttet en konstruktør, er det fordi Java, hvis der ikke er erklæret en konstruktør, selv erklærer en tom standardkonstruktør (eng.: default constructor) uden parametre.
Det vil sige at Java, i Boks2's tilfælde, usynligt har defineret konstruktøren:
public Boks2()
{
}
Denne konstruktør har vi kaldt, hver gang vi har oprettet en boks med 'new Boks2()'.
Der kaldes altid en konstruktør, når et objekt oprettes
Standardkonstruktøren genereres automatisk, hvis der ikke er andre konstruktører i klassen
En standardkonstruktør genereres kun, hvis der ikke er andre konstruktører i klassen.
Hvis vi ikke havde defineret en konstruktør
uden parametre i Boks3, ville compileren brokke sig i BenytBoks3
over, at konstruktøren til new Boks3()
ikke fandtes:
BenytBoks3.java:6: No constructor matching Boks3() found in class Boks3.
enBoks = new Boks3();
Ret Boks3 til også at have variablen massefylde. Definér en ekstra konstruktør, der også får massefylden overført (den oprindelige konstruktør med lgd, b og h kan sætte massefylde til 1).
Definér klassen Pyramide med en
konstruktør hvor man kan sætte side
og højde.
Brug konstruktøren til at oprette 3 pyramider og udregne
volumen (side*side*højde/4).
Lad os tage et andet eksempel, en terning. Den vigtigste egenskab ved en terning er dens værdi (dvs. antallet af øjne på siden, der vender opad lige nu) mellem 1 og 6.
/** En klasse der beskriver 6-sidede terninger */
public class Terning
{
/** antallet af øjne på den side på terningen, der vender opad lige nu */
private int værdi;
/** konstruktør der opretter en terning */
public Terning()
{
// vælg en tilfældig side til at starte med
værdi = (int) (Math.random() * 6 + 1);
}
/** kaster terningen, så den får en anden værdi */
public void kast()
{
// vælg en tilfældig side
double tilfældigtTal = Math.random();
værdi = (int) (tilfældigtTal * 6 + 1);
}
/** Aflæser terningens værdi */
public int getVærdi()
{
return værdi;
}
/** Sætter terningens værdi */
public void setVærdi(int nyVærdi)
{
værdi = nyVærdi;
}
/** giver en beskrivelse af terningen som en streng */
public String toString()
{
String svar = ""+værdi; // værdi som streng, f.eks. "4"
return svar;
}
}
Vi har ladet værdien være privat og lavet metoden getVærdi(), som kan bruges udefra. Der er også en setVærdi()-metode, som kan bruges hvis man får brug for at sætte værdien.
Her er et program, der bruger et Terning-objekt til at slå med, indtil vi får en 6'er:
public class BenytTerning
{
public static void main(String[] arg)
{
Terning t;
t = new Terning(); // opret terning
// Slå nu med terningen indtil vi får en sekser
boolean sekser = false;
int antalKast = 0;
while (sekser==false)
{
t.kast();
antalKast = antalKast + 1;
System.out.println("kast "+antalKast+": "+t.getVærdi());
if (t.getVærdi() == 6) sekser = true;
}
System.out.println("Vi slog en 6'er efter "+antalKast+" slag.");
}
}
kast 1: 4
kast 2: 2
kast 3: 6
Vi slog en 6'er efter 3 slag.
Skriv et program, der rafler med to terning-objekter, indtil der slås en 6'er.
Skriv et program, der rafler med fire terninger, indtil der slås tre eller fire 6'ere. Udskriv antal øjne for hver terning.
Skriv et program, der rafler med 12 terninger og hver gang udskriver øjnene, summen af øjnene og hvor mange 6'ere der kom. Brug ArrayList-klassen til at holde styr på terningerne.
Lav en Moent-klasse, der repræsenterer en mønt med 2 sider (du kan tage udgangspunkt i Terning.java). Lav metoden krone(), der returnerer true eller false. Lav et program, der kaster en mønt 100 gange og tæller antal gange, det fik krone.
Indtil nu har alle vore objekter haft simple typer som variabler. Nu vil vi se på objekter, der har andre objekter som variabler (dvs. de har referencer til andre objekter).
Når man laver et større program, bliver det ofte nødvendigt at uddelegere nogle af opgaverne fra hovedprogrammet til andre dele af programmet. I vores tilfælde kunne vi godt lave et lille terningspil direkte fra main(), men hvis vi skulle lave f.eks. et yatzy- eller matadorspil, ville det blive besværligt, at skulle holde rede på hver enkelt terning (og alle de andre objekter) på den måde. Hver gang en spiller kaster med terningerne, skal man først kaste hver enkelt terning, hvorefter man skal udregne summen (eller i Yatzy undersøge antallet af par, tre ens osv.).
En løsning er at skabe andre, mere overordnede objekter, som tager sig af detaljerne. I vores tilfælde kan man definere en Raflebæger-klasse, som er bekvem at bruge fra hovedprogrammet og som har terningerne og holder styr på dem.
Her er UML-klassediagrammet:
Et raflebæger indeholder terninger.
Pilen symboliserer har-relation: Et Raflebaeger har 0 til * (flere) terninger.
Pilen fra raflebæger-klassen til Terning-klassen repræsenterer en har-relation. Med 'har' menes, at referencerne til Terning-objekterne kendes af raflebæger-objektet (her via en ArrayList). Terning-objekterne kendes ikke nødvendigvis af hovedprogrammet.
Raflebægeret har metoderne ryst(), der kaster alle terningerne, sum(), der udregner summen af terningernes værdier, og antalDerViser(), der fortæller, hvor mange terninger. der har en given værdi (f.eks. hvor mange terninger, der viser 6 øjne).
import java.util.ArrayList;
public class Raflebaeger
{
/** listen af terninger, der er i raflebægeret */
public ArrayList<Terning> terninger;
public Raflebaeger(int antalTerninger)
{
terninger = new ArrayList<Terning>();
for (int i=0; i<antalTerninger; i++)
{
Terning t;
t = new Terning();
terninger.add(t);
}
}
/** lægger en terning i bægeret */
public void tilføjTerning(Terning t)
{
terninger.add(t);
}
/** ryster bægeret, så alle terningerne bliver 'kastet' og får en ny værdi */
public void ryst()
{
for (Terning t : terninger)
{
t.kast();
}
}
/** finder summen af alle terningernes værdier */
public int sum()
{
int resultat;
resultat = 0;
for (Terning t : terninger)
{
int terningensVærdi = t.getVærdi();
resultat = resultat + terningensVærdi;
}
return resultat;
}
/** finder antallet af terninger, der viser en bestemt værdi */
public int antalDerViser(int værdi)
{
int resultat=0;
for (Terning t : terninger)
{
int terningensVærdi = t.getVærdi();
if (terningensVærdi==værdi)
{
resultat = resultat + 1;
}
}
return resultat;
}
/** beskriver bægerets indhold som en streng */
public String toString()
{// (listens toString() kalder toString() på hver terning)
return terninger.toString();
}
}
Herunder er et lille program, der spiller med tre terninger, indtil man får netop to seksere:
public class BenytRaflebaeger
{
public static void main(String[] arg)
{
Raflebaeger bæger;
boolean toSeksere;
int antalForsøg;
bæger = new Raflebaeger(3); // opret et bæger med 3 terninger
toSeksere=false;
antalForsøg = 0;
while (toSeksere==false)
{
bæger.ryst(); // kast alle terningerne
System.out.print("Bæger: " + bæger + " sum: " + bæger.sum());
System.out.println(" Antal 6'ere: "+bæger.antalDerViser(6)
+ " antal 5'ere: "+bæger.antalDerViser(5));
if (bæger.antalDerViser(6) == 2)
{
toSeksere = true;
}
antalForsøg++;
}
System.out.println("Du fik to seksere efter "+ antalForsøg+" forsøg.");
}
}
Bæger: [4, 4, 4] sum: 12 Antal 6'ere: 0 antal 5'ere: 0
Bæger: [5, 5, 6] sum: 16 Antal 6'ere: 1 antal 5'ere: 2
Bæger: [2, 5, 6] sum: 13 Antal 6'ere: 1 antal 5'ere: 1
Bæger: [4, 2, 4] sum: 10 Antal 6'ere: 0 antal 5'ere: 0
Bæger: [6, 4, 1] sum: 11 Antal 6'ere: 1 antal 5'ere: 0
Bæger: [6, 6, 4] sum: 16 Antal 6'ere: 2 antal 5'ere: 0
Du fik to seksere efter 6 forsøg.
Linjen:
bæger = new Raflebaeger(3);
opretter et raflebæger med tre terninger i.
Skriv et program, der vha. et Raflebaeger rafler med fire terninger, indtil der slås tre eller fire 6'ere. Udskriv antal øjne for hver terning.
Skriv et program, der vha. et Raflebaeger rafler med 12 terninger og udskriver terningernes værdier, summen af værdierne og hvor mange 6'ere der kom.
Skriv et simpelt Yatzy-spil med fem terninger. Man kaster én gang og ser, om man har et par, to par, tre ens, hus (et par og tre ens, f.eks. 25225), fire ens eller fem ens.
Udvid Raflebaeger, så man kan spørge, om der er fire ens, med en fireEns()-metode:
public boolean fireEns()
{
...
}
Lav tilsvarende de andre metoder (toEns(), treEns(), toPar(),
hus()...).
(vink: Gør flittigt brug af
antalDerViser()-metoden)
Ret toString()-metoden, så den fortæller, om der var fem ens, hus eller lignende.
Lav et program (en klasse med en main()-metode), der rafler et Raflebaeger et par gange og skriver dets indhold ud. Her er et eksempel på, hvordan uddata kunne se ud:
1 4 4 3 4 : Tre ens
4 2 1 6 6 : Et par
2 6 2 2 6 : Hus
5 2 3 6 4 : Ingenting
2 3 4 5 4 : Et par
6 5 2 6 2 : To par
6 6 2 2 6 : Hus
Nogle gange kan et objekt have brug for at referere til sig selv. Det gøres med nøgleordet this, der ligner (og bruges som) en variabel7.
this refererer til det objekt, man er i
this
virker som en variabel inde i objektet, der peger på
objektet selv (b er en variabel, der peger på objektet udefra)
import java.util.ArrayList;
public class Boks2medThis
{
private double længde;
private double bredde;
private double højde;
public void sætMål(double længde, double bredde, double højde)
{
if (længde>0 && bredde>0 && højde>0)
{
this.længde = længde;
this.bredde = bredde;
this.højde = højde;
} else {
System.out.println("Ugyldige mål.");
this.længde = 10.0;
this.bredde = 10.0;
this.højde = 10.0;
}
}
public double volumen()
{
return bredde*højde*længde;
}
public void føjTilListe(ArrayList l)
{
l.add(this);
}
}
I sætMål() er der nu to sæt variabler med samme navn.
Java vælger da altid den variabel, der er "tættest på", dvs. f.eks. 'længde' svarer til parametervariablen længde. For at få fat i feltet/objektvariablen skal vi bruge this.længde.
Derfor skal vi skrive:
this.længde = længde;
for at tildele objektets længde-variabel den nye værdi.
En anden anvendelse af this er, når et objekt har brug for at give en reference til sig selv til et andet objekt. Normalt ville vi tilføje en boks til en liste med:
ArrayList l = new ArrayList()
Boks2medThis b = new Boks2medThis();
l.add(b);
Med metoden føjTilListe() kan vi i stedet for bede b om at tilføje sig selv:
b.føjTilListe(l);
Vi vil senere (i Spiller-klassen i matadorspillet i kapitlet om nedarvning) se et eksempel på dette, hvor det er en fordel i praksis.
Dette afsnit giver nogle ekstra eksempler, der repeterer stoffet i kapitlet.
Det normale er en 6-sidet terning, men der findes også 4-, 8- 12- og 20-sidede. Klassen nedenfor beskriver en generel n-sidet terning.
/** En klasse der beskriver 4-, 8- 12- og 20-sidede terninger */
public class NSidetTerning
{
/** hvor mange sider har terningen (normalt 6) */
private int sider;
/** den side på terningen, der vender opad lige nu */
private int værdi;
/** konstruktør der opretter en standardterning med 6 sider */
public NSidetTerning()
{
sider = 6;
kast(); // sæt værdi til noget
}
/** konstruktør der opretter en terning med et vist antal sider */
public NSidetTerning(int antalSider)
{
if (antalSider >= 3) sider = antalSider;
else sider = 6;
kast();
}
/** kaster terningen, så den får en anden værdi */
public void kast()
{
// find en tilfældig side
double tilfældigtTal = Math.random();
værdi = (int) (tilfældigtTal * sider + 1);
}
/** giver antallet af øjne på den side på terningen, der vender opad */
public int getVærdi()
{
return værdi;
}
/** ændrer terningen til at vende en bestemt side opad */
public void setVærdi(int nyVærdi)
{
if (nyVærdi > 0 && nyVærdi <= sider) værdi = nyVærdi;
else System.out.println("Ugyldig værdi");
}
/** giver en beskrivelse af terningen som en streng.
Hvis den ikke har 6 sider udskrives også antal af sider */
public String toString()
{
String svar = ""+værdi; // værdi som streng, f.eks. "4"
if (sider!= 6) svar = svar+"("+sider+"s)";
return svar;
}
}
Antallet af sider og værdien er private. Antallet af sider kan ikke ændres udefra, når først terningen er skabt.
Her er et program til at afprøve klassen med:
public class BenytNSidetTerning
{
public static void main(String[] arg)
{
NSidetTerning t = new NSidetTerning(); // sekssidet terning
System.out.println("t viser nu "+t.getVærdi()+" øjne");
NSidetTerning t6 = new NSidetTerning(6); // sekssidet terning
NSidetTerning t4 = new NSidetTerning(4); // firesidet terning
NSidetTerning t12 = new NSidetTerning(12); // tolvsidet terning
System.out.println("t4 er "+t4); // t4.toString() kaldes implicit
t4.kast();
System.out.println("t4 er nu "+t4);
t4.kast();
System.out.println("terninger: "+t+" "+t6+" "+t4+" "+t12);
t.kast();
t12.kast();
System.out.println("terninger: "+t+" "+t6+" "+t4+" "+t12);
for (int i=0; i<5; i++)
{
t.kast();
t6.kast();
t4.kast();
t12.kast();
System.out.println("kast "+i+": "+t+" "+t6+" "+t4+" "+t12);
if (t.getVærdi() == t6.getVærdi())
{
System.out.println("t og t6 er ens!");
}
}
}
}
t viser nu 6 øjne
t4 er 4(4s)
t4 er nu 1(4s)
terninger: 6 1 4(4s) 5(12s)
terninger: 6 1 4(4s) 3(12s)
kast 0: 3 1 4(4s) 2(12s)
kast 1: 1 6 4(4s) 11(12s)
kast 2: 1 1 4(4s) 5(12s)
t og t6 er ens!
kast 3: 3 6 4(4s) 3(12s)
kast 4: 3 2 2(4s) 6(12s)
Lad os lave en klasse til at repræsentere en person. Hvert person-objekt skal have et fornavn, et efternavn og en alder. Når man opretter en ny Person, skal man angive disse data, f.eks.: new Person("Jacob","Nordfalk",30), så vi definerer en konstruktør med disse parametre.
Vi definerer også, at hver person har metoden toString(), der returnerer en streng med personens oplysninger af formen "Jacob Nordfalk (30 år)".
Desuden har vi metoden præsentation(), der skriver oplysningerne pænt ud til skærmen som "Jeg hedder Jacob og jeg er 30 år". Denne metode returnerer ikke noget, men skriver i stedet hilsenen ud til skærmen (personer under 5 år siger bare "agyyy!")
Til sidst kunne man forestille sig, at en person kan hilse på en anden person (metoden hils()). Det afhænger af alderen, hvordan man hilser. En person på over 60 år vil hilse på Jacob med "Goddag, hr. Nordfalk", mens en yngre bare vil sige "Hej Jacob".
import java.util.ArrayList;
public class Person
{
public String fornavn;
public String efternavn;
public int alder;
public ArrayList<Konto> konti; // bruges senere
public Person(String fornavnP, String efternavnP, int alderP)
{
fornavn = fornavnP;
efternavn = efternavnP;
alder = alderP;
konti = new ArrayList<Konto>(); // bruges senere
}
public String toString()
{
return fornavn+" "+efternavn+" ("+alder+" år)";
}
public void præsentation()
{
if (alder < 5) System.out.println("agyyy!");
else System.out.println("Jeg hedder "+fornavn+" og jeg er "+alder+" år.");
}
public void hils(Person andenPerson)
{
if (alder < 5) System.out.print("ma ma.. ");
else if (alder < 60) System.out.print("Hej "+andenPerson.fornavn+". ");
else System.out.print("Goddag, hr. "+andenPerson.efternavn+". ");
præsentation();
}
}
Bemærk, at Person-objektet har to andre objekter, nemlig to strenge. Selvom man ikke plejer at tegne strenge med i klassediagrammer, har vi alligevel taget dem med for at illustrere, at der faktisk også eksisterer en har-relation mellem disse to klasser.
En Person har to String-objekter. Disse er undtagelsesvist også vist.
Læg også mærke til, hvordan vi fra hils()-metoden kalder præsentation(). Lad os prøve at oprette tre personer og lade dem præsentere sig og derpå hilse på hinanden:
public class BenytPerson
{
public static void main(String[] arg)
{
Person j, k, l;
j = new Person("Jacob", "Nordfalk", 30);
k = new Person("Kai", "Lund", 86);
l = new Person("Lars", "Holm", 2);
System.out.println("Vi har oprettet "+j+", "+k+" og "+l);
j.præsentation();
k.præsentation();
l.præsentation();
j.hils(k);
k.hils(j);
l.hils(j);
}
}
Vi har oprettet Jacob Nordfalk (30 år), Kai Lund (86 år) og Lars Holm (2 år)
Jeg hedder Jacob og jeg er 30 år.
Jeg hedder Kai og jeg er 86 år.
agyyy!
Hej Kai. Jeg hedder Jacob og jeg er 30 år.
Goddag, hr. Nordfalk. Jeg hedder Kai og jeg er 86 år.
ma ma.. agyyy!
I linjen
x.hils(y);
er det x-variablens person (Jacob), der hilser på y-variablens person. Da x-variablens person er under 60, vil den uformelle hilsen "Hej Kai" blive brugt. I linjen under er det lige omvendt.
I kapitel Fejl: Henvisningskilde ikke fundet, Fejl: Henvisningskilde ikke fundet kan du læse mere om, hvordan man designer sit program, d.v.s. vælger hvilke klasser, der skal defineres, hvilke metoder de skal have og hvilke relationer, der skal være mellem objekterne.
Dette afsnit giver en alternativ introduktion til objekter og klasser, ved at se på hvordan man kunne modellere en simpel billetautomat.
DSB har fået en ny konkurrent: BlueJ Trafikselskab (BT) skal for fremtiden køre på de danske skinner. BT er ny på markedet og mangler en leverandør af deres IT-systemer. I har vundet udbuddet om at designe, programmere og bygge deres billetautomater.
Udrulningen foregår i flere faser: I første omgang ønsker BT en simpel billetautomat til enkeltbilletter med én fast pris, betalt kontant.
Bliver projektet en succes kan I forvente at BT ønsker at fortsætte samarbejdet og at automaten i senere faser skal udvides til at håndtere flere billettyper, betalingsformer og rabatsystemer.
Start med at skimme afsnit Fejl: Henvisningskilde ikke fundet Fejl: Henvisningskilde ikke fundet.
Her er en liste af kravene, som BT har til automaten i fase 1:
K1) Når en automat opstilles på en station skal installatøren kunne angive prisen per billet. Efter installation skal prisen være fast.
K2) Automaten skal kunne fortælle kunden hvad en billet koster.
K3) Automaten skal kunne udskrive en billet mærket med BT's logo.
K4) Automaten vil til enhver tid kunne vise kunden balancen, dvs hvor mange penge kunden har puttet i automaten.
K5) Ved opstilling skal installatøren kunne angive balancen, sådan at installatøren kan udskrive en eller flere test-billetter uden at skulle putte penge i automaten.
K6) Hver nat skal en medarbejder kunne aflæse hvor mange billetter der er blevet solgt af automaten og kunne tømme den for penge.
Opstil 4-5 yderligere krav som automaten bør opfylde i fase 1, samt et antal idéer til krav til fremtidige faser.
Kig på Fejl: Henvisningskilde ikke fundet Fejl: Henvisningskilde ikke fundet.
Brugsscenariet "Et køb af en kunde"
Primær aktør |
Brugeren eller systemet det handler om i denne situation |
Startsituation |
Hvad skal være opfyldt før brugsscenariet forekommer |
Slutsituation |
Resultatet af at brugsscenariet udføres |
Hovedscenarie |
Sekvensen af handlinger, der fører aktøren fra startsituationen frem til slutsituationen |
Afvigelser |
Afvigelser og undtagelser fra hovedscenariet |
Åbne spørgsmål |
Problemstillinger, der først tages stilling til på et senere tidspunkt |
Skriv brugsscenarierne ned for et køb af en kunde (K2 – K4) og for medarbejderens aflæsning (K6), ved at udfylde skabelonen ovenfor.
Første udkast til programmet består af klassen Billetautomat
De forskellige metoder svarer til brugerens eller installatørens handlinger.
For at afprøve klassen, har man lavet et program, der benytter Billetautomat og kalder metoderne, der er omfattet af kravene K1-K4:
public class BenytBilletautomat
{
public static void main(String[] arg)
{
Billetautomat automat = new Billetautomat(24);
System.out.println("Installatøren har installeret en ny billetautomat");
System.out.println();
System.out.println("En kunde vil købe en billet");
int pris = automat.getBilletpris();
System.out.println("Billetprisen er: " + pris + " kroner");
System.out.println("Kunden putter 24 kroner i automaten");
automat.indsætPenge(24);
System.out.println("Balancen er nu på " + automat.getBalance() + " kroner");
automat.udskrivBillet();
System.out.println("Balancen er nu på " + automat.getBalance() + " kroner");
}
}
Installatøren har installeret en ny billetautomat
En kunde vil købe en billet
Billetprisen er: 24 kroner
Kunden putter 24 kroner i automaten
Balancen er nu på 24 kroner
##########B##T#########
# BlueJ Trafikselskab #
# #
# Billet #
# 24 kr. #
# #
##########B##T#########
# Du har 24 kr til gode #
##########B##T#########
Balancen er nu på 0 kroner
Afprøv Billetautomaten grundigt i BlueJ og se om den i alle tilfælde gør noget hensigtsmæssigt.
Undersøg om kunden har mulighed for at misbruge automaten!
Lav derefter en liste med mindst 5 fejl og mangler ved automaten.
/**
* Model af en simpel billetautomat til enkeltbilletter med én fast pris.
*/
public class Billetautomat {
private int pris; // Prisen for én billet.
private int balance; // Hvor mange penge kunden p.t. har puttet i automaten
private int total; // Total mængde penge som billetautomaten har modtaget
private int antalBilletterSolgt; // Antal billetter automaten i alt har solgt
/**
* Opret en billetautomat der sælger billetter til en given billetpris.
* @param billetpris skal være større end nul (p.t. bliver det ikke tjekket)
*/
public Billetautomat(int billetpris) {
pris = billetpris;
balance = 0;
total = 0;
antalBilletterSolgt = 0;
}
/**
* Opret en billetautomat der sælger billetter til en given billetpris
* @param billetpris skal være større end nul (p.t. bliver det ikke tjekket)
* @param startbalance mængden af penge automaten allerede indeholder
*/
public Billetautomat(int billetpris, int startbalance) {
int pris = billetpris;
balance = startbalance;
total = 0;
antalBilletterSolgt = 0;
}
/**
* Giver prisen for en billet.
*/
public int getBilletpris() {
int resultat = pris;
return resultat;
}
/**
* Modtag nogle penge (i kroner) fra en kunde.
*/
public void indsætPenge(int beløb) {
balance = balance + beløb;
total = total + beløb;
}
/**
* Giver balancen (beløbet maskinen har modtaget til den næste billet).
*/
public int getBalance() {
return balance;
}
/**
* Udskriv en billet.
* Opdater total og nedskriv balancen med billetprisen
*/
public void udskrivBillet() {
System.out.println("##########B##T#########");
System.out.println("# BlueJ Trafikselskab #");
System.out.println("# #");
System.out.println("# Billet #");
System.out.println("# " + pris + " kr. #");
System.out.println("# #");
System.out.println("##########B##T#########");
System.out.println("# Du har " + balance + " kr til gode #");
System.out.println("##########B##T#########");
System.out.println();
antalBilletterSolgt = antalBilletterSolgt + 1;
total = total + balance; // Opdater total med balance
balance = 0; // Nulstil balance
}
}
Husk at lave små main()-programmer, der afprøver de ting, du programmerer.
Lav en klasse, der repræsenterer en
bil. En bil har en farve, et antal kørte kilometer og en
nypris. Definér metoderne:
public
void kør(int antalKilometer) //
opdaterer antal kørte kilometre
public
double pris() //
giver den vurderede salgspris
public
String toString() //
giver en beskrivelse af bilen
Udbyg Bil-klassen med en liste, der husker,
hvilke personer, der sidder i bilen lige nu. Definér følgende
metoder på Bil-klassen og afprøv klassen.
public
void enSætterSigInd(Person p)//
kaldes når person sætter sig ind i bilen
public
String hvemSidderIBilen() //
giver en beskrivelse af personerne i bilen
public
void alleStigerUd() //
kaldes, når alle stiger ud af bilen
Lav et program, der holder styr på en musiksamling. Opret en klasse, der repræsenterer en udgivelse (int år, String navn, String gruppe, String pladeselskab). Programmet skal huske listen over udgivelser og kunne udskrive den.
Udviklingsværktøjet BlueJ, der er beskrevet i afsnit Fejl: Henvisningskilde ikke fundet, er meget velegnet til at forstå begreberne behandlet i dette kapitel bedre. Har du installeret BlueJ, kan du hente bogens eksempler som BlueJ-projekter (på http://javabog.dk/OOP/kode/bluej-projekter/).
Pak ZIP-filen med BlueJ-projekterne ud, start
BlueJ og åbn projektet kapitel_04_bokse.
Projektet viser
klasserne Boks2, BenytBoks2, Boks3 og BenytBoks3.
Tryk på
'Compile'-knappen for at oversætte alle klasserne.
Højreklik derefter på Boks2 og
opret et objekt ved at vælge 'new Boks2()'.
Boks2-Objektet
dukker op som en rød kasse nederst til venstre:
Højreklik
på det røde Boks2-objekt og kald sætMål().
Sæt parametrene til f.eks. 2, 3 og 3 og klik OK.
Undersøg
objektets variabler (højreklik og vælg Inspect).
Kald derefter volumen() igen og tjek resultatet.
Højreklik på BenytBoks2 og vælg main() for at køre programmet.
Åbn nu Boks3 og se på
kildekoden.
Skift derefter til 'Interface' for at se javadoc for
klassen.
Når du har leget med Boks-klasserne som de er, så prøv at ændre i klasserne (ret i koden). Prøv derefter de andre BlueJ-projekter.
Der er 2 fejl i koden nedenfor. Find dem og ret dem. Kig eventuelt på afsnittene om formen af en klasse og formen af en metode ovenfor.
public class Fejlfinding1
{
private int a = 5;
private String b;
private c String;
{
}
Der er 3 fejl i koden nedenfor. Find dem og ret dem.
public class Fejlfinding2
{
private String b;
public Fejlfinding2() {
return b;
}
b = "Hej";
public Fejlfinding2(String c) {
b = c
}
}
Der er 9 fejl i koden nedenfor. Find dem, ret dem og begrund rettelserne.
public class Fejlfinding4
{
private int a = 5;
private String b;
public void x1(int y)
{
y = a;
}
a = 2;
public Fejlfind(int a) {
a = 4;
String = "goddag";
}
public x2(int y)
{
a = y*2;
}
public int x3(y int)
{
b = y;
}
public void x4(int y)
{
return 5;
}
}
Der er 8 fejl i koden nedenfor. Find dem og ret dem.
import java.util.*;
public class Fejlfinding3
{
public String l = 'hej';
public string etLangtVariabelnavn;
public arrayList v2;
public INT v3;
public v5;
public int vi = 5.3;
public ArrayList vi3 = "xx";
public String vi5 = new String(Hej);
}
Der er 6 fejl i koden nedenfor. Find dem og ret dem.
public class Fejlfinding5
{
private int a = 5
public void x1(int y) {
a = y; }
}
public void x2(int y) // fejlmeddelelse: 'class' or 'interface' expected
{
a = y*2*x1;
public int x3(int y)
{
a = y*x2();
}
}
public void x4(int y)
{
x4 = 8;
}
}
Find så mange fejl du kan i koden nedenfor og ret dem.
public class Fejlfinding6
{
public int m()
{
System.out.println("Metode m blev kaldt.");
public void m2()
{
String s = "Metode m2 blev kaldt."
System.out.println(s);
return s;
}
public m3()
{
System.out.println("Metode m3 blev kaldt.");
}
public void m4(int)
{
System.out.println("m4 fik parameter "+p);
}
public void m5(p1, p2, p3)
{
System.out.println("m5 fik "+p1+" og "+p2+" og "+p3);
System.out.println("s er: "+s);
String s = p2.toUpperCase();
}
}
1Vi vil senere se, at såkaldte klassevariabler (static) også erklæres her.
2Når programudførelsen går ind i metoden, oprettes de lokale variabler. De nedlægges igen, når metoden returnerer.
3Man kan godt lave en klasse, der både har en main()-metode og som der også oprettes objekter ud fra, men det kan være forvirrende for en begynder at blande de to ting sammen.
4Skriver man ingenting, svarer det til public for klasser i samme pakke – se afsnit Fejl: Henvisningskilde ikke fundet.
5Det helt rigtige ville være at lade metoden 'protestere' og afbryde programudførslen ved at kaste en exception – f.eks. IllegalArgumentException. Det ser vi på senere, i kapitel Fejl: Henvisningskilde ikke fundet afsnit Fejl: Henvisningskilde ikke fundet.
6Kommentarerne med /** og */ gør, at man automatisk kan generere dokumentation ud fra klassen (af samme form som standardklassernes dokumentation). Javadoc er behandlet i kapitel 2 i "Videregående programmering i Java", der kan læses på http://javabog.dk/VP.
7Man kan dog ikke tildele this en anden værdi.