4 Definition af klasser

Indhold:



Kapitlet forudsættes af resten af bogen.

Forudsætter kapitel 3, Objekter.



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.

4.1 En Boks-klasse

Lad os tage et eksempel på en klassedefinition:



Vi definerer klassen Boks, som indeholder tre variabler, nemlig bredde, højde og længde. Derudover definerer vi metoden volumen(), som arbejder på disse data. Metoden returnerer en double og tager 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; 
  }
}

4.1.1 Variabler

Variablerne bredde, højde og længde kaldes også objektvariabler, fordi hvert Boks-objekt har en af hver.


Objektvariabler 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 højde-, en bredde- og en længde-variabel.


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 højde, længde og bredde begynder vol-variabler altså ikke at eksistere, bare fordi vi har skabt en Boks2.

4.1.2 Brug af klassen

Objekter af klassen Boks kan f.eks. benyttes på følgende måde:


public class BenytBoks
{
  public static void main(String args[])
  {
    double rumfang;

    Boks boksobjekt;
    boksobjekt = new Boks();
    boksobjekt.længde= 12.3;
    boksobjekt.bredde= 2.22;
    boksobjekt.højde = 6.18;
    rumfang = boksobjekt.volumen();
    System.out.println("Boksens volume: "+ rumfang);
  }
}

Boksens volume: 168.75108


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= 12.3;
  boksobjekt.bredde= 2.22;
  boksobjekt.højde = 6.18;


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 linierne ovenfor. Metoden returnerer en double - denne lægges over i rumfang-variablen, som udskrives.

4.1.3 Metodedefinition

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 den eventuelt tager:

  double volumen()

Kroppen kommer lige under hovedet4:

  {
    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 linie i kroppen fortæller, at resultatet af vol bliver givet tilbage (returneret) til der, hvor 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= 12.3;
    boksobjekt.bredde= 2.22;
    boksobjekt.højde= 6.18;
    rumfang = boksobjekt.volumen();


Her får det Boks-objekt, som boksobjekt refererer til, sat sine variabler, og når metoden volumen() derefter kaldes på dette objekt, vil længde, bredde og højde have disse værdier. rumfang bliver sat til den værdi, vol har i return-linien, 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

4.1.4 Flere objekter

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 args[])
  {
    Boks boks1, boks2;
    boks1 = new Boks();
    boks2 = new Boks();

    boks1.længde= 12.3;
    boks1.bredde= 2.22;
    boks1.højde= 6.18;

    boks2.længde= 13.3;
    boks2.bredde= 3.33;
    boks2.højde= 7.18;

    double v1, v2;

    v1 = boks1.volumen();
    v2 = boks2.volumen();

    System.out.println("Volumenforskel: "+ (v2 - v1));
  }
}

Volumenforskel: 149.24394


Når vi kalder volumen() på boks1 og boks2, 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.


4.2 Indkapsling

Indkapsling 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, se eksemplet ovenfor, kan det føre til situationer, som kommer ud af kontrol i store komplekse systemer.


Ved at indkapsle data er den eneste måde at ændre data på brugen af metoder. I metoderne kan man sikre sig mod vanvittige overgreb på variabler 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 10001 pakker på hver minus en kubikmeter og 10000 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 "synligheden" af en variabel eller metode med nøgleordene public og private:


public betyder "synlig for alle"
private betyder "kun synlig i klassen"

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)
    {
       System.out.println("Ugyldige mål. Bruger standardmål.");
      længde = 10.0;
      bredde = 10.0;
      højde  = 10.0;
    } else {
      længde = lgd;
      bredde = b;
      højde  = h;
    }
  }

  public double volumen()
  {
    double vol;
    vol = længde*bredde*højde;
    return vol; 
  }
}


Klassen er illustreret med UML nedenfor. Bemærk, at variablerne er private, så de har et - foran, mens metoderne, som kan ses udefra (public), har et + foran:



