next up previous contents index
Next: Sichtbarkeitsspezifikationen Up: Klassen und Objekte Previous: Objektzerstörung

Innere Klassen

 

In Java 1.0:

In Java 1.1 zusätzlich:  

Ziel: Definition von Hilfsklassen möglichst nahe an der Stelle, wo sie gebraucht werden.

Geschachtelte und innere Klassen sind wesentlich motiviert durch das neue Ereignismodell im AWT von Java1.1.    

Geschachtelte ``top-level'' Klassen und Interfaces

 

Klassen oder Interfaces, die static deklariert sind und in andere ``top-level''-Klassen oder Interfaces eingeschachtelt sind.

Interfaces sind immer static, d.h. für Interfaces ist nur die ``top-level''-Variante möglich.

Geschachtelte ``top-level'' Klassen und Interfaces verhalten sich wie andere Paket-Elemente auf der äußersten Ebene, außer daß ihrem Namen der Name der umgebenden Klasse vorangestellt wird.

public class LinkedList
{ // interface verkettungselement
  public static interface Linkable
  { public Linkable getNext();
    public void setNext(Linkable n);
  }
  // kopf der liste:
  Linkable head;
  // einfügen:
  public void insert(Linkable n)
  { n.setNext(head);
    head = n;
  }
  // kopfzugriff:
  public Linkable getHead()
  { return head;
  }
}

Benutzung:

a) Definition der Verkettungselemente:

class LinkableInteger 
implements LinkedList.Linkable
{ 
  int i;
  LinkedList.Linkable Next;
  // konstruktor:
  LinkableInteger(int i)
  { this.i = i;
  }
  // versprochene Methoden:
  public LinkedList.Linkable getNext()
  { return Next;
  }
  public void setNext(LinkedList.Linkable n)
  { Next = n;
  }
  // für die ausgabe: String-Umwandlung
  // überschreibe toString aus Object
  public String toString()
  { return i + "";
  }
}

b) Testlauf:

public class LiMain
{
  public static void main (String[] argv)
    {
      LinkedList li = new LinkedList();
      li.insert(new LinkableInteger(16));
      li.insert(new LinkableInteger(6));
      li.insert(new LinkableInteger(-4));
      
      LinkedList.Linkable l;
      for (l = li.getHead(); 
           l != null; 
           l = l.getNext())
        System.out.println(l);
    }
}

c) Ergebnis:

-4
6
16

Elementklassen

    

Im Gegensatz zu den static-deklarierten geschachtelten ``top-level''-Klassen, die mehr oder weniger nur zur Strukturierung dienen, sind Elementklassen echte innere Klassen.

tex2html_wrap2156

Damit kann das Objekt der Elementklasse implizit auf die Instanzvariablen der umgebenden Klasse zugreifen (auch auf private).

Die umgebende Klasse hat Zugriffrechte auf alle Felder aller ihrer Elementklassen. Zwei Elementklassen der selben umgebenden Klasse haben Zugriffrechte auf die Elemente des jeweilig anderen.

Elementklassen dürfen keine statischen Elemente (Attribute, Methoden, Klassen, Interfaces) besitzen. Gilt auch für lokale Klassen (Seite gif) und anonyme Klassen (Seite gif).

Beispielprojekt: Einbau eines Iterators in eine Buchstabenmenge

Beschreibung der Buchstabenmenge:


tabular754

Ziel: ein Iterator für charset.

Typisches Verwendungsmuster für einen solchen Iterator:

 

   charset cs = new charset(somestring);
   Enumeration e = cs.characters();
   while (e.hasMoreElements())
     machwasmit(e.nextElement());

(Anderes Iteratorbeispiel auf Seite gif).

a) Originalversion der Buchstabenmenge:

import java.util.BitSet;

class charset
{ // Buchstabenmenge:
  private BitSet chars = new BitSet();
  // Konstruktor:
  public charset(String str)
  { for (int i = 0; i < str.length(); i++)
    chars.set(str.charAt(i));
  }
  // Umwandlung in String:
  public String toString()
  { String result="[";
    int size = chars.size();
    for (int i = 0; i < size; i++)
        if (chars.get(i))
          result += (char)i;
    return result +"]";
  }
}

