Indhold:
Nøgleordene abstract og final
Indre klasser, herunder lokale klasser og anonyme klasser
Brug af indre klasser og anonyme klasser til at lytte efter hændelser
Brug af anonyme klasser til at oprette bl.a. tråde i en håndevending
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.
Noget der er erklæret abstract er ikke implementeret og skal defineres i en nedarvning. Det skrives i kursiv i UML-notationen.
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.
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.
Noget, der er erklæret final, kan ikke ændres. Både variabler, metoder og klasser kan erklæres 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 objektEn 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.
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
{
}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:
(Almindelige) indre klasser
Lokale klasser
Anonyme klasser
Der er flere fordele ved at benytte indre klasser (visse undtagelser bliver forklaret sidst i afsnittet):
Den indre klasse er knyttet til den ydre
klasse og kan kun anvendes i denne.
Man behøver derfor
ikke bekymre sig for sammenhængen med resten af programmet.
Det kan give et mere overskueligt program at lægge klasser,
der alligevel har meget stærk binding (er meget afhængige
af hinanden) inden i hinanden (indkapsling).
Den indre klasse kan arbejde direkte på
den ydre klasses variabler og metoder, også de private.
Det
skyldes, at et objekt af en indre klasse altid hører til et
objekt af den ydre klasse.
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.
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).
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
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.
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
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);
}
}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.
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().
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).
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.