Nu da variablerne bredde, højde og længde 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 kan kalde for at sætte målene. Nu da den eneste måde at ændre data på er ved specifikt at kalde metoden sætMål(), kan vi indlægge en ønsket logik - for eksempel sikre os mod 0 (nul) eller negative værdier.


public class BenytBoks2
{
  public static void main(String args[])
  {
    Boks2 enBoks = new Boks2();

    //ulovligt: enBoks.længde= 12.3;
    //ulovligt: enBoks.bredde= 2.22;
    //ulovligt: enBoks.højde= 6.18;

    enBoks.sætMål( 2.0, 2.5, 1.5);

    System.out.println("Volumen er: "+ enBoks.volumen());

    enBoks.sætMål(-2.0, 0.0, 1.0);

    System.out.println("Volumen er: "+ enBoks.volumen());

    enBoks.sætMål( 2.0, 3.0 ,1.0);

    System.out.println("Volumen er: "+ enBoks.volumen());
  }
}

Volumen er: 7.5
Ugyldige mål. Bruger standardmål.
Volumen er: 1000.0
Volumen er: 6.0


En anden fordel ved indkapsling er, at man bliver uafhængig af repræsentationen. Man kunne f.eks. gemme volumen i Boks-klassen i stedet for højden og så lade højden være beregnet.


4.3 Konstruktører

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 eksempel med nogle konstruktører:



public class Boks3 
{
  private double længde;
  private double bredde;
  private double højde;

  public Boks3()
  {
    System.out.println("Standardboks oprettes");
    sætMål(10, 10, 10);
  }

  // En anden konstruktør der tager bredde, højde og længde
  public Boks3(double lgd, double b, double h)
  {
    System.out.println("Boks oprettes med lgd="+lgd+" b="+b+" h="+h);
    sætMål(lgd,b,h);
  }

  public void sætMål(double lgd, double b, double h)
  {
    if (lgd<=0 || b<=0 || h<=0) 
    {
       System.out.println("Ugyldige mål. Bruger standardmål.");
      længde = 10.0;
      bredde = 10.0;
      højde  = 10.0;
    } else {
      længde = lgd;
      bredde = b;
      højde  = h;
    }
  }

  public double volumen()
  {
    return længde*bredde*højde; 
  }
}


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 prøver Boks3 med:

public class BenytBoks3
{
  public static void main(String args[])
  {
    Boks3 enBoks;
    // brug konstruktøren uden parametre
    enBoks = new Boks3();

    System.out.println("Volumen er: "+ enBoks.volumen());

    Boks3 enAndenBoks;
    // brug den anden konstruktør
    enAndenBoks = new Boks3(5,5,10);

    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

4.3.1 Standardkonstruktører

Når vi i de foregående eksempler (f.eks. Boks2) ikke har benyttet en konstruktør, er det, fordi Java, hvis ikke en konstruktør er erklæret, selv erklærer en tom standardkonstruktør uden parametre. Dvs. Java i Boks2's tilfælde usynligt har defineret konstruktøren:


public Boks2()
{
}


Denne konstruktør har vi kaldt, hver gang vi har oprettet et objekt med 'new'.


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 oversætteren i BenytBoks3 brokke sig over, at denne type konstruktør ikke fandtes:

  BenytBoks3.java:7: No constructor matching Boks3() found in class Boks3.
                  enBoks = new Boks3();

4.3.2 Opgaver

  1. Definér klassen Pyramide. Objekterne skal have variablerne side og højde (definér en konstruktør) og en metode til at udregne volumen (side*side*højde/4). Skriv en BenytPyramider, som opretter 3 pyramider og udregner volumen.

  2. Ret Boks3 til også at have variablen massefylde, og definér en ekstra konstruktør der også tager massefylden (den oprindelige konstruktør med lgd, b og h kan sætte massefylden til 1). Lav også metoder til at sætte massefylden, sætMassefylde(double m), og udregne vægten, vægt(). Test din klasse med en ændret udgave af BenytBoks3.

4.4 En Terning-klasse

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
{
  // den side der vender opad lige nu
  int værdi;

  // konstruktør
  public Terning()
  {
    kast(); // kald kast() der sætter værdi til noget fornuftigt
  }

  // metode til at kaste terningen
  public void kast()
  {
    // find en tilfældig side
    double tilfældigtTal = Math.random();
    værdi = (int) (tilfældigtTal * 6 + 1);
  }

  // giver en beskrivelse af terningen som en streng
  public String toString()
  {
    String svar = ""+værdi;  // værdi som streng, f.eks. "4"
    return svar;
  }
}


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 args[])
  {
    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.værdi);
      if (t.væ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.

4.4.1 Opgaver

  1. Skriv et program, der rafler med to terning-objekter, indtil der slås en 6'er.

  2. Skriv et program, der rafler med fire terninger, indtil der slås tre eller fire 6'ere. Udskriv antal øjne for hver terning.

  3. Skriv et program, der rafler med 12 terninger, og hver gang udskriver øjnene, summen af øjnene og hvor mange 6'ere der kom. Brug Vector-klassen til at holde styr på terningerne.

  4. 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.


4.5 Relationer mellem objekter

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).

4.5.1 En Raflebæger-klasse

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, der 'har' terningerne, og som er bekvem at bruge fra hovedprogrammet. Med 'har' menes, at referencerne til Terning-objekterne kendes af raflebægeret, men 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.*;

public class Raflebaeger
{
  public Vector terninger;  // Raflebaeger har en vektor af terninger

