21 Indre klasser

Indhold:



Kapitlet 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 11, Interfaces, afsnittet om final i kapitel 20, Avancerede klasser (og 12, Hændelser og 16, Flertrådet programmering for at forstå nogle af eksemplerne).



Indre klasser er mindre "hjælpeklasser" defineret inde i en anden klasse. Dette kapitel 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 kapitlet):

21.1 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.1.1 Eksempel - Linietegning

Man benytter ofte indre klasser i forbindelse med at lytte efter hændelser. Her kommer Linietegning-eksemplet fra kapitel 12 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 er synlig for den indre klasse
  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.

21.2 Lokale 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 args[]){
    YdreKlasseMedLokalKlasse ydre = new YdreKlasseMedLokalKlasse();
    ydre.prøvLokaltObjekt(2);
  }
}

4
3
2
1


21.3 Anonyme klasser

En anonym klasse er en klasse uden navn, som der oprettes et objekt ud fra der, hvor den defineres.


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


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.


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.

21.3.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ører, 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.3.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.1.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.3.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 løber de i en anden løkke 1.000.000 gange 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.4 Resumé (fjernet)

Dette afsnit findes i den trykte bog


21.5 Opgaver

  1. Tag TegnbareObjekter.java fra kapitel 11 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. Kig på javadokumentationen til interfacet Comparator 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.6 Avanceret (fjernet)

Dette afsnit findes i den trykte bog



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