javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen

4 Definition af klasser

Indhold:

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.

4.1 En Boks-klasse

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; 
  }
}

4.1.1 Variabler

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.

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

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

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

4.2 Indkapsling

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.

4.2.1 Opgaver

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

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

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

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 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();

4.3.2 Opgaver

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

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

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
{
  /** 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.

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

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

4.6 Nøgleordet this

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)

Læs igen definitionen af Boks2. I dens sætMål()-metode brugte vi andre variabelnavne for parametrene (nemlig lgd, b og h) end felterne (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.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.

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.

/** 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)

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

4.7.3 Design af klasser

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.

4.7.4 Projekt Billetautomat

Dette afsnit giver en alternativ introduktion til objekter og klasser, ved at se på hvordan man kunne modellere en simpel billetautomat.

Projektbeskrivelse

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.

Krav

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.

Opgave

Opstil 4-5 yderligere krav som automaten bør opfylde i fase 1, samt et antal idéer til krav til fremtidige faser.

Analyse - brugsscenarier

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

Opgave

Skriv brugsscenarierne ned for et køb af en kunde (K2 – K4) og for medarbejderens aflæsning (K6), ved at udfylde skabelonen ovenfor.

Design og programmering

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

Opgaver

  1. Afprøv Billetautomaten grundigt i BlueJ og se om den i alle tilfælde gør noget hensigtsmæssigt.

  2. Undersøg om kunden har mulighed for at misbruge automaten!

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

4.8 Test dig selv

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

4.9 Resumé

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

4.9.1 Formen af en klasse

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

4.9.2 Formen af en metode

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

4.10 Opgaver

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

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

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

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

4.10.1 BlueJ

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.

4.10.2 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;

  public Fejlfinding2() {
    return b;
  }

  b = "Hej";

  public Fejlfinding2(String c) {
    b = c
  }
}
  1. 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;
  }
}
  1. 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);
}
  1. 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;
  }
}
  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();
  }
}

4.11 Avanceret

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

4.11.1 Bankkonti

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

4.11.2 Opgaver

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

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.

javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen
http://javabog.dk/ - Forord af Jacob Nordfalk.
Licens og kopiering under Åben Dokumentlicens (ÅDL) hvor intet andet er nævnt (79% af værket).

Ønsker du at se de sidste 21% af dette værk (267325 tegn) skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.