  public Raflebaeger(int antalTerninger)
  {
    terninger = new Vector();
    for (int i=0;i<antalTerninger;i++)
    {
      Terning t;
      t = new Terning();
      tilføj(t);
    }
  }

  public void tilføj(Terning t)       // Læg en terning i bægeret
  {
    terninger.addElement(t);
  }

  public void ryst()                  // Kast alle terningerne
  {
    for (int i=0;i<terninger.size();i++) 
    {
      Terning t;
      t = (Terning) terninger.elementAt(i);
      t.kast();
    }
  }

  public int sum()                    // Summen af alle terningers værdier
  {
    int resultat;
    resultat=0;
    for (int i=0;i<terninger.size();i++) 
    {
      Terning t;
      t = (Terning) terninger.elementAt(i);
      resultat = resultat + t.værdi;
    }
    return resultat;
  }
  
  public int antalDerViser(int værdi) // Antal terninger med en bestemt værdi
  {
    int resultat;
    resultat = 0;
    for (int i=0;i<terninger.size();i++) 
    {
      Terning t;
      t = (Terning) terninger.elementAt(i);
      if (t.værdi==værdi) 
      {
        resultat = resultat + 1;
      }
    }
    return resultat;
  }

  public String toString ()           // Beskriv bægerets indhold
  {// (vektorens 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 ToSeksere
{
  public static void main(String[] args)
  {  
    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.


Linien:

    bæger = new Raflebaeger(3);

opretter et raflebæger med tre terninger i.

4.5.2 Opgaver

  1. 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.

  2. 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.

  3. 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, ved at kalde en fireEns()-metode:

  public boolean fireEns() 
  { 
    ... 
  } 

Lav tilsvarende de andre metoder.

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
... 


4.6 Nøgleordet this

Nogle gange kan et objekt have brug for at referere til sig selv. Det gøres med this-nøgleordet, der ligner (og bruges som) en variabel5.


this refererer til det objekt, man er i

Læs igen definitionen af Boks2. I dens sætMål-metode brugte vi andre variabelnavne for parametrene (nemlig lgd, b og h) end objektvariablerne (længde, bredde og højde). Vi kan altid få fat i objektets variabler med this, så vi kunne også have brugt de samme variabelnavne:

import java.util.*;
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) 
    {
       System.out.println("Ugyldige mål. Bruger standardmål.");
      this.længde = 10.0;
      this.bredde = 10.0;
      this.højde  = 10.0;
    } else {
      this.længde = længde;
      this.bredde = bredde;
      this.højde  = højde;
    }
  }

  public double volumen()
  {
    return bredde*højde*længde; 
  }

  public void føjTilVektor(Vector v)
  {
    v.addElement(this);
  }
}


this virker som en variabel der refererer til objektet selv


I sætMål() er der nu to sæt variabler med samme navn. Da vælger Java altid den variabel, der er "tættest på", dvs. f.eks. 'længde' svarer til parametervariablen længde. For at få fat i 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 vektor med:

