Kapitel 6. Nedarvning

Kapitlet forudsættes af resten af bogen.

Forudsætter Kapitel 5, Definition af klasser.

I dette kapitel vil vi se på, hvordan man kan genbruge programkode ved at tage en eksisterende klasse og udbygge den med flere metoder og variabler (nedarvning).

6.1. At udbygge eksisterende klasser

Hvad gør man, hvis man ønsker en klasse, der ligner en eksisterende klasse, men alligevel ikke helt er den samme?

Svaret er: Man kan definere underklasser, der arver (genbruger en del af koden) fra en anden klasse og kun definerer den ekstra kode, der skal til for at definere underklassen i forhold til stamklassen (kaldet superklassen).

Arv er et meget vigtigt element i objektorienterede sprog. Med nedarvning kan man have en hel samling af klasser, der ligner hinanden på visse punkter, men som er forskellige på andre punkter.

6.1.1. Eksempel: En falsk terning

Hvis man vil snyde i terningspil, findes der et kendt trick: Bruge sine egne terninger, hvor man har boret 1'er-sidens hul ud, kommet bly i hullet og malet det pænt over, så det ikke kan ses. Sådan en terning vil have meget lille sandsynlighed for at få en 1-er og en ret stor sandsynlighed for at få en 6'er.

Herunder har vi lavet en nedarvning fra Terning til en ny klasse, FalskTerning, ved at starte erklæringen med:

public class FalskTerning extends Terning

Vi har automatisk overtaget (arvet) alle metoder og variabler fra Terning-klassen fordi vi skriver "extends Terning". Dvs. at et FalskTerning1-objekt også har en værdi-variabel og en toString()-metode.

Vi ændrer nu klassens opførsel ved at definere en anden udgave af kast()-metoden:

// En Terning-klasse for falske terninger.

public class FalskTerning1 extends Terning
{
  // tilsidesæt kast med en "bedre" udgave
  public void kast()
  {
    // udskriv så vi kan se at metoden bliver kaldt
    System.out.print("[kast() på FalskTerning1] ");

    værdi = (int) (6*Math.random() + 1);

    // er det 1 eller 2? Så lav det om til 6!
    if ( værdi <= 2 ) værdi = 6;
  }
}

I klassediagrammet er underklassen vist med en hul pil fra FalskTerning1 til Terning.

Dette kaldes også en er-en-relation; FalskTerning1 er en Terning, da den jo har alle de egenskaber en terning har.

Kort sagt:

En klasse kan arve variabler og metoder fra en anden klasse

Klassen, der nedarves fra, kaldes superklassen

Klassen, der arver fra superklassen, kaldes underklassen

Underklassen kan tilsidesætte (omdefinere) metoder arvet fra superklassen ved at definere dem igen

Andre steder i litteraturen er der brugt talrige betegnelser for superklasse og underklasse. Her er et udpluk:

Figur 6-1. Java

Superklasse kaldes også: Baseklasse, basisklasse, forældreklasse, stamklasse.

Underklasse kaldes også: Afledt klasse, nedarvet klasse, barn, subklasse.

I vores eksempel er superklassen Terning og underklassen FalskTerning1.

I det følgende program kastes med to terninger, en rigtig og en falsk:

public class Snydespil1
{
  public static void main(String[] args)
  {
    Terning t1 = new Terning();
    FalskTerning1 t2 = new FalskTerning1();

    System.out.println("t1: "+t1); // kunne også kalde t1.toString()
    System.out.println("t2: "+t2);

    for (int i=0; i<5; i++)
    {
      t1.kast();
      t2.kast();
      System.out.println("t1=" + t1 + "  t2=" + t2);
      if (t1.værdi == t2.værdi) System.out.println("To ens!");
    }
  }
}

Resultatet bliver:

[kast() på FalskTerning1] t1: 1
t2: 3
[kast() på FalskTerning1] t1=1  t2=5
[kast() på FalskTerning1] t1=1  t2=3
[kast() på FalskTerning1] t1=4  t2=3
[kast() på FalskTerning1] t1=6  t2=6
To ens!
[kast() på FalskTerning1] t1=2  t2=6

Vi kan altså bruge FalskTerning1-objekter på præcis samme måde som Terning-objekter.

6.1.2. At udbygge med flere metoder og variabler

Lad os nu se på et eksempel, hvor vi definerer nogle variabler og metoder i nedarvingen.

Figur 6-2. Java

public class FalskTerning2 extends Terning
{
  public int snydeværdi;

  public void sætSnydeværdi(int nySnydeværdi)
  {
    snydeværdi = nySnydeværdi;
  }

  public void kast()
  {
    System.out.print("[kast() på FalskTerning2] ");

    værdi = (int) (6*Math.random() + 1);

    // 1 eller 2? Så lav det om til snydeværdi!
    if ( værdi <= 2 ) værdi = snydeværdi;
  }
}

FalskTerning2 har fået en ekstra variabel, snydeværdi, og en ekstra metode, sætSnydeværdi(), der sætter snydeværdi til noget andet.

public class Snydespil2
{
  public static void main(String[] args)
  {
    FalskTerning2 t1 = new FalskTerning2();
    t1.sætSnydeværdi(4);

    for (int i=0; i<5; i++)
    {
      t1.kast();
      System.out.println("t1=" + t1);
    }
  }
}

Resultatet bliver:

[kast() på FalskTerning2] [kast() på FalskTerning2] t1=4
[kast() på FalskTerning2] t1=4
[kast() på FalskTerning2] t1=6
[kast() på FalskTerning2] t1=6
[kast() på FalskTerning2] t1=4

6.1.3. Nøgleordet super

Nogen gange ønsker man i en nedarvet klasse at få adgang til superklassens metoder, selvom de måske er blevet tilsidesat med en ny definition i nedarvingen. F.eks. kunne det være rart, hvis vi kunne genbruge den oprindelige kast()-metode i FalskTerning.

Med super refererer man til de metoder, der er kendt for superklassen. Dermed kan vi skrive en smartere udgave af FalskTerning:

public class FalskTerning3 extends Terning
{
  // tilsidesæt kast med en "bedre" udgave
  public void kast ()
  {
    super.kast(); // kald den oprindelige kast-metode

    // blev det 1 eller 2? Så lav det om til en 6'er!
    if ( værdi <= 2 ) værdi = 6;
  }
}

super.kast() kalder kast()-metoden i superklassen. Derefter tager vi højde for, at det er en falsk terning.