Schnittstellenobjekte
Übergabe von Objektreferenzen
Die Klasse »Main« in dem folgenden Programm implementiert einen Aktionsempfänger für ein Textfeld »Main.this.text«.
Durch Auswertung von »Main.this.text.addActionListener( this )« erhält das Textfeld eine Referenz auf das Objekt der Klasse »Main« und kann so bei Bedarf die Methode »actionPerformed« aufrufen.
Main.java
public final class Main implements
java.lang.Runnable,
java.awt.event.ActionListener
{public void actionPerformed( final java.awt.event.ActionEvent event )
{ Main.this.text.setText( Main.this.text.getText().toUpperCase() ); }public void run()
{ Main.this.text.addActionListener( this );
final javax.swing.JFrame frame
= new javax.swing.JFrame( "Text in Großbuchstaben wandeln." );
frame.add( Main.this.text, java.awt.BorderLayout.CENTER );
frame.setDefaultCloseOperation( javax.swing.JFrame.EXIT_ON_CLOSE );
frame.pack(); frame.setVisible( true ); }public final javax.swing.JTextField text
= new javax.swing.JTextField( "Müßiggang" );public static void main( final java.lang.String[] args )
{ javax.swing.SwingUtilities.invokeLater( new Main() ); }}transcript
- Aussprachehinweis
- close cloz
- Oberfläche
.----------------.
| Text in Gro... |
|----------------| Eingabetaste .----------------.
||Muessiggang ----------------------->| MUESSIGGANG |
'----------------' '----------------'
Verdrahtung
Wenn einem Objekte eine Referenz auf ein anderes Objekt mitgeteilt wird, welche das erste Objekt sich merkt, um dem anderen Objekt später Nachrichten senden zu können, so sagen wir auch, daß wir die beiden Objekte miteinander verdrahtet hätten. Die Anweisung »Main.this.text.addActionListener( this );« ist ein Beispiel für eine solche Verdrahtung.
- Verdrahtung mit »Main.this.text.addActionListener( this );«
.----------.
| Textfeld |
'----------'
||
Main-Objekt |V"this"
.----------------|--------------------.
| v |
| actionPerformed |
| |
'-------------------------------------'
Grenzen der oben gezeigten Vorgehensweise
Die oben verwendete Vorgehensweise stößt jedoch an ihre Grenzen, sobald von mehreren verschiedenen Oberflächenelementen verschiedene Methoden aufgerufen werden sollen. Da die Schnittstelle »java.awt.event.ActionListener« die Signatur fest »actionPerformed(java.awt.event.ActionEvent)« vorgibt, kann dazu nicht einfach eine weitere Signatur, wie beispielsweise »actionPerformed1(java.awt.event.ActionEvent)«, implementiert werden, die dann von einem anderen Oberflächenelement aufgerufen werden soll. Ein anderes Textfeld müßte notgedrungen dieselbe Methode aufrufen.
- gewünschte, aber nicht mögliche Verdrahtung
.----------. .-----------.
| Textfeld | | Textfeld1 |
'----------' '-----------'
| |
Main-Objekt | |
.----------------|---------------|--------------.
| v v |
| actionPerformed actionPerformed1 |
| |
'-----------------------------------------------'- mögliche, aber nicht gewünschte Verdrahtung
.----------. .-----------.
| Textfeld | | Textfeld1 |
'----------' '-----------'
| |
Main-Objekt | .-----------'
.----------------|-|------------------.
| v v |
| actionPerformed |
| |
'-------------------------------------'
Verwendung von Schnittstellenobjekten
Es ist aber möglich, verschiedene Objekte zur Implementation einer Schnittstelle anzulegen und an verschiedene Oberflächenelemente zu übergeben, um so die Signatur »actionPerformed(java.awt.event.ActionEvent)« doch mehrfach implementieren zu können.
Wenn es beispielsweise gewünscht wird, zwei Methoden »actionPerformed(java.awt.event.ActionEvent)« anzulegen, dann ist dies möglich, indem beide in jeweils ein eigenes Objekt eingepackt werden. Dann kann jedes Textfeld eine Referenz auf ein anderes Objekt enthalten, wobei jedes Objekt eine Methode mit der richtigen Signatur »actionPerformed(java.awt.event.ActionEvent)« enthält.
- Verdrahtung mit Schnittstellenobjekten
.----------. .-----------.
| Textfeld | | Textfeld1 |
'----------' '-----------'
| |
Main-Objekt | |
.----------------|--------------------------|-------------------.
| v v |
| .-----------------. .-----------------. |
| | actionPerformed | | actionPerformed | |
| '-----------------' '-----------------' |
| erstes zweites |
| Schnittstellenobjekt Schnittstellenobjekt |
| |
'---------------------------------------------------------------'
Bevor wir diese Technik mit zwei Textfeldern zeigen, wollen wir zuerst das obenstehende Programm ohne Änderung seiner Funktionalität auf ein Schnittstellenobjekt umstellen (Refaktor), da so der Unterschied zwischen der bisherigen und der neuen Vorgehensweise am besten erkannt werden kann.
Main.java
public final class Main implements
java.lang.Runnable
{ public void run()
{Main.this.text.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ Main.this.text.setText( Main.this.text.getText().toUpperCase() ); }});final javax.swing.JFrame frame
= new javax.swing.JFrame( "Text in Großbuchstaben wandeln." );
frame.add( Main.this.text, java.awt.BorderLayout.CENTER );
frame.setDefaultCloseOperation( javax.swing.JFrame.EXIT_ON_CLOSE );
frame.pack(); frame.setVisible( true ); }
public final javax.swing.JTextField text
= new javax.swing.JTextField( "Beispiel" );
public static void main( final java.lang.String[] args )
{ javax.swing.SwingUtilities.invokeLater( new Main() ); }}
Der Exemplarerzeugungsausdruck erzeugt ein Objekt, welches die Schnittstelle »java.awt.event.ActionListener« mit Hilfe der in den geschweiften Klammern angegebenen Methode der Signatur »actionPerformed(java.awt.event.ActionEvent)« implementiert. Neu daran ist die Verwendung einer Schnittstelle und der geschweiften Klammern. Die bisher verwendeten Exemplarerzeugungsausdrücke enthielten hingegen immer Namen einer Klasse und keine geschweiften Klammern.
Eine Referenz auf das erzeugte java.awt.event.ActionListener -Objekt wird der Methode »Main.this.text.addActionListener« übergeben. Dies bezeichnen wir als Verdrahtung. Damit kann das Textfeld jenes Objekt dann jenes java.awt.event.ActionListener -Objekt gegebenenfalls benachrichtigen.
In den Methoden eines solchen Exemplarerzeugungsausdrucks kann auf die Felder und Methoden der umgebenden Klasse zugegriffen werden. Daher kann in der Methode »actionPerformed« auf das Feld »Main.this.text« zugegriffen werden.
Nun ist es leicht, ein Programm mit zwei Textfeldern zu schreiben, von denen jedes mit einer eigenen actionPerformed -Methode verdrahtet wird. Dazu verdoppeln wir einfach alle Programmteile, welche direkt mit dem Textfeld zusammenhängen und passen die neuen Kopien etwas an. Damit ein Unterschied zwischen beiden Textfeldern erkennbar wird, soll das eine Textfeld in Großbuchstaben und das andere in Kleinbuchstaben umwandeln.
Main.java
public final class Main implements
java.lang.Runnable
{ public void run()
{Main.this.text0.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ Main.this.text0.setText( Main.this.text0.getText().toUpperCase() ); }});Main.this.text1.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ Main.this.text1.setText( Main.this.text1.getText().toLowerCase() ); }});final javax.swing.JFrame frame
= new javax.swing.JFrame( "Text wandeln." );frame.add( Main.this.text0, java.awt.BorderLayout.NORTH );
frame.add( Main.this.text1, java.awt.BorderLayout.SOUTH );frame.setDefaultCloseOperation( javax.swing.JFrame.EXIT_ON_CLOSE );
frame.pack(); frame.setVisible( true ); }public final javax.swing.JTextField text0 = new javax.swing.JTextField( "DdEeFf" );
public final javax.swing.JTextField text1 = new javax.swing.JTextField( "GgHhIi" );public static void main( final java.lang.String[] args )
{ javax.swing.SwingUtilities.invokeLater( new Main() ); }}- Oberfläche
.----------------.
| Text wandeln |
|----------------| Eingabetaste .---------------.
||DdEeFf ------------------------->| DDEEFF |
||--------------|| Eingabetaste '---------------' .---------------.
||GgHhIi ------------------------------------------------>| hghhii |
'----------------' '---------------'- Verdrahtung mit Schnittstellenobjekten
.-----------. .-----------.
| Textfeld0 | | Textfeld1 |
'-----------' '-----------'
| |
Main-Objekt | |
.----------------|--------------------------|-------------------.
| v v |
| .-----------------. .-----------------. |
| | actionPerformed | | actionPerformed | |
| '-----------------' '-----------------' |
| erstes zweites |
| Schnittstellenobjekt Schnittstellenobjekt |
| |
'---------------------------------------------------------------'
Anonyme innere Klassen
Obwohl es so scheinen könnte, als sei die Klasse eines mit »new java.awt.event.ActionListener()…« angelegten Objekts eine Schnittstelle (hier: die Schnittstelle »java.awt.event.ActionListener«), so handelt es sich doch tatsächlich um ein Klasse. Diese Klasse implementiert jene Schnittstelle und sie wird beim Übersetzen automatisch erzeugt. Da ihr im Programm kein bestimmter Name gegeben wird, wird sie auch als eine anonyme Klasse bezeichnet. Weil der Exemplarerzeugungsaudruck, der zum Anlagen der Klasse führt, in einer Klassendeklaration enthalten ist, wird sie auch als eine innere Klasse bezeichnet. Insgesamt sagt man also, daß das oben mit »new java.awt.event.ActionListener()…« angelegte Objekt eine anonyme innere Klasse als Typ habe.
Die Bedeutung der Verdrahtung
Der Sinn der folgenden Anwesisung besteht darin, dem Textfeld zu sagen, was es bei einem bestimmten Ereignis tun soll. Dies wird darin durch die Anweisung »Main.this.text0.setText( Main.this.text0.getText().toUpperCase() );« festgelegt. Inhaltlich geht es also eigentlich darum, dem Textfeld eine Anweisung zu übergeben, die es beim Eintreten eines bestimmten Ereignisses ausführen soll.
- Anweisung
Main.this.text0.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ Main.this.text0.setText( Main.this.text0.getText().toUpperCase() ); }});
Aus formalen Gründen ist es aber nicht möglich einfach nur jene Anweisung zu übergeben, vielmehr muß die Anweisung zunächst in eine Methodendeklaration verpackt werden, die geschieht durch die Deklaration der Methode »actionPerformed«. Diese Methode muß dann wiederum in ein Objekt gepackt werden, das eine Klasse einer bestimmten Schnittstelle haben muß. Dadurch wird die Formulierung etwas komplizierter. Aber inhaltlich geht es darum, daß das Textfeld, weiß was es bei einem bestimmten Ereignis tun soll.
Das qualifizierte »this«
Das unqualifizierte »this« im Rumpf einer Methode bezieht sich immer auf das Objekt, in welchem jene Methode direkt enthalten ist. Im Falle der folgenden Beispielanweisung würde sich das »this« also auf das darin mit »new« angelegte Objekt der damit stillschweigend angelegten anonymen inneren Klasse beziehen. Dieses Objekt hat aber gar kein Feld »text0«, so daß die Anweisung fehlerhaft ist.
- Fehlerhafte Anweisung
Main.this.text0.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ this.text0.setText( Main.this.text0.getText().toUpperCase() ); }});
Um auszudrücken, daß das Objekt der Klasse vom Typ »Main« gemeint ist, für welches die run-Methode aufgerufen wurde, muß dem Schlüsselwort »this« der Name der Klasse jener run-Methode als eine Qualifikation vorangestellt werden.
- Korrekte Anweisung
Main.this.text0.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ Main.this.text0.setText( Main.this.text0.getText().toUpperCase() ); }});
Oft ist es aber auch möglich, das Schlüsselwort »this« ganz wegzulassen. In diesem Fall wählt die Java -Implementation das nächstliegende Feld mit diesem Namen.
- Korrekte Anweisung
Main.this.text0.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ text0.setText( Main.this.text0.getText().toUpperCase() ); }});
Das folgende Programmbeispiel verdeutlicht das eben Gesagte.
Main.java
public final class Main
{ int i = 4;public void run()
{ new java.lang.Runnable()
{ int i = 9;
public void run()
{ java.lang.System.out.println( i );
java.lang.System.out.println( this.i );
java.lang.System.out.println( Main.this.i ); }}.run(); }public static void main( final java.lang.String[] args )
{ new Main().run(); }}java.lang.System.out
9
9
4
Man beachte auch, wie an zwei Stellen im Programm eine run-Methode direkt nach der Exemplarerzeugung mit ».run()« aufgerufen wird.
Begriffe (JLS7, 8.1.3)
Eine anonyme Klasse (anonymous class ) ist auch eine innere Klasse (inner class ). Eine innere Klasse ist auch eine verschachtelte Klasse (nested class ).
Beispiele für Arten innerer und verschachtelter Klassen, die keine anonymen Klassen sind, sollen später folgen.
nested class
^
| non-static
|
inner class
^
| new ...
|
anonymous class
Namen einer anonymen Klasse anzeigen
Anonyme Klassen sind nur insofern namenslos als daß sie keinen explizit im Quelltext festgelegten Namen haben. Das folgende Programm zeigt, daß sie aber durchaus einen Namen haben, der zur Laufzeit ermittelt werden kann.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.Runnable object
= new java.lang.Runnable()
{ public void run() {} };
java.lang.System.out.println( object.getClass().getName() ); }}java.lang.System.out
Main$1
Lokale Variablen für die Textfelder
Beim ersten Programm dieser Lektion mußte das Textfeld in einer Exemplarvariablen referenziert werden, weil es von mehreren Methoden der Klasse erreichbar sein sollte. Befinden sich aber die Empfänger alle innerhalb der Methode »run«, so können auch die Textfelder über lokale Variablen dieser Methode referenziert werden, die dann aber mit »final« gekennzeichnet werden müssen, damit sie in den Innenklassen verwendet werden dürfen.
Main.java
public final class Main implements
java.lang.Runnable
{ public void run()
{final javax.swing.JTextField text0 = new javax.swing.JTextField( "DdEeFf1" );
final javax.swing.JTextField text1 = new javax.swing.JTextField( "GgHhIi1" );text0.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ text0.setText( text0.getText().toUpperCase() ); }});text1.addActionListener
( new java.awt.event.ActionListener()
{ public void actionPerformed( final java.awt.event.ActionEvent event )
{ text1.setText( text1.getText().toLowerCase() ); }});final javax.swing.JFrame frame
= new javax.swing.JFrame( "Text wandeln." );frame.add( text0, java.awt.BorderLayout.NORTH );
frame.add( text1, java.awt.BorderLayout.SOUTH );frame.setDefaultCloseOperation( javax.swing.JFrame.EXIT_ON_CLOSE );
frame.pack(); frame.setVisible( true ); }public static void main( final java.lang.String[] args )
{ javax.swing.SwingUtilities.invokeLater( new Main() ); }}transcript
Methodenliterale
Man kann das voranstehende Programm mit Methodenliteralen einfacher schreiben als mit anonymen inneren Klassen, aber dies geht nur bei Schnittstellen, die lediglich eine Methode enthalten. Bei anderen Schnittstellen müssen weiterhin andere Techniken eingesetzt werden.
Main.java
public final class Main implements
java.lang.Runnable
{ public void run()
{final javax.swing.JTextField text0 = new javax.swing.JTextField( "DdEeFf2" );
final javax.swing.JTextField text1 = new javax.swing.JTextField( "GgHhIi2" );text0.addActionListener
( event -> text0.setText( text0.getText().toUpperCase() ));text1.addActionListener
( event -> text1.setText( text1.getText().toLowerCase() ));final javax.swing.JFrame frame
= new javax.swing.JFrame( "Text wandeln." );frame.add( text0, java.awt.BorderLayout.NORTH );
frame.add( text1, java.awt.BorderLayout.SOUTH );frame.setDefaultCloseOperation( javax.swing.JFrame.EXIT_ON_CLOSE );
frame.pack(); frame.setVisible( true ); }public static void main( final java.lang.String[] args )
{ javax.swing.SwingUtilities.invokeLater( new Main() ); }}transcript
Umrechnen
Schreiben Sie ein GUI-Programm mit zwei untereinander angeordneten Textfeldern.
- Wenn in dem oberen Textfeld ein Text eingegeben wird, so soll dieser als Geschwindigkeit in km/h interpretiert werden; in dem unteren Textfelde soll alsdann die gleiche Geschwindigkeit in m/s angezeigt werden.
- Wenn in dem unteren Textfeld ein Text eingegeben wird, so soll dieser als Geschwindigkeit in m/s interpretiert werden; in dem oberen Textfelde soll alsdann die gleiche Geschwindigkeit in km/h angezeigt werden.