  Vector v = new Vector()
  Boks2medThis b = new Boks2medThis();
  v.addElement(b);


Med metoden føjTilVektor() kan vi i stedet for bede b om at tilføje sig selv:

  b.føjTilVektor(v);


Vi vil senere (i Spiller-klassen i matadorspillet i kapitlet om nedarvning) se et eksempel på dette, hvor det er en fordel i praksis.


4.7 Ekstra eksempler

Dette afsnit giver nogle ekstra eksempler, der repeterer stoffet i kapitlet.

4.7.1 En n-sidet terning

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.

Vi har ladet antallet af sider og værdien være private, og lavet metoden hentVærdi(), som kan bruges udefra. Der er også en sætVærdi()-metode, mens antallet af sider ikke kan ændres udefra, når først terningen er skabt.



// En n-sidet terning.
public class NSidetTerning
{
  // hvor mange sider har terningen (normalt 6)
  private int sider;

  // den side der vender opad lige nu
  private int værdi;

  // konstruktør opretter normal terning med 6 sider
  public NSidetTerning ()
  {
    sider = 6;
    kast(); // sæt værdi til noget
  }

  // opretter terning med et vist antal sider
  public NSidetTerning (int antalSider)
  {
    if (antalSider >= 3) sider = antalSider;
    else sider = 6;
    kast();
  }

  // metode der kaster terningen
  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å siden der vender opad
  public int hentVærdi ()
  {
    return værdi;
  }

  // sætter antallet af øjne der vender opad
  public void sætVærdi (int nyVærdi)
  {
    if (nyVærdi > 0 && nyVærdi <= sider) værdi = nyVærdi;
    else System.out.println("Ugyldig værdi");
  }