b) Testen der Originalversion der Buchstabenmenge:

public class charsetMain
{ // hauptprogramm
  public static void main(String argv[])
    { // erstes kommandozeilenargument
      charset cs = new charset(argv[0]);
      System.out.println(cs + "");
    }
}

c) Testlauf:

Eingabe:

   java charsetMain JavaProgrammierung

Ausgabe:

   [JPaegimnoruv]

c) Einbau einer inneren Iteratorklasse

 

import java.util.*
// für BitSet, Enumeration
// und NoSuchElementException

class charset
{ // Buchstabenmenge:
  private BitSet chars = new BitSet();

  // Konstruktor:
  public charset(String str)
  { for (int i = 0; i < str.length(); i++)
    chars.set(str.charAt(i));
  }

  // innere Elementklasse:
  private class cs_iterator 
  implements Enumeration
  { private int pos;
    private int setsize = chars.size();
    
    // versprochenes "hasMoreElements":
    public boolean hasMoreElements()
    { while (pos < setsize && 
             !chars.get(pos))
        pos++;
      return pos < setsize;
    }

    // versprochenes "nextElement":
    public Object nextElement()
    throws NoSuchElementException
    { if (hasMoreElements())
        return new Character((char) pos++);
      else
        throw new NoSuchElementException();
    }
  }
  
  // liefert einen Iterator:
  public Enumeration characters()
    { return new cs_iterator();
    }
}

d) Testen des neuen charset mit Iterator:

import java.util.*;

public class it_charsetMain
{ public static void main(String argv[])
  { // erstes kommandozeilenargument
    charset cs = new charset(argv[0]);

    Enumeration e = cs.characters();
    while (e.hasMoreElements())
      System.out.println(e.nextElement());
  }
}
e) Testlauf:

Eingabe:

   java it_charsetMain Java

Ausgabe:

   J
   a
   v

Syntax-Erweiterungen und neue Regeln für Elementklassen

a) Wie sprechen ``innere Objekte'' das ihnen zugeordnete Objekt der umgebenden Klasse an?

Im charset-Beispiel:

tex2html_wrap2158

pos und setsize sind Attribute der Selbstreferenz this  .

Ihr expliziter Name wäre also this.pos, bzw. this.setsize.  

Um chars explizit anzusprechen, schreiben wir:

    charset.this.chars

b) Tiefere Schachtelung von Elementklassen?

Beliebig möglich. Allerdings darf keine der eingeschachtelten Klassen so heißen wie eine der sie umgebenden!

Das bedeutet:

Obige neue Syntax:

    classname.this

ist völlig ausreichend:  

public class A
{ public string name = "a";
  public class B
  { public string name = "b";
    public class C
    { public string name = "c";
      public void print()
      { System.out.println(name);
        System.out.println(this.name);
        System.out.println(C.this.name);
        System.out.println(B.this.name);
        System.out.println(A.this.name);
      }
    }
  }
}

c) Welches ist das umgebende Objekt?

In unserem Beispiel haben wir den Default-Fall benutzt:

  public Enumeration characters()
    { return new cs_iterator();
    }

Hier: Keine Angabe des umgebenden Objektes für die neue Instanz der inneren Klasse. Daher wird die Selbstreferenz this als umgebendes Objekt angenommen:

Äquivalent zu obigem ist die neue explizite Schreibweise:

  public Enumeration characters()
    { return this.new cs_iterator();
    }

Für andere Fälle brauchen wir die neue allgemeine Syntax:

 

   containing_instance.new inner_class(...)

wobei containing_instance eine Instanz der umfassenden Klasse von inner_class ist.

d) Innere Objekte außerhalb der umgebenden Klasse erzeugen?

Geht, wenn die innere Klasse nicht wie in unserem charset-Beispiel privat ist.

