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

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

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

Figuren 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 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 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ærdier.

public class BenytBoks2
{
  public static void main(String[] arg)
  {
    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, 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.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:

/** 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");
    sætMål(10, 10, 10);
  }

  /** en anden konstruktør der får 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);
  }

  /** sætter boksens bredde, højde og længde */
  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;
    }
  }

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

xxx forklar /** og javadoc

Vi kan afprøve Boks3 med:

public class BenytBoks3
{
  public static void main(String[] arg)
  {
    Boks3 enBoks;
    enBoks = new Boks3();             // brug konstruktøren uden parametre
    System.out.println("Volumen er: "+ enBoks.volumen());

    Boks3 enAndenBoks;    
    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 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 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 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 af dem.

  2. Ret Boks3 til også at have variablen massefylde, og 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 massefylden til 1). Lav også 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 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
{
  /** antallet af øjne på den side på terningen, der vender opad lige nu */
  public int værdi;

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

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

Et raflebæger indeholder terninger.

Pilen symboliserer har-relation: Et Raflebaeger har 0 til * (flere) terninger.

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
{
  /** listen af terninger, der er i raflebægeret */
  public ArrayList terninger;

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

  /** lægger en terning i bægeret */
  public void tilføj(Terning t)
  {
    terninger.add(t);
  }

  /** ryster bægeret, så alle terningerne bliver 'kastet' og får en ny værdi */
  public void ryst()
  {
    for (int i=0;i<terninger.size();i++) 
    {
      Terning t;
      t = (Terning) terninger.get(i);
      t.kast();
    }
  }

  /** finder summen af alle terningernes værdier */
  public int sum()
  {
    int resultat=0;
    for (int i=0; i<terninger.size(); i++) 
    {
      Terning t;
      t = (Terning) terninger.get(i);
      resultat = resultat + t.værdi;
    }
    return resultat;
  }

  /** finder antallet af terninger, der viser en bestemt værdi */
  public int antalDerViser(int værdi)
  {
    int resultat;
    resultat = 0;
    for (int i=0;i<terninger.size();i++) 
    {
      Terning t;
      t = (Terning) terninger.get(i);
      if (t.væ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 ToSeksere
{
  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.

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, 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 this-nøgleordet, der ligner (og bruges som) en variabel5.

this refererer til det objekt, man er i


this virker som en variabel, der refererer til objektet selv

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øjTilListe(ArrayList l)
  {
    l.add(this);
  }
}

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 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 hentVærdi ()
  {
    return værdi;
  }

  /** ændrer terningen til at vende en bestemt side 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 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;
  }
}

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.

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.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 ArrayList konti; // bruges senere

  public Person(String fornavnP, String efternavnP, int alderP)
  {
    fornavn = fornavnP;
    efternavn = efternavnP;
    alder = alderP;
    konti = new ArrayList(); // 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 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 Design af klasser

I kapitel 22, Objektorienteret analyse og design 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.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

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 l = 'hej';
  public string etLangtVariabelnavn;
  public arrayList v2;
  public INT v3;
  public Double v4 = 5.3;
  public v5;
  public int vi = 5.3;
  public ArrayList vi3 = "xx";
  public String vi5 = new String(Hej);
}
  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 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, 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 kan være forvirrende for en begynder at blande de to ting sammen.

4Skriver man ingenting, svarer det til public (inden for samme pakke - se afsnit 6.9).

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

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

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