javabog.dk  |  << forrige  |  indhold  |  næste >>  |  eksempler

21 Avancerede klasser

Indhold:


Indre klasser er en forudsætning for at forstå den måde, mange værktøjer laver kode til at håndtere hændelser.

Forudsætter kapitel 5, Nedarvning, 12, Interfaces (og 13, Hændelser og 17, Flertrådet programmering for at forstå nogle af eksemplerne) og ønske om at vide mere om emner, som først bliver relevante, når man laver større programmer.



21.1 Nøgleordet abstract

Noget der er erklæret abstract er ikke implementeret og skal defineres i en nedarvning. Det skrives i kursiv i UML-notationen.

21.1.1 Abstrakte klasser

En abstrakt klasse erklæres således

public abstract class X 
{
  public void a()
  {
    //..
  }
}



Det er ikke tilladt at oprette objekter fra en abstrakt klasse

  public static void main(String[] arg)
  {
    X x = new X();   // ulovligt! X er abstrakt



I stedet skal man arve fra klassen

public class Y extends X 
{
}

og lave objekter fra den nedarvede klasse:

  public static void main(String[] arg)
  {
    X x;            // lovligt

    x = new Y();    // lovligt, Y er ikke abstrakt

Basisklasserne for IO-systemet, InputStream og OutputStream er abstrakte, fordi programmøren altid skal bruge en mere konkret klasse, f.eks. FileInputStream (se afsnit 15.6.2).

I Matador-eksemplet afsnit 5.3 kunne vi have erklæret klassen Felt for abstrakt, da der ikke måtte oprettes 'nøgne' Felt-objekter, men kun nedarvinger som Start og Helle.

Det er lovligt (og nyttigt i visse tilfælde) at have variabler af en abstrakt klasse (det svarer til, at det er lovligt og nyttigt at have variabler af en interface-type). F.eks. er det i Matador-eksemplet ret vigtigt at have en Felt-variabel, der kan referere til alle slags felter.

21.1.2 Abstrakte metoder

En metode erklæret abstract har et metodehoved, men ingen krop. Den kan kun erklæres i en abstrakt klasse

public abstract class X 
{
  public abstract void a();
}

Nedarvede klasser skal definere de abstrakte metoder (eller også selv være abstrakte)

public class Y extends X 
{
  public void a()
  {
    //..
  }
}



I Matador-eksemplet afsnit 5.3 kunne vi have erklæret metoden landet() for abstrakt og på den måde sikre, at den blev defineret i alle nedarvinger.

// Superklassen for alle matadorspillets felter
public abstract class Felt
{
  String navn;

  public void passeret(Spiller sp)
  {
    System.out.println(sp.navn+" passerer "+navn);
  }

  public abstract void landet(Spiller sp);
}

Hvis vi glemte at definere landet() i en nedarving (eller måske kom til at stave metodenavnet forkert), ville oversætteren stoppe os og komme med en fejlmeddelelse.

En klasse, hvor alle metoder er defineret abstrakte, kaldes en ren abstrakt klasse. Sådan en klasse har i de fleste tilfælde nogenlunde samme rolle som et interface1.

21.2 Nøgleordet final

Noget, der er erklæret final, kan ikke ændres. Både variabler, metoder og klasser kan erklæres final.

21.2.1 Variabler erklæret final

En variabel, der er erklæret final, kan ikke ændres, når den først har fået en værdi.

public class X 
{
  public final int a=10;

  //..
  // forbudt: a=11;
}

Herover kan a's værdi ikke ændres i den efterfølgende kode.

Det kan lette overskueligheden at vide, hvilke variabler, der er konstante. Desuden udføres programmet lidt hurtigere.

Foran en objekt- eller klassevariabel bestemmer final ikke adgang/synlighed, men kan bruges sammen med public, protected og private.

final kan også bruges på lokale variabler (hvor public, protected og private aldrig kan bruges):

  public static void main(String[] arg)
  {
    final Vector v = new Vector();

    //v = new Vector();  // ulovligt! v kan ikke ændres.

Bemærk: Når vi arbejder med objekter, er variablerne jo referencer til objekterne. En variabel erklæret final kan ikke ændres til at referere til et andet objekt, men objektet kan godt få ændret sin indre tilstand, f.eks. gennem et metodekald:

    v.addElement("Hans");  // lovligt, v refererer stadig til samme objekt

21.2.2 Metoder erklæret final

En metode erklæret final kan ikke tilsidesættes i en nedarving.

public class X 
{
  public final void a()
  {
    // ..
  }
}



public class Y extends X 
{
  public void a() // ulovligt! a() er final
  {
    //..
  }
}

Den virtuelle maskine kan optimere final metoder, så kald til dem sker en smule hurtigere.

21.2.3 Klasser erklæret final

En klasse erklæret final må man overhovedet ikke arve fra (og alle dens metoder bliver dermed final).

public final class X 
{
  // ..
}



public class Y extends X  // ulovligt! X er final
{
}

21.3 Indre klasser

Indre klasser er mindre "hjælpeklasser" defineret inde i en anden klasse. Dette afsnit handler om de forskellige måder at definere indre klasser på, og de forhold, der her gør sig gældende.

Siden Java version 1.1 har der eksisteret 3 slags indre klasser:



Der er flere fordele ved at benytte indre klasser (visse undtagelser bliver forklaret sidst i afsnittet):

21.4 Almindelige indre klasser

En almindelig indre klasse er en klasse, der erklæres på linie med objektvariabler og metoder:

public class YdreKlasse
{
  class IndreKlasse
  {
  }
}

Programkoden i den indre klasse kan anvende alle den ydre klasses variabler og metoder - også de private. Den indre klasse er knyttet til et objekt af den ydre klasse.

21.4.1 Eksempel - Linietegning

Man benytter ofte indre klasser i forbindelse med at lytte efter hændelser. Her kommer Linietegning-eksemplet fra kapitel 13 igen, men hvor vi lader en indre klasse lytte efter museklik.

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class LinietegningIndre extends Applet
{
  // Selv private variabler har den indre klasse adgang til
  private Point trykpunkt = null;
  private Point slippunkt = null;

  public void init()
  {
    Linielytter lytter = new Linielytter();
    this.addMouseListener(lytter);
  }

  // En indre klasse
  class Linielytter implements MouseListener
  {
    public void mousePressed (MouseEvent event)
    {
      trykpunkt = event.getPoint();           // sæt variablen i det ydre objekt
    }

    public void mouseReleased (MouseEvent event)
    {
      slippunkt = event.getPoint();
      repaint();                              // kald det ydre objekts metode
    }

    public void mouseClicked (MouseEvent event) {} // kræves af MouseListener
    public void mouseEntered (MouseEvent event) {} // kræves af MouseListener
    public void mouseExited (MouseEvent event) {}  // kræves af MouseListener
  }
  // slut på indre klasse

  // en metode i den ydre klasse
  public void paint (Graphics g)
  {
    if (trykpunkt != null && slippunkt != null)
      g.drawLine (trykpunkt.x, trykpunkt.y, slippunkt.x, slippunkt.y);
  }
}

Læg mærke til, at den indre klasse uden videre har adgang til den ydre klasses variabler og metoder (sammenlign med Linietegning fra afsnit 13.2).

21.5 Lokale indre klasser

En lokal klasse er defineret i en blok programkode ligesom en lokal variabel.

public class YdreKlasse
{

  public void metode()
  {
    // ...

    class LokalKlasse
    {
      // metoder og variabler her ...
    }
    
    LokalKlasse objektAfLokalKlasse = new LokalKlasse();

    // ...

  }
}

Lokale klasser er kun synlige og anvendelige i den blok, hvor de er defineret. Ligesom lokale variabler er de ikke synlige uden for metoden (og nøgleordene public, private, protected og static foran klassen har derfor ingen mening).

Lokale klasser kan benytte alle variabler og metoder, der er synlige inden for blokken. Dog skal lokale variabler i den omgivende metode være erklæret final, dvs. være konstante, før de kan bruges i den lokale klasse.

Lokale klasser bruges ret sjældent (men de er gode at forstå, før man går videre til anonyme klasser).

Nedenstående er et eksempel på en lokal klasse, der benytter variabler defineret i den ydre klasse:

public class YdreKlasseMedLokalKlasse
{
  private int a1 = 1;          // Objektvariabler behøver ikke være final

  public void prøvLokaltObjekt(final int a2) // Bemærk: final
  {
    final int a3 = 3;                        // Bemærk: final

    class LokalKlasse {                      // definér lokal klasse
      int a4 = 4;
      public void udskriv()
      {
        System.out.println( a4 );
        System.out.println( a3 );
        System.out.println( a2 );
        System.out.println( a1 );
      }
    } // slut på lokal klasse

    LokalKlasse lokal = new LokalKlasse(); // opret lokalt objekt fra klassen
    lokal.udskriv();
  }

  public static void main(String[] arg){
    YdreKlasseMedLokalKlasse ydre = new YdreKlasseMedLokalKlasse();
    ydre.prøvLokaltObjekt(2);
  }
}

4
3
2
1

21.6 Anonyme indre klasser

En anonym klasse er en klasse uden navn, som der oprettes et objekt ud fra der, hvor den defineres (altså en unavngiven lokal klasse).

public class YdreKlasse
{
  public void metode()
  {
    // ... programkode for metode

    X objektAfAnonymKlasse = new X()
    {
      void metodeIAnonymKlasse()
      {
        // programkode
      }
      // flere metoder og variabler i anonym klasse
    };

    // mere programkode for metode
  }
}

Lige efter new angives det, hvad den anonyme klasse arver fra, eller et interface, der implementeres (i dette tilfælde X). Man kan ikke definere en konstruktør til en anonym klasse (den har altid standardkonstruktøren). Angiver man nogen parametre ved new X(), er det parametre til superklassens konstruktør.

Fordelen ved anonyme klasser er, at det tillades på en nem måde at definere et specialiseret objekt præcis, hvor det er nødvendigt - det kan være meget arbejdsbesparende.

21.6.1 Eksempel - filtrering af filnavne

Følgende program udskriver alle javafiler i det aktuelle katalog. Det sker ved at kalde list()-metoden på et File-objekt og give det et FilenameFilter-objekt som parameter.

Interfacet FilenameFilter har metoden accept(File dir, String filnavn), som afgør, om en fil skal tages med i listningen (se evt. Javadokumentationen).

import java.io.*;
public class FilnavnfiltreringMedAnonymKlasse
{
  public static void main(String[] arg)
  {
    File f = new File( "." );      // det aktuelle katalog
    FilenameFilter filter;

    filter = new FilenameFilter() 
        { // En anonym klasse
          public boolean accept( File f, String s) // En metode
          {
            return s.endsWith( ".java");  // svar true hvis fil ender på .java
          }
        } // slut på klassen
      ; // slut på tildelingen filter = new ...

    // brug objektet som filter i en liste af et antal filer
    String[] list = f.list( filter ); 

    for (int i=0; i<list.length; i=i+1) System.out.println( list[i] );  
  }
}

YdreKlasseMedLokalKlasse.java
FilnavnfiltreringMedAnonymKlasse.java
LinietegningAnonym.java
AnonymeTraade.java
A.java
BenytIndreKlasser.java

21.6.2 Eksempel - Linietegning

Udviklingsværktøjer benytter ofte anonyme klasser i forbindelse med at lytte efter hændelser. Her er Linietegning-eksemplet igen, hvor vi bruger en anonym klasse som lytter (sml. eksemplet i 21.4.1).

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class LinietegningAnonym extends Applet
{  
  private Point trykpunkt = null;
  private Point slippunkt = null;

  public void init()
  {    
    this.addMouseListener(
      new MouseListener() 
      {
        public void mousePressed (MouseEvent event)
        {
          trykpunkt = event.getPoint();
        }

        public void mouseReleased (MouseEvent event)
        {
          slippunkt = event.getPoint();
          repaint();  
        }

        public void mouseClicked (MouseEvent event) {}
        public void mouseEntered (MouseEvent event) {}
        public void mouseExited (MouseEvent event) {}
      } // slut på anonym klasse
    ); // slut på kald til addMouseListener()

    System.out.println("Anonymt lytter-objekt oprettet");    
  }

  public void paint (Graphics g)
  {
    if (trykpunkt != null && slippunkt != null)
      g.drawLine (trykpunkt.x, trykpunkt.y, slippunkt.x, slippunkt.y);
  }
}

21.6.3 Eksempel - tråde

Her gennemløber vi en løkke, der i hvert gennemløb opretter et Runnable-objekt fra en anonym klasse og en tråd, der kører på det. Objekterne får hvert sit nummer fra 1 til 5, som de udskriver 20 gange, før de slutter. For at få trådene til at kæmpe lidt om processortiden tæller de til 1.000.000 mellem hver udskrivning.

public class AnonymeTraade
{
  public static void main(String[] arg)
  {

    for (int i=1; i<=5; i=i+1)
    {
      // n bruges i den anonyme klasse
      final int n = i;

      Runnable r = new Runnable()
      {
        public void run()
        {
          for (int j=0; j<20; j=j+1) 
          {
            System.out.print(n);

            // Lav noget, der tager tid
            int x = 0;
            for (int k=0; k<1000000; k=k+1) x=x+k;
          }
          System.out.println("Færdig med "+n+".");
        }
      };

      Thread t = new Thread(r);
      t.start(); 
    }
  }
}

111122221223311332441114433221144332211442255115544332115544332211Færdig med 1.
54442233332233Færdig med 2.
544335544Færdig med 3.
54Færdig med 4.
555555555Færdig med 5.

Man ser, hvordan objekt nummer 1, der blev startet først, også er det første, der afslutter.

21.7 Resumé (kan læses i den trykte bog)

21.8 Opgaver

  1. Tag TegnbareObjekter.java fra kapitel 12, og lav (i init()-metoden) fem forskellige objekter, der implementerer Tegnbar-interfacet (brug anonyme klasser). De fem objekter skal have forskellig måde at reagere på tegn() og sætPosition().

  2. Læs afsnit 12.3.1 eller dokumentationen til Comparator-interfacet i pakken java.util. Lav tre Comparator-objekter (vha. anonyme klasser), der sorterer strenge hhv. alfabetisk, omvendt alfabetisk og alfabetisk efter andet tegn i strengene. Lav en liste (Vector) med ti strenge, og test din sortering med Collections.sort(liste, Comparator-objekt).

21.9 Avanceret (kan læses i den trykte bog)

21.9.1 public, protected og private på en indre klasse (kan læses i den trykte bog)

21.9.2 static på en indre klasse (kan læses i den trykte bog)

1Forskellen på en ren abstrakt klasse og et interface er, at klassen godt kan indeholde variabler, mens et interface kun har konstanter. Derudover kan man implementere flere interfaces, men kun arve fra én klasse.


javabog.dk  |  << forrige  |  indhold  |  næste >>  |  eksempler
Jacob Nordfalk - Objektorienteret programmering i Java - http://javabog.dk - Licens og kopiering under Åben Dokumentlicens (ÅDL)