Also ändern:

  // innere Elementklasse:
  public class cs_iterator 
  implements Enumeration
  { private int pos;
    ....
  }

Dann funktioniert auch folgendes Hauptprogramm:

  public static void main(String argv[])
    { // erstes kommandozeilenargument
      charset cs = new charset(argv[0]);

      Enumeration e = cs.new cs_iterator();
      while (e.hasMoreElements())
        System.out.println(e.nextElement());
    }

Noch ein Beispiel für die externe Erzeugung innerer Objekte. Klassenhierarchie von Seite gif:

A a = new A;           // A Instanz
A.B b = a.new B();     // B Instanz in a
A.B.C c = b.new C();   // C Instanz in b
c.print();             // Methode aus C

e) Kann man von inneren Klassen erben?

Ja (``strange as it may seem'').

Damit gibt es aber auch eine zweite Art, wie Objekte innerer Klassen erzeugt werden können: mit dem Oberklassen-Konstruktoraufruf super.  

Zusätzlich zur Syntax

   containing_instance.new inner_class(...)

brauchen wir also  

   containing_instance.super(...)

f) Ändern sich durch innere Klassen die Regeln für die Namensbindung?  

Ja. Wir haben jetzt drei Hierarchien in Java-Programmen:

1.
die klassische Blockschachtelung im Anweisungsteil
2.
die Vererbungshierarchie
3.
die Klassenschachtelung

Aufgabe der Namensanalyse: Finde zu jedem Auftreten eines Bezeichners die laut Sprachdefinition dazugehörige Definition.

Eine Methode der Klasse K verwendet in einer Anweisung den Bezeicher x.

Ist x

Vererbungshierarchie und Klassenschachtelung im Bild:

tex2html_wrap2160

Die beiden Hierarchien sind völlig unabhängig voneinander.

Es ist wichtig, sie nicht zu verwechseln!

Konfliktfall zwischen umfassender Klasse und Oberklasse

In Java 1.1. gelöst zugunsten der Oberklasse, d.h.

der Bezeichner x der Oberklasse verdeckt den der umfassenden Klasse.

Zusätzlich zu dieser Regel erfordert Java im Konfliktfall die explizite Benennung des Bezeichners, d.h.  

Lokale Klassen

      Lokale Klassen sind innere Klassen, die nicht auf oberer Ebene in andere Klassen eingeschachtelt sind, sondern lokal in Anweisungsblöcken von Methoden, statischen Initialisierern oder Instanz-Initialisierern.    

Sehr ähnlich zu Elementklassen (siehe Seite gif) mit folgenden wichtigen Unterschieden:

Anonyme Klassen

      Anonyme Klassen werden wie lokale Klassen (siehe Seite gif) z.B. innerhalb von Anweisungsblöcken definiert.

Anonyme Klassen haben keinen Namen. Sie entstehen immer zusammen mit einem Objekt.

Ihr Zweck: ``Einweg-Klassen''    

import java.io.*;
// gibt die Namen aller .java-Dateien im 
// angegebenen Verzeichnis aus
public class dirlist
{public static void main(String argv[])
 { File f = new File(argv[0]);
   String[] list = 
     f.list (new FilenameFilter() 
     { public boolean accept(File f, 
                             String s)
       { return s.endsWith(".java");
       }
     }
             ); // Formatierung schwierig!
   for (int i = 0; i < list.length; i++)
     System.out.println(list[i]);
 }
}

Die Syntax für anonyme Klassen ist

   new-expression class-body

Für die anonyme Klasse kann man keine extends- oder implements-Klauseln angeben.

Daher:

In unserem Beispiel implementiert die anonyme Klasse ein Interface:

   f.list(new FilenameFilter() 
          { ...
          }
         ):

Da anonyme Klassen keinen Namen haben, können sie auch keine Konstruktoren haben. Deshalb hat Java 1.1   die   Instanzinitialisierungsblöcke eingeführt (Seite gif).


next up previous contents index
Next: Sichtbarkeitsspezifikationen Up: Klassen und Objekte Previous: Objektzerstörung

Peter Pfahler, 1997