  // giver en strengrepræsentation af terningen
  // 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;
  }
}


Her er et program til at afprøve klassen med:


public class BenytNSidetTerning
{
  public static void main(String args[])
  {
    NSidetTerning t = new NSidetTerning(); // sekssidet terning

    System.out.println("t viser nu "+t.hentVæ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.hentVærdi() == t6.hentVæ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)


4.7.2 Personer

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.*;
public class Person
{
  public String fornavn;
  public String efternavn;
  public int alder;
  public Vector konti; // bruges senere

  public Person(String fornavnP, String efternavnP, int alderP)
  {
    fornavn = fornavnP;
    efternavn = efternavnP;
    alder = alderP;
    konti = new Vector(); // 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-en-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 args[])
  {
    Person x, y, z;
    x = new Person("Jacob", "Nordfalk", 30);
    y = new Person("Kai", "Lund", 86);
    z = new Person("Peter", "Holm", 2);

    System.out.println("Vi har oprettet "+x+", "+y+" og "+z);
    x.præsentation();
    y.præsentation();
    z.præsentation();
    x.hils(y);
    y.hils(x);
    z.hils(x);
  }
}

Vi har oprettet Jacob Nordfalk (30 år), Kai Lund (86 år) og Peter 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 linien

    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 linien under er det lige omvendt.

4.7.3 Bankkonti

Lad os se nærmere på relationer mellem objekter i bankverdenen. De vigtigste egenskaber ved en bankkonto er saldoen, og hvem der ejer den. Et Konto-objekt kunne altså have et Person-objekt tilknyttet, en har-en-relation, og denne person bør være kendt, når kontoen oprettes. Det er derfor oplagt, at ejeren skal angives i Konto's konstruktør. Kontoen skal også sørge for at indsætte sig selv (her bruges nøgleordet this) i personens liste over konti, sådan at der er konsistens mellem data i Konto-objektet og data i Person-objektet.


public class Konto
{
  public int saldo;
  public Person ejer;

  public Konto(Person ejeren)
  {
    saldo = 0;
    ejer = ejeren;               // Sæt kontoen til at referere til personen
    ejer.konti.addElement(this); // ..og personen til at referere til kontoen
  }

  public void overførsel(int kroner)
  {
    saldo = saldo + kroner;
  }

  public String toString()
  {
    return ejer+" har "+saldo+" kroner";
  }
}


En Konto har altid en Person tilknyttet


Dette er et eksempel på en har-en-relation begge veje. Læg mærke til, hvordan vi fra Konto-objektet får fat i listen af ejerens konti. Den tilføjer vi så kontoen selv - this - til:

    ejer.konti.addElement(this);


4.8 Test dig selv (fjernet)

Dette afsnit findes i den trykte bog


4.9 Resumé (fjernet)

Dette afsnit findes i den trykte bog


4.10 Opgaver

Husk at lave små main()-programmer, der afprøver de ting, du programmerer.


  1. Udbyg Person-klassen med metoden formue(), der skal returnere summen af saldiene på personens konti. Lav en BenytKonto med flere personer, der har flere konti.

  2. Lav en klasse, der repræsenterer en Postering på en bankkonto med tekst, indsat beløb (udtræk regnes negativt) og dato. Udvid Konto med en liste af posteringer, og metoden udskrivPosteringer(), der skal udskrive posteringerne og løbende saldo på skærmen.

  3. Lav en klasse, der repræsenterer en bil. En bil har en farve, et antal kørte kilometer og en (vurderet) pris.

  4. Udbyg Bil-klassen med en vektor, 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 en person sætter sig ind i bilen.
    public String hvemSidderIBilen() // giver en streng, der beskriver personerne i bilen.
    public void alleStigerUd() // kaldes, når alle stiger ud af bilen.

  5. Udbyg Person-klassen, så en person kan eje en bil. Udbyg metoden formue(), sådan at den husker at indregne bilens pris. Metoden skal virke både for personer med og uden bil (Person-objekter uden bil kan have denne variabel sat til null).

4.10.1 Fejlfinding

  1. 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;
  {
}


  1. Der er 3 fejl i koden nedenfor. Find dem, og ret dem.

public class Fejlfinding2
{
  private String b;
  b = "Hej";

  public Fejlfinding2() {
    return b;
  }

  public Fejlfinding2(String c) {
    b = c
  }
 }
}


  1. Der er 9 fejl i koden nedenfor. Find dem, og ret dem.

import java.util.*;

public class Fejlfinding3
{
  public String v = 'hej';
  public string etLangtVariabelnavn;
  public vector v2;
  public INT v3;
  public flot v4;
  public v5;
  public int vi = 5.3;
  public Vector vi3 = "xxx";
  public String vi5 = new String(Hej);
}


  1. Der er 8 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(int y)
  {
    b = y;
  }

  public void x4(int y)
  {
    return 5;
  }


  1. Der er 7 fejl i koden nedenfor. Find dem, og ret dem.

public class Fejlfinding5
{
  private int a = 5
  public void x1(int y)   {
    a = y;                         }
}
  public 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;
  }
}


  1. 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, og de nedlægges igen, når metoden returnerer.

3Man kan godt lave en klasse, der både har en main()-metode og som der oprettes objekter ud fra, men det anses for at være dårlig stil, fordi det blander tingene sammen.

4Ligesom hos de fleste dyr :-)

5Man kan dog ikke tildele this en anden værdi.


Jacob Nordfalk - Objektorienteret programmering i Java - http://javabog.dk