Programmieren
ProgrammierenDieser Abschnitt behandelt fortgeschrittene Javakonzepte:
- 10420 views
Oberflächenprogrammierung mit Swing
Oberflächenprogrammierung mit SwingGrafische Benutzeroberflächen in Java
- GUI: englische Abkürzung für "Graphical User Interface" oft auch mit UI (User Interface) abgekürzt
- Ziel graphischer Benutzeroberflächen:
- Intuitiv bedienbare Benutzerschnittstellen für ungeübte Benutzer
- Optionen zu effizienten Benutzung von erfahrenen Benutzern (Kommandos auf Befehlstasten, Tabulatoren zum Bewegen zwischen Eingabefeldern etc.)
- Vorteil von Java-GUIs
- Plattformunabhängigkeit (write once, run anywhere)
- 7454 views
Entwicklungsgeschichte der grafischen Benutzerschnittstellen in Java (JRE)
Entwicklungsgeschichte der grafischen Benutzerschnittstellen in Java (JRE)Swing ist das Ergebnis einer Evolution der grafischen Benutzerschnittstellen die seit 1996 immer weiterentwickelt wurden. Der geschichtliche Überblick ist hilfreich beim Verstehen der recht komplexen Paketstrukturen von Swing und AWT.
Java 1.0: Another Windowing Toolkit (AWT)
- Jahr 1996
- Paket: java.awt.*
Die ursprüngliche Grafikbibliothek AWT sollte die folgenden Anforderungen erfüllen
- einfach zu verstehen und zu Programmieren ("Volks GUI")
- weniger komplex als das damals populäre X11, Motif von Unix-Workstations
- unabhängig von Windows (Rechte, Lizensen!)
- Kleine Teilmenge der wichtigsten GUI Elemente
- geplanter Einsatz Browser-Applets und TV-Settop Boxen (Auf Neudeutsch "Kabeltuner")
- Internet Browser zu mehr Interaktion und mehr Intelligenz zu verhelfen
- leicht auf unterschiedlichen Betriebssytemen zu implementieren
AWT hat daher die folgenden Eigenschaften
- nur wenige GUI Komponenten
- HeavyWeight Implementierung (direkte Abbildung auf Betriebssystemkomponenten)
- Threadsicher
- keine Model-View-Controller Architektur
Java 1.1: AWT + Java Foundation Classes (JFC)
- Jahr 1997
- Paket: javax.swing.*
Das AWT musste um eine objektorientierte Ereignissteuerung erweitert werden da das Implementieren von GUIs mit vielen Komponenten sehr unübersichtich war
Gleichzeitig wurde ein vollkommenes Neudesign mit einem optionalen Package (javax Pakete) mit dem Projektnamen Swing (engl. Schaukel) vorgestellt. Der offizielle Name war "Java Foundation Classes".
Da JFC zum Zugriff auf die Betriebssytemkomponenten AWT benutzen muss, ist es von AWT abhängig. JFC ist eine "light weight" Implementierung und daher stärker vom gegebenen Betriebssytemen entkoppelt.
Java 1.2: JFC/Swing Standard GUI
JFC (Swing) muss nicht mehr separat geladen werden und ist Bestandteil des Standard JREs. Es verdrängt AWT mehr und mehr. Seine wichtigsten Eigenschaften sind
Swing wurde seit JRE 1.2 kontinuierlich weiterentwickelt und erhielt in jeder neuen JRE Version zusätzliche Eigenschaften. Die Demoanwendung SwingSet2 ist eine gute Referenzanwendung in der sehr viele Komponenten verwendet werden. |
"Lightweight" versus "Heavyweight" Implementierungen
Man spricht von Heavyweight Komponenten wenn zu ihrer Implementierung die vom Betriebssytem zu Verfügung gestellten Komponenten verwendet werden.
In einer Heavyweight Implementierung wird z.Bsp. zur Anzeige einer Menüauswahliste direkt die Menüauswahlliste des Betriebssytem verwendet.
Man spricht von einer Lightweight Implementierung wenn eine Grafikbibliothek vom Betriebsystem nur einen Pixelbereich auf dem Bildschirm nutzt und dann die Komponenten selbst zeichnet (rendered). Bei einer Lightweigtimplemenierung muss die Anwendung zum Beispiel eine Menüauswahlliste selbst auf dem Bildschirm zeichnen und selbst darauf achten, welche Bereiche des Fenster überschrieben werden.
Vorteile | Nachteile | |
---|---|---|
Heavyweight |
|
|
Lightweight |
|
|
Anmerkung: Die historischen (vor 2005) Probleme von Swing sind in den aktuellen Javaversion nicht mehr vorhanden. Moderne Garbage-Kollektoren blockieren die Benutzerschnittstellen nicht mehr sichtbar. Die aktuelle 2D Bibliothek von Java zeichnet in der Regel ausreichend schnell und benutzt auf den gängigen Plattformen (Windows, Mac, Linux) die Betriebssystemoptimierungen für Fonts, 2D- und 3D-Operationen. JavaFX stellt auch die nötigen hardwareunterstützten Operationen für Bildtransformationen und Filme zur Verfügung.
- 6142 views
Implementieren von einfachen Swingkomponenten
Implementieren von einfachen SwingkomponentenSwing-Komponenten
Eine sehr anschauliche Übersicht über die existierenden Swing-Komponenten kann man auf der Seite des MIT finden. Hier gibt es auch Beispielcode. Bei Oracle kann man auch ein Tutorial mit Beispielcode finden.
Komponente (In Java Swing) |
---|
Komponenten sind grafische Bereiche die mit dem Benutzer interagieren können. |
Toplevel Container
Swing verfügt über drei "Top-level-Container" in denen Benutzeroberflächen implementiert werden können:
- JFrame: reguläre freistehende Programmfenster mit (optionaler) Menüleiste
- JDialog: Eine Dialog-Box für modale Dialoge wie z.Bsp. Fehlermeldungen
- JWindow: Ein freistehendes Fenster ohne Menüleisten, ohne Buttons zur Verwaltung des Fensters
Container (In Java Swing) |
---|
Ein Container ist eine Komponente die andere AWT Kompomenten enthalten kann. |
Fenster: Klasse JFrame
Fenster können mit der Klasse JFrame erzeugt werden. Das folgende Beispielprogramm erzeugt ein einfaches Fenster der Größe 300 Pixel x 100 Pixel mit einem Titel in der Kopfleiste:
package s2.swing; import javax.swing.JFrame; public class JFrameTest { public static void main(String[] args) { JFrame myFrame = new JFrame("Einfaches Fenster"); myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Beende Anwendung bei Schließen des Fensters myFrame.setSize(300,100); // Fenstergroesse 300x100 Pixel myFrame.setVisible(true); // Mache Fenster sichtbar } }
Erzeugt das folgende Fenster (MacOS):
Die Klasse JFrame erbt die Eigenschaften von Component, Container, Window und Frame aus dem AWT Paket:
Die vollständge Beschreibung findet man in der Java API Dokumentation.
Ein JFrame besteht aus mehreren Ebenen in denen unterschiedliche grafische Objekt positioniert sind:
Die Verwendung der unterschiedlichen Ebenen ist ein fortgeschrittenes Thema und wird im Rahmen des Kurses nicht behandelt. Es ist jedoch wichtig zu wissen, dass diese verschiedenen Ebenen existieren. Das Glass ane erlaubt beispielsweise eine unsichtbare Struktur über das GUI zu legen und damit zum Beispiel Mausereignisse abzufangen. Mehr Informationen sind z. Bsp. in der Oracle Dokumentation zu finden.
Buttons: Die Klasse JButton
Buttons können leicht mit der Klasse JButton erzeugt werden. Sie können mit der add() Methode dem Fenster hinzugefügt werden da das JFrame als Container in der Lage ist Komponenten aufzunehmen.
Beispiel: JButton in einem JFrame
package s2.swing; import javax.swing.JButton; import javax.swing.JFrame; public class JFrameButtonTest { public static void main(String[] args) { JFrame myFrame = new JFrame("Einfaches Fenster"); myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); myFrame.add(new JButton("Click mich ich bin ein JButton!")); myFrame.setSize(300,100); myFrame.setVisible(true); } }
Ergibt das folgende Fenster:
Swing bietet eine Reihe von Varianten von Buttons an auch in Menüleisten verwendet werden können. Die entsprechenden Klassen werden von der Klasse AbstractButton abgeleitet:
Textfelder: Klasse JTextComponent und JTextField
Swing erlaubt die Verwaltung von Eingabetextfeldern durch die Klasse JTextField. Die Klasse JTextField ist die einfachste Möglichkeit Text auszugeben und einzugeben wie im folgenden Beispiel zu sehen ist.
Beispiel: JTextfield in JFrame
package s2.swing; import javax.swing.JFrame; import javax.swing.JTextField; public class TextfeldTest { public static void main(String[] args) { JFrame frame = new JFrame("Ein Fenster mit Textfeld"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new JTextField("Editier mich. Ich bin ein JTexfield", 60)); frame.setSize(300, 100); frame.setVisible(true); } }
Das Programm erzeugt bei der Ausführung das folgende Fenster:
Beispiel: JTextField in einem Applet/Hauptprogramm
Swing bietet eine ganze Reihe von Möglichkeiten mit Texten umzugehen wie sich aus der Klassenhierarchie von JTtextComponent ergibt:
Beschriftungen: Klasse JLabel
Einfache, feste Texte für Beschriftungen werden in Swing mit der Klasse JLabel implementiert:
package s2.swing;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
*
* @author s@scalingbits.com
*/
public class JLabelTest {
public static void main(String[] args) {
JFrame f = new JFrame("Das Fenster zur Welt!");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JLabel("Hinweistext!"));
f.setSize(100, 80);
f.setVisible(true);
}
}
Das Programm erzeugt bei der Ausführung das folgende Fenster:
Container, Behälter für andere Komponenten: Klasse JPanel
Zum Anordnen und Positionieren von Komponenten wird die Klasse JPanel verwendet. Sie ist ein Behälter der andere Komponenten aus der JComponent-Hierarchie verwalten kann. Dies sind z.Bsp.:
- andere JPanels
- JButton
- JTextField
- JLabel etc.
JPanels haben zwei weitere wichtige Eigenschaften
- sie können nur ihren eigenen Hintergrund zeichnen
- sie benutzen Layoutmanager um die Positionierung der JComponenten durchzuführen
Jede Instanz eines JPanels hat einen eigenen Layoutmanager. Da typischerweise ein Layoutmanager nicht ausreicht, werden JPanels und deren Layoutmanager geschachtelt.
Beispiel: JTextfield in JFrame
import javax.swing.JFrame; import javax.swing.JTextField; /** * * @author s@scalingbits.com */ public class TextfeldTest { public static void main(String[] args) { JFrame frame = new JFrame("Ein Fenster mit Textfeld"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new JTextField("Editier mich. Ich bin ein JTextfield", 60)); frame.setSize(300, 100); frame.setVisible(true); } }
Das Programm erzeugt bei der Ausführung das folgende Fenster:
Beispiel: JTextField in einem Applet
Quellcode |
---|
package s2.swing; import java.awt.BorderLayout; /** public JPanelJApplet() { JButton myButton = new JButton("Click mich"); JLabel myLabel = new JLabel("Ich bin ein JLabel"); JPanel myPanel = new JPanel(); Container myPane = getContentPane(); @Override |
- 8575 views
Top Level Container
Moin Moin,
in ihrem Skript steht etwas von "3 Top-Level Container", aufgezählt sind aber 4.
Also wie viele sind es?
Anmerkung: Hinter "JApplet" steht ein Semikolon und kein Doppelpunkt. Und es fehlt ein "p" bei pain, bei "JFrame" ganz am Ende im letzten Absatz.
Gruß
- Log in to post comments
Gut beobachtet
Applets werden von keinen gängigen Browsern aus Sicherheitsgründen mehr unterstützt. Da man mit Applet nichts mehr machen kann bleiben noch drei. Habe das Skript angepasst.
- Log in to post comments
Layoutmanager
LayoutmanagerLayoutmanager erlauben die Anordnung von Komponenten in einem Container. Abhängig vom gewählten Manager werden die Komponenten in ihrer gewünschten oder in einer gestreckten, bzw. gestauchten Form angezeigt. Die Feinheiten der Layout-Manager werden hier nicht behandelt. Es werden auch nur die wichtigsten Layout-Manager vorgestellt. Die Swingdokumentation ist für die Entwicklung von GUIs unerlässlich.
Definition: Layout-Manager |
---|
Ein Layout-Manager ist ein Objekt, welches Methoden bereitstellt, um die grafische Repräsentation verschiedener Komponenten innerhalb eines Container-Objektes anzuordnen |
Wichtig: Eine absolute Positionierung von Komponenten ist ungünstig, da Fenster eine unterschiedliche Größe haben können.
Java biete eine Reihe von Layout-Managern. Im Folgenden wird eine Auswahl vorgestellt:
Flowlayout
Das FlowLayout ist das einfachste und Standardlayout. Es wird benutzt wenn kein anderer Layoutmanager angeben wird. In ihm werden die Komponenten in der Reihenfolge des Einfügens von links nach rechts eingefügt.
Strategie: Alle Komponenten werden in einer Reihe wie in einem Fließtext angeordnet. Reicht die gegebene Breite nicht erfolgt ein Umbruch mit einer neuen Zeile.
Dem JPanel jp wird im Folgenden kein expliziter LayoutManager mitgegeben um die 6 Knöpfe zu verwalten es wird der FlowLayoutmanager als Standardeinstellung verwendet:
package s2.swing; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; /** * Zeigt einen sehr einfachen FlowlayoutManager mit 5 Buttons * @author s@scalingbits.com */ public class FlowlayoutTest { /** * Hauptmethode * @param args Es werden keine Parameter ausgewertet */ public static void main(String[] args) { JFrame f = new JFrame("FlowLayout"); JPanel jp = new JPanel(); for (char c = 0; c <= 5; ++c) { // Stecke 6 Buttons in das Panel jp.add(new JButton("Button " + (char)('A'+c))); } f.add(jp); // Füge Panel zu Frame //Beende Anwendung beim Schliesen des Fensters f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.pack(); // Berechne Layout f.setVisible(true);// Zeige alles an } }
Ergibt ein Fenster mit dem folgenden Layout:
Bei mangelndem horizontalem Platz wird ein automatischer Umbruch in eine neue Zeile durchgeführt (siehe rechts). |
Eigenschaften Flowlayout
- Komponenten behalten ihre Wunschgröße
- Zeilenumbruch bei mangelndem horizontalen Platz
- aus den beiden ersten Eigenschaften ergibt sich, dass Komponenten eventuell nur teilweise angezeigt werden oder völlig verdeckt sein können!
Borderlayout
Der Borderlayoutmanager erlaubt das Gruppieren von Komponenten abhängig von der Richtung im Panel.
Strategie: Die zur Verfügung stehende Fläche wird in fünf Bereiche nach den Himmelsrichtungen aufgeteilt
- NORTH (Oben)
- SOUTH (Unten)
- EAST (Rechts)
- WEST (Links)
- CENTER (Mitte)
Der Centerbereich ist der priorisierte Bereich.
Das BorderLayout ist die Standardeinstellung für die Klassen Window und JFrame.
Im nächsten Beispiel wir je ein Knopf (Button) in einem der Bereiche angelegt:
}
Zur Bestimmung der Position werden Konstanten mit den englischen Namen der Himmelsrichtung verwendet (Bsp. BorderLayout.NORTH). Beim Borderlayout ist zu beachten, dass Komponenten die oben, bzw. unten angeordnet werden über die ganze Breite des Containers gezeichnet werden. Komponenten die links und rechts angeordnet werden sind jedoch immer unter, bzw. über den Komponenten die oben und unten angelegt urden.
Eigenschaften BorderlayoutSiehe Fenster rechts mit größerem Fensterbereich:
|
BoxLayout
Das BoxLayout erlaubt das Anordnen von Komponenten in Zeilen oder Spalten. Durch das Verschachteln von BoxLayoutmanagern kann man ähnliche Effekte wie beim GridLayout erzielen. Man hat jedoch die Möglichkeit einzelne Zeilen oder Spalten individuell zu konfigurieren.
Das BoxLayout versucht alle Komponenten mit ihrer bevorzugten Breite bei der horizontalen Darstellung zu positionieren. Die Höhe aller Komponenten wird hier versucht auf Die Wunschhöhe der höchsten Komponente wird benutzt um die Gesamthöhe zubestimmen.
Bei der vertikalen Darstellung wird entsprechend versucht die bevorzugte Höhe zu verwenden. Bei der vertikalen Darstellung versucht der Layoutmanager alle Komponenten horizontal so weit wie die breiteste Komponente zu strecken.
Komponenten können aneinander
- Linksbündig
- Rechtsbündig
- Zentriert
ausgerichtet werden
Das folgende Beispiel zeigt ein horizontales Boxlayout welches nach 2 Sekunden automatisch die Orientierung zu vertikal und dann wieder zurück triggert:
package s2.swing;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class BoxLayoutTest {
public static void main(String[] args) {
int wartezeit = 2000; // in Millisekunden
JFrame f = new JFrame("BoxLayout");
JPanel jp = new JPanel();
// Erzeuge ein horizontales und ein vertikales BoxLayout
BoxLayout horizontal = new BoxLayout(jp, BoxLayout.X_AXIS);
BoxLayout vertikal = new BoxLayout(jp, BoxLayout.Y_AXIS);
jp.setLayout(horizontal);
for (char c = 0; c < 4; ++c) {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true); // Warte 2 Sekunden try { Thread.sleep(wartezeit); // Wechsle fünfmal die Orientierung alle zwei Sekunden for (int i = 0; i < 5; i++) { jp.setLayout(vertikal); f.setTitle("BoxLayout - Vertikal"); f.pack(); Thread.sleep(wartezeit); jp.setLayout(horizontal); f.setTitle("BoxLayout - Horizontal"); f.pack(); Thread.sleep(wartezeit); } } catch (InterruptedException e) { // Mache nichts im Fall einer Ausnahme } } }
Ein horizontales Layout wird mit der Konstanten BoxLayout.X_AXIS beim Konfigurieren des Layoutmanagers erzeugt:
Ein vertikales Layout wird beim Erzeugen des Layoutmanagers mit Hilfe der Konstanten BoxLayout.Y_AXIS konfiguriert:
Eigenschaften Boxlayout
- Jede Komponente wird in ihrer Wunschgröße dargestellt
- Die Größe des Containers ergibt sich aus der größten Wunschhöhe und der größten Wunschbreite
Hinweis: Die zweizeilige JTextArea im Beispiel oben hat eine andere Wunschgröße als die Knöpfe
GridLayout
Der GridLayoutmanager erlaubt die Anordnung von Komponenten in einem rechteckigen Raster (Grid: engl. Raster, Gitter). Der Gridlayoutmanager versucht die Komponenten von links nach rechts und von oben nach unten in das Raster einzufügen.
Strategie: Alle Zellen haben eine einheitliche Größe
Wird für die Größe der Zeilen oder Spalten eine 0 angegeben erlaubt dies das Anlegen beliebig vieler Element in den Spalten oder Zeilen.
Im folgenden Beispiel werden 11 Buttons und ein Textfeld in drei Reihen und vier Spalten angeordnet: package s2.swing;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class GridLayoutTest {
public static void main(String[] args) {
JFrame f = new JFrame("GridLayout");
JPanel jp = new JPanel();
jp.setLayout(new GridLayout(3,4));
for (char c = 0; c < 11; ++c) {
jp.add(new JButton("Button " + (char)('A'+c)));
}
JTextArea jta =new JTextArea(3,10);
jta.append("JTextArea \nsecond row\nthird row");
jp.add(jta);
f.add(jp);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
}
|
Dies ergibt das folgende Fenster bei der Ausführung: |
Eigenschaften des Gridlayouts
|
gestauchtes Fenster: |
- 8749 views
Boxlayout - Schreibfehler
Hallo,
"Das folgende Beispiel zeigt ein vertikales Boxlayout welches nach 2 Sekunden automatisch die Orientierung zu vertikal und zurück triggert:"
-> Müsste sicher "die Orientierung zu HORIZONTAL und zurück triggert heißen ?
und
"Ein horizontales Layout wird mit der Konstanten BoxLayout.Y_AXIS beim Konfigurieren des Layoutmanagers erzeugt:"
-> horizontal müsste BoxLayout.X_AXIS sein, richtig ?
- Log in to post comments
BorderLayoutManager
Hallo,
Der BorderLayoutManager teilt Norden und Süden die gewünschte Breite und nicht die gewünschte Höhe zu, oder sehe ich das falsch?
Vgl. Westen / Osten.
lg
- Log in to post comments
Gute Überlegung
Schauen Sie sich mal das Bild mit den Buttons an:
- Norden und Süden sind gerade groß genug. Sie sind aber breiter als minimal notwendig.
- Osten, Westen, Mitte sind ursprünglich so breit wie sie minimal sein müssen. Zieht man das GUI in die Länge werden sie höher als notwendig.
- Log in to post comments
Rechtschreibfehler
Jede Komponente wird in ihrer Wunschgröße gargestellt???
Eigenschaften von BoxLayout
- Log in to post comments
Ereignisse und deren Behandlung
Ereignisse und deren BehandlungGUI Programme und Ereignisverarbeitung
GUI Programme laufen nicht linear ab. Bei Benutzerinteraktionen muss das Programm in der Lage sein sofort einen bestimmten Code zur Behandlung auszuführen.
Programmaktionen werden durch Benutzeraktionen getriggert. Man spricht hier von einem ereignisgesteuerten Programmablauf.
Definition: Ereignis |
---|
Ein Ereignis ist ein Vorgang in der Umwelt des Softwaresystems von vernachlässigbarer Dauer, der für das System von Bedeutung ist. |
Im Rahmen dieses Abschnitts sprechen wir von einer wichtigen Gruppe von Ereignissen den Benutzerinteraktionen:
Beispiele sind
- Mausclick
- Tasteneingabe
- Menülistenauswahl
- Zeigen auf einen Bereich des GUI
- Texteingabe oder Veränderung
Der Programmablauf wird aber auch von anderen weniger offensichtlichen Benutzerintreaktionen gesteuert
- Verdecken des Programmfensters durch ein anderes Fenster
- Fenstermodifikationen
- Vergößern, verkleinern
- Bewegen,
- Schließen eines Fenster
- Bewegen der Maus über das Programmfenster ohne Klicken (nicht auf allen Plattformen)
- Erlangen des Fokus auf einem Fenster
Ereignisklassen
Java benutzt Klassen zur Behandlung von Ereignissen (engl. Events) der Java-Benutzeroberflächen. Typische Klassen sind
Die Klassen enthalten die Beschreibung von GUI Ereignissen (z.Bsp. Maus wurde auf Position x=17, y=24 geklickt).
Sie korrelieren mit den Klassen die die grafischen Komponenten implementieren: Z.Bsp.
- Window erzeugt einen WindowEvent beim Schließen des Fensters
- JFrame einen WindowEvent beim Schließen des Fensters (JFrame ist ein Window!)
- JButton erzeugt einen ActionEvent beim Betätigen der Schaltfläche
Erzeugen von Ereignissen und deren Auswertung
Ereignisse werden automatisch von den Swingkomponenten erzeugt. Dies ist die Klasse JComponent mit ihren abgeleiteten Klassen. Sie werden zum Beispiel erzeugt, wenn ein Benutzer die Schaltfläche eines JButton betätigt. Die Aufgabe die dem Entwickler verbleibt ist die Registrierung seiner Anwendung für bestimmte GUI-Ereignisse. Die Anwendung kann nur auf Ereignisse reagieren gegen die sich sich vorher registriert hat.
Der Entwickler kann dann nach der Registrierung eines bestimmten Ereignisses ist die Auswertung des Ereignisses vornehmen und auf das Ereignis mit der gewünschten Aktion reagieren.
Java verwendet hierzu die Ereignis-Delegation um die Komponente die das Ereignis auslöst von der Ereignisbehandlung zu entkoppeln.
- Der Benutzer betätigt z.Bsp. eine Schaltfläche
- Das Laufzeitsystem erkennt das Ereignis
- Objekte die das Ereignis beobachten (engl. "Listener" in Java) werden aufgerufen und das Ereignis wird behandelt.
- Ein Listener erhält vom Laufzeitsystem ein ActionEvent-Objekt mit allen Informationen die zum Ereignis gehören.
Die Listenerobjekte müssen sich mit Hilfe einer Registrierung bei den GUI Objekten anmelden:
- Komponenten die Ereignisse erzeugen (Klasse JComponent) können, erlauben die Registrierung von "Listener" Objekten (engl. zuhören; nicht nur hören!)
- die Registrierung erfolgt mit Methoden der Syntax addXXXListener(...) der GUI Komponenten
- "Listener" Objekte implementieren das Java Interface ActionListener und damit die Methoden die beim Eintritt eines Ereignisses ausgeführt werden sollen.
Wichtig: Beziehung zwischen Listenerobjekt und GUI Objekt
Ein Listenerobjekt kann sich gegen ein oder mehrere GUI Objekte registrieren
- Registriert man ein Listenerobjekt gegen genau ein GUI Objekt (Bsp. 1 Button <-> 1 Listenerobjekt)
- muß man den Urheber des Ereignisses und den Ereignistyp nicht analysieren. Der Typ des Ereignis und das GUI Objekt sind bekannt
- Registriert man ein Listenerobjekt gegen mehrere GUI Objekte (Bsp. 3 Buttons <-> 1 Listenerobjekt)
- muß man im Listenerobjekt das übergebene Ereignisobjekt auf den Verursacher, das GUI Objekt, untersuchen
- muß man ein Listenerinterface implementieren, dass auch die Ereignisse aller Objekte verarbeiten kann.
- Beispiel: 1 Button und ein Textfeld werden von einem Listenerobjekt verwaltet. Man benötigt hier eine gemeinsame Listener-Oberklasse die beide graphische Objekte verwalten kann
Beispiel
Das folgenden Beispiel ist eine sehr einfache Implementierung eines JFrame mit einem Button und einem ActionListener:
package s2.swing; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; /** * * @author s@scalingbits.com */ public class ActionListenerBeispiel implements ActionListener { @Override public void actionPerformed(ActionEvent ae) { //Ausgabe des zum ActionEvent gehörenden Kontexts System.out.println("Aktion: " + ae.getActionCommand()); } public static void main(String[] args) { JFrame myJFrame = new JFrame("Einfacher ActionListener"); myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton jb = new JButton("Hier drücken"); ActionListenerBeispiel behandeln = new ActionListenerBeispiel(); jb.addActionListener(behandeln); // Füge Listener zu Button myJFrame.add(jb); // Füge Button zu Frame myJFrame.pack(); myJFrame.setVisible(true); } // Ende main() } // Ende der Klasse
Die Klasse ActionListenerBeispiel implementiert die Methode actionPerformed() nach der Spezifikation eines ActionListener.
Das Programm startet in der main() Methode und erzeugt das folgende GUI:
- Nach klicken des Button "feuert" das Buttonobjekt jb ein Ereignis (Event)
- die Methode actionPerformed() des registrierten Listener behandeln wird mit einem Eventobjekt als Übergabeobjekt aufgerufen
- Das Eventobjekt ae wird analysiert und das
- Kommando wird als Text auf der Konsole ausgegeben:
Aktion: Hier drücken
Der Text "Hier drücken" wird ausgeben, da das Eventobjekt bei Buttons immer den Text des Buttons ausgibt.
Ereignisse (Events)
Es gibt eine reichhaltige Hierarchie von spezialisierten Event- und Listenerklassen. Hierzu sei auf die Java-Tutorials von Oracle verwiesen.
Weitere Events sind zum Beispiel:
- ItemEvent: Z. Bsp. Analysieren von JCheckBox Komponenten
- MouseEvent: Analysieren von Mausposition, Bewegung etc.
- ChangeEvent: Z. Bsp. Analysieren von Änderungen an einem JSlider
Der Swing "Event Dispatch Thread"
Damit Benutzeraktionen unabhängig vom normalen Programmablauf behandelt werden können, benutzt Swing eine eigene Ausführungseinheit, einen Thread, zum Bearbeiten der Benutzeraktionen. Dieser "Thread" ist ein Ausführungspfad der parallel zum normalen Programmablauf im main-Thread abläuft. Threads laufen parallel im gleichen Adressraum des Prozesses und haben daher Zugriff auf die gleichen Daten.
Blockieren des GUIs
Alle Aktionen der ActionListener werden vom "Swing-Event-Dispatch-Thread" ausgeführt. Diese Thread arbeitet GUI Interaktionen ab während das Javaprogramm mit anderen Threads im Hintergrund weiterlaufen kann.
Wichtig |
---|
Alle Codestrecken von zur Behandlung von Ereignissen (Methode actionPerformed()) werden in nur einem Thread aufgerufen und blockieren alle anderen Behandlungen während sie ausgeführt werden. Im Klartext: Das GUI wird während der Ausführungszeit einer Ereignisbehandlung nicht bedient und ist blockiert. Vermeiden Sie aufwändige (=langlaufende) Implementierungen in den actionPerformed() Methoden! |
Beispiel
Das folgende Programm blockiert das GUI für 2 Sekunden. Die Blockade ist nach dem Klicken an der geänderten Farbe des Buttons zu erkennen. Er bleibt gedrückt:
package s2.swing; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; /** * * @author s@scalingbits.com */ public class ActionListenerBlockiert implements ActionListener { @Override public void actionPerformed(ActionEvent ae) { //Ausgabe des zum ActionEvent gehörenden Kontexts System.out.println("Aktion: " + ae.getActionCommand()); try {// Thread für 2s blockieren (schlafen) Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { JFrame myJFrame = new JFrame("Einfacher ActionListener"); myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton jb = new JButton("Hier drücken"); ActionListenerBlockiert behandeln = new ActionListenerBlockiert(); jb.addActionListener(behandeln); // Füge Listener zu Button myJFrame.add(jb); // Füge Button zu Frame myJFrame.pack(); // Berechne Layout myJFrame.setVisible(true); } }
Synchronisation mit Swing GUIs
|
- 7692 views
Erzeugen von Ereignissen und deren Auswertung
Sollte der zweite Absatz unter der Überschrift eigentlich so formuliert sein?
"Der Entwickler kann dann nach der Registrierung eines bestimmten Ereignisses die Auswertung des Ereignisses vornehmen und auf das Ereignis mit der gewünschten Aktion reagieren."
Und beim ersten Beispiel steht: "Nach klicken des Button "feuert" ..."
Dort ist der Button "Hier klicken" gemeint, oder?
- Log in to post comments
ActionListenerBeispiel
In dem Beispiel:
ActionListenerBeispiel behandeln = new ActionListenerBeispiel();
--> jb.addActionListener(behandeln); // Füge Listener zu Button
myJFrame.add(jb); // Füge Button zu Frame
myJFrame.setVisible(true);
myJFrame.pack();
müsste der Listener der auf jb registriert wird, "ActionListenerBeispiel" heißen oder?
- Log in to post comments
Antwort
Ich bin mir nicht ganz sicher, dass wir über das gleiche sprechen.
Kommunikation über diese Kommentare haben ihre Grenzen.
behandeln ist ein Zeiger auf ein Objekt ActionListenerBeispiel.
Dieses Objekt wird beim dem Objekt jb mit addActionListener registriert.
Das Programm übersetzt und funktioniert auch.
- Log in to post comments
Event-Interface und Event-Adapter
Event-Interface und Event-AdapterDie Implementierung eines einfachen ActionListener-Schnittstelle ist recht einfach, da nur eine Methode implementiert werden muss.
Viele nicht triviale ActionListener erfordern jedoch die Implementierung von mehreren Methoden aufgrund der Komplexität der entsprechenden Komponente. Ein Beispiel hierfür ist die Schnittstelle MouseListener. Sie erfordert die Implementierung der folgenden Methoden:
- mouseClicked(MouseEvent e)
- mouseEntered()
- mouseExited()
- mousePressed()
- mouseReleased()
Dies ist aufwändig wenn man sich nur für ein bestimmtes Ereignis wie z. Bsp. mouseReleased() interessiert. Man muss alle Interfacemethoden implementieren. Vier der Methoden bleiben leer und sind überflüssig.
Listener-Adapter Klassen
Um diese unnötige Schreibarbeit zu vermeiden, stellt Swing nach dem Entwurfsmuster Adapter Klassen als abstrakte Klassen mit leeren, aber schon implementierten Methoden zur Verfügung.
Vorteil: Der Entwickler kann seine Klasse aus der abstrakten Klasse ableiten und muss nur die Methoden für die Ereignisse implementieren die ihn interessieren.
Für die Implementierung von Maus-Events steht zum Beispiel die Klasse MouseAdapter zur Verfügung, die die entsprechenden Methoden implementiert.
Einige der Adapterklassen die Swing zur vereinfachten Implementierung von Ereignisschnittstellen (Event Interface) sind:
Spezialisierungen der Schnittsteller EventListener | Adapterklasse |
---|---|
ComponentListener | ComponentAdapter |
ContainerListener | ContainerAdapter |
FocusListener | FocusAdapter |
KeyListener | KeyAdapter |
MouseListener | MouseAdapter |
MouseMotionListener | MouseMotionAdapter |
WindowListener | WindowAdapter |
Im folgenden Beispiel wird die Klasse MouseAdapterTest aus der abstrakten Klasse MouseAdapter abgeleitet um nur die Methode mouseClicked() implementieren zu müssen:
package s2.swing; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JFrame; public class MouseAdapterTest extends MouseAdapter { public MouseAdapterTest() { erzeugeGUI(); } public static void main(String[] args) { MouseAdapterTest mat = new MouseAdapterTest(); } private void erzeugeGUI() { JFrame myJFrame = new JFrame("Mouse Click Adapter Test"); myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton jb = new JButton("Hier drücken"); jb.addMouseListener(this); myJFrame.getContentPane().add(jb); myJFrame.pack(); myJFrame.setVisible(true); } @Override public void mouseClicked(MouseEvent mEvent) { System.out.println("MouseClick wurde auf Position [" + mEvent.getX() + "," + mEvent.getY() + "] " + mEvent.getClickCount() + " mal geklickt"); } } // Ende der Klasse
Das Programm registriert sich selbst gegen den Button und ist jetzt in der Lage, die Information des dEventobjekts mEvent auszulesen (x,y Position, Anzahl der Mehrfachclicks).
Beispiel
Im Diagramm (unten) sind die beiden Möglichkeiten aufgeführt die ein Entwickler zum Auslesen eines Mauscklicks benutzen kann. Die von Java zur Verfügung gestellten Infrastruktur besteht aus dem zu implementierenden Interface MouseListener und einer abstrakten Klasse Mouseadapter:
Ein Entwickler der sich nur gegen das MouseClick Ereignis registrieren möchte, kann wie im Diagramm oben:
- das Interface MouseListener in z.Bsp. der Klasse ListenerAufwaendig implementieren. Hier müssen alle 5 Methoden des Interface implementiert werden. Die vier nicht benötigten Methoden können als leere Methoden implementiert werden.
- aus der abstrakten Klasse MouseAdapter eine Klasse wie z.Bsp. ListenerEinfach ableiten. In ihr muss man nur eine einzige Methode mouseClicked() durch Überschreiben implementieren. Die anderen vier Methoden sind schon in der Klasse MouseAdapter als leere Proformamethoden implementiert. Beim Ableiten aus der abstrakten Klasse MouseAdapter müssen nur die gewünschten Methoden überschrieben werden. Ein Konstruktor ist nicht nötig, da die Klasse MouseAdapter einen Default-Konstruktor besitzt.
Anbei die vollständige Implementierung der semantisch gleichwertigen Methoden:
Klasse ListenerAufwaendig | Klasse ListenerEinfach |
---|---|
package s2.swing; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class ListenerAufwaendig implements MouseListener{
@Override public void mouseClicked(MouseEvent mEvent) {
System.out.println("MouseClick wurde auf Position ["
+ mEvent.getX() + ","
+ mEvent.getY() + "] "
+ mEvent.getClickCount() + " mal geklickt");
}
@Override public void mouseEntered(MouseEvent mEvent)
{ /* leere Implementierung, erzwungen */ }
@Override public void mouseExited(MouseEvent mEvent)
{ /* leere Implementierung, erzwungen */ }
@Override public void mousePressed(MouseEvent mEvent)
{ /* leere Implementierung, erzwungen */ }
@Override public void mouseReleased(MouseEvent mEvent)
{ /* leere Implementierung, erzwungen */ }
}
|
package s2.swing; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; public class ListenerEinfach extends MouseAdapter{ @Override public void mouseClicked(MouseEvent mEvent) { System.out.println("MouseClick wurde auf Position [" + mEvent.getX() + "," + mEvent.getY() + "] " + mEvent.getClickCount() + " mal geklickt"); } } |
- 6304 views
Anonyme und innere Klassen
Anonyme und innere KlassenNach den bisher vorgestellten Prinzipien erfordert die Implementierung eines GUI die Implementierung von vielen Listenern und führt zur Erzeugung sehr vieler Klassen. Java erlaubt hier die Implementierung von anoymen, inneren Klassen die nur im Kontext einer bestimmten Klasse existieren und den Namensraum der Klassen nicht unnötig belasten.
Innere Klassen
Innere Klassen helfen es zu vermeiden, dass man Klassen veröffentlicht die nur von genau einer anderen Klasse benutzt werden. Innere Klassen werden syntaktisch wie normale Klassen implementiert. Der einzige Unterschied ist, dass sie im Block einer äusseren Klasse implementiert werden. Sie werden in der äusseren Klasse mit dem gleichen Block der äusseren Klasse wie die Klassenvariablen und Methoden der äusseren Klasse implementiert.
Die in der Vorlesung vorgestellten inneren Klassen sind Elementklassen.
Definition Elementklasse |
---|
Elementklassen sind wie Instanzmethoden und Instanzvariablen Elemente einer (anderen) Klasse. Sie werden auf der gleichen Blockebene wie Instanzmethoden und Instanzvariablen implementiert. Sie haben einen Zugriffschutz wie Instanzmethoden und Instanzvariablen. |
Innere Klassen können auch als lokale Klasse innerhalb eines beliebigen Blocks implementiert werden. Diese Variante ist nicht Gegenstand der Vorlesung.
Die inneren Klassen gehören zum Paket der äusseren Klasse. Importkommandos müssen in der äusseren Klasse implementiert werden.
Besondere Eigenschaften von Elementklassen |
---|
Instanzen von Elementklassen sind Komponenten des umgebenden Objekts! |
Das vorhergehende Beispiel kann jetzt wie folgt implementiert werden:
package s2.swing; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JFrame; /** * * @author s@scalingbits.com */ public class MouseAdapterInnereKlasseTest { /** * Die innere Klasses MyMouseListener */ class MyMouseListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent mEvent) { System.out.println("MouseClick wurde auf Position [" + mEvent.getX() + "," + mEvent.getY() + "] " + mEvent.getClickCount() + " mal geklickt"); } } /** * Erzeuge GUI im Konstruktor */ public MouseAdapterInnereKlasseTest() { JFrame myJFrame = new JFrame("Mouse Click Innere Klasse Test"); myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton jb = new JButton("Hier drücken"); jb.addMouseListener(new MyMouseListener()); myJFrame.getContentPane().add(jb); myJFrame.pack(); myJFrame.setVisible(true); } public static void main(String[] args) { MouseAdapterInnereKlasseTest mat = new MouseAdapterInnereKlasseTest(); } }
Die Unterschiede sind die Folgenden:
- Die Klasse MouseAdapterInnereKlasseTest muss nicht mehr von der Klasse MouseAdapter abgeleitet werden oder eine Schnitstelle MouseEvent implementieren
- Es wird eine eigene innere Klasse MyMouseListener implementiert die nicht ausserhalb der umgebenden Klasse bekannt ist.
- Die Klasse MyMouseListener erbt (extends Schlüsselwort) von der Klasse MouseAdapter.
- Beim Hinzufügen eines Listeners zum Button wird eine Instanz der inneren Klasse erzeugt.
Diese innere Klasse hat für den Entwickler den Vorteil, dass er keine neuen Klassen nach aussen hin bekanntmachen muss. Die Implementierung des Listeners erfolgt in der gleichen Datei. Die Implementierung des Listener ist also visuell näher als wenn sie in einer eigenen Klasse und einer eigenen Datei geschehen würde.
Objektabhängigkeit von Instanzen innerer Klassen
Im obigen Beispiel wird eine Instanz der Klasse MyMouseListener erzeugt. Dies ist nur erlaubt wenn das Objekt aus dem Kontext (Methode) eines Objekts der Klasse MouseAdapterInnereKlasseTest erzeugt wird. Dieses äussere Objekt existiert, da die innere Klasse im Konstruktor von MouseAdapterInnereKlasseTest erzeugt wird.
Der Code zum Erzeugen des GUI im Konstruktor kann nicht einfach in die statische main() Methode kopiert werden. Hier gibt es noch keinen Kontext zu einem Objekt der Klasse MouseAdapterInnereKlasseTest. Der javac Übersetzer wird einen Fehler melden.
Anonyme, innere Klasse
Für Swing wurde das Konzept der anonymen, inneren Klasse entwickelt.
Definition anonyme, innere Klasse |
---|
Eine anonyme, innere Klasse ist eine lokale Klasse ohne Namen die innerhalb eines Ausdrucks (Block) definiert und instanziiert wird. |
Anonyme, innere Klassen haben keinen Namen und daher auch keine Konstruktoren. Sie werden typischerweise als Implementierungen für Adapterklassen oder Schnittstellen verwendet.
Beispiel
Anonyme, innere Klassen erlauben die benötigte Listenerimplementierung noch eleganter durchzuführen:
An der Stelle an der eine Instanz eines Listeners benötigt wird kann man auch direkt eine vollständige Klasse implementieren und instanziieren.
Das folgende Beispiel hat die gleiche Funktion wie das vorgehende Beispiel mit der Implementierung einer inneren Klasse. Es kommt aber ohne einen eigenen Klassennamen aus:
package s2.swing; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JFrame; public class MouseAdapterAnonymeInnereKlasseTest { /** * Erzeuge GUI im Konstruktor */ public MouseAdapterAnonymeInnereKlasseTest() { JFrame myJFrame = new JFrame("Mouse Click Adapter Test"); myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton jb = new JButton("Hier drücken"); jb.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent mEvent) { System.out.println("MouseClick wurde auf Position [" + mEvent.getX() + "," + mEvent.getY() + "] " + mEvent.getClickCount() + " mal geklickt"); } }); myJFrame.getContentPane().add(jb); myJFrame.pack(); myJFrame.setVisible(true); } public static void main(String[] args) { MouseAdapterAnonymeInnereKlasseTest mat = new MouseAdapterAnonymeInnereKlasseTest(); } }
InteressantEine anonyme, innere Klasse kann...
Wichtig: Es müssen alle Methoden implementiert werden, die notwendig sind um die Klasse zu instanzieren |
Die Unterschiede sind die Folgenden:
- Die Klasse MouseAdapterInnereAnonymeKlasseTest muss nicht mehr von MouseAdapter abgeleitet werden oder einen MouseEvent implementieren
- Beim Hinzufügen eines Listeners zum Button wird
- mit dem new Operator eine Instanz einer anonymen Klasse angelegt, die die abstrakte Klasse MouseAdapter implementiert. Sie ist anonym da sie selbst keinen Namen besitzt.
- die anonyme Klasse wird innerhalb der Klasse MouseAdapterInnereAnonymKlasseTest soweit wie nötig implementiert um aus der abstrakten Oberklasse eine normale Klasse zu erzeugen
Diese "Hilfskonstruktion" hat für den Entwickler eine Reihe von Vorteilen:
- Das Ereignis kann textuell sehr nahe an der Erzeugung der Komponente implementiert werden. Der Code wird übersichtlicher
- Es müssen keine neuen Klassen mit eigenen Namen erzeugt werden. Hierdurch wird die gesamte Klassenhierarchie übersichtlicher und deutlich kleiner.
Statische innere Klassen, nicht anonyme lokale Klassen
... sind nicht Gegenstand dieser Vorlesung.
- 15872 views
1. MouseAdapterInnereKlasseTest
Für das 1. Code Beispiel "MouseAdapterInnereKlasseTest", heißt die Innere Klasse nicht "MyMouseListener" anstatt wie in der Erklärung darunter "MyMouseClicked" beschrieben?
Entschuldigen Sie falls der Fehler schon in der Vorlesung gefunden wurde, ich war heute krank.
MfG
- Log in to post comments
Korrekte Beobachtung
Danke für den Hinweis. Der Text wurde korrigiert. Die beiden Programmierbeispiele wurden um eine Methode entschlackt und sind jetzt kürzer und hoffentlich besser lesbar.
- Log in to post comments
Müsste es dann in der
Müsste es dann in der Erklärung nicht statt:
"Die Klasse MyMouseClicked implementiert das MouseAdapter"
...
"Die Klasse MyMouseListener implementiert das MouseAdapter"
... heißen ?
- Log in to post comments
kleiner Rechtschreibefehler
Hallo Herr Schneider,
mir ist ein kleiner Rechtschreibefehler aufgefallen. Bei den besonderen Eigenschaften von Elementklassen haben sie beim ersten Punkt umschliesen anstatt umschließen geschrieben.
freundliche Grüße
- Log in to post comments
Danke, wurde verbessert.
Danke für die Wertschätzung der deutschen Sprache.
Fehler wird verbessert.
- Log in to post comments
Übungen (Swing)
Übungen (Swing)Ausnahmefenster
Implementieren Sie ein Fenster mit Hilfe der Klasse JFrame zur Behandlung von Ausnahmen.
- Das JFrame soll beim Auftreten einer Ausnahmen aufgerufen werden und den Namen der Ausnahme zeigen.
- Das Programm soll dann auf Wunsch beendet werden oder es soll ein Stacktrace auf der Konsole angezeigt werden
- Das Programm soll ein Bild aus dem Internet als Label verwenden
Das Fenster soll in etwa wie das folgende GUI aussehen:
Verwenden Sie das Rahmenprogramm AusnahmeFenster.java welches eine Infrastruktur zum Testen zur Verfügung stellt:
package s2.swing;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class AusnahmeFenster {
final private JFrame hf;
final private JButton okButton;
final private JButton exitButton;
final private Exception myException;
/**
* Aufbau des Fensters zur Ausnahmebehandlung
*
* @param fehlermeldung ein beliebiger Fehlertext der angezeigt wird
* @param e Die Ausnahme die angezeigt werden soll
*/
public AusnahmeFenster(String fehlermeldung, Exception e) {
JLabel logo;
JPanel buttonPanel;
myException = e;
// 1. Erzeugen einer neuen Instanz eines Swingfensters
System.out.println("Hier beginnt die Arbeit: Löschen Sie dieses Kommando");
// ...
hf = null;
// 3. Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel: 220
// 2. Parameter: vertikale Größe: 230
// ...
// 8. Labelerzeugung
logo = meinLogo();
// 4. Nicht Beenden bei Schliessen des Fenster
// 5. Anlegen der Buttons
okButton = null;
exitButton= null;
// 10. Hinzügen der Eventbehandlung
// Tipp: Die Klasse muss noch das Interface ActionListener implementieren!
// ...
// 6. Aufbau des Panels
// ...
// 7. Aubau des ContentPanes
// ...
// 2.1 Das Layout des JFrame berechnen.
// ...
// 3. Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel
// 2. Parameter: vertikale Größe
// ...
// 2.2 Sichtbar machen des JFrames. Immer im Vordergrund
// ...
// ...
}
/**
* Implementieren des Logos
* 9.ter Schritt
* @return Zeiger auf das Logoobjekt
*/
private JLabel meinLogo() {
URL logoURL;
JLabel logoLabel;
String myURL = "https://upload.wikimedia.org/wikipedia/commons/0/01/DHBW_MA_Logo.jpg";
try {
logoURL = new URL(myURL);
ImageIcon myImage = new ImageIcon(logoURL);
logoLabel = new JLabel(myImage);
} catch (java.net.MalformedURLException e) {
System.out.println(e);
System.out.println("Logo URL kann nicht aufgelöst werden");
logoLabel = new JLabel("Logo fehlt");
}
return logoLabel;
}
/**
* Behandlung der JButton-Ereignisse
* 11. ter Schritt
* @param e
*/
public void actionPerformed(ActionEvent e) {
//System.exit(0);
//System.out.println("OK Button clicked");
//myException.printStackTrace();
}
/**
* Hauptprogramm zum Testen des Ausnahmefensters
* @throws Exception
*/
public static void main(String[] args) {
AusnahmeFenster dasFenster;
try {
myTestMethod();
} catch (Exception e) {
dasFenster = new AusnahmeFenster("Hier läuft etwas schief", e);
}
}
/**
* Eine Testmethode die mit einer Division durch Null eine
* Ausnahme provoziert
* @throws Exception
*/
public static void myTestMethod() throws Exception {
int a = 5;
int b = 5;
int c = 10;
c = c / (a - b);
System.out.println("Programm regulär beendet");
}
}
Empfehlung: Bauen Sie das GUI schrittweise auf und testen Sie es Schritt für Schritt
- Erzeugen eines einfachen JFrame
- Sichtbarmachen des JFrame
- Größe des JFrames setzen
- Programm nach Schließen des JFrames weiterlaufen lassen
- Anlegen der Buttons
- Aufbau des Panels
- Verbinden von Buttons, Panel und JFrame
- Erzeugen des Labels mit dem GIF und Hinzufügen zum Pane
- Implementieren des Labels mit Logo
- Hinzufügen der ActionListener zu den Buttons
- Implementieren der Aktionen
Taschenrechner
Implementieren Sie einen Taschenrechner mit den vier Grundrechenarten.
Benutzen Sie hierfür die Aufgabenstellung der Universität Bielefeld von Herrn Jan Krüger:
- Aufgabenstellung
- Quellen und Rahmenprogramm: Calculator.tar.gz
Innere Klasse und anonyme Klasse
Das Programm der vorhergehenden Übung (Ausnahmefenster) benutzt in der Musterlösung eine einzige Methode actionPerformed() in der Klasse AusnahmeFenster um die Aktionen der beiden Buttons auszuführen.
public class AusnahmeFensterFertig implements ActionListener {
...
public AusnahmeFenster(String fehlermeldung, Exception e) {
...
okButton = new JButton();
exitButton = new JButton();
// 10. Hinzügen der Eventbehandlung
okButton.addActionListener(this);
exitButton.addActionListener(this);
...
}
public void actionPerformed(ActionEvent e) {
JButton source = (JButton) (e.getSource());
if (source == exitButton) {
System.exit(0);
}
if (source == okButton) {
System.out.println("OK Button clicked");
myException.printStackTrace();
}
}
}
Aufgabe 1:
- Implementieren Sie eine externe Klasse SystemExitListener die als Listener für den "Beenden" Button genutzt werden kann.
- Ändern Sie die Klasse AusnahmeFenster derart,dass Sie für den "Beenden" Button nicht mehr das Ausnahmefenster als Objekt registrieren (this). Erzeugen Sie als registrierten Listener eine Instanz der Klasse SystemExitListener.
Sie haben nun eine eigene Klasse mit einem eigenen Objekt zur Behandlung des "Beenden" Ereignisses
Weiterführend (optional)
Implementieren Sie einen Menüeintrag "Beenden" in einer Menüleiste mit dem EIntrag "Ablage".
Herangehen:
|
Aufgabe 2:
- Die Klasse AusnahmeFenster soll NICHT mehr die Schnittstelle ActionListener implementieren.
- Implementieren Sie eine innere Klasse SystemExitAction. Sie soll einen ActionListener implementieren, der das Programm beendet.
- Ändern sie den Aufruf addActionListener() des exitButton so, dass er die Klasse SystemExitAction benutzt.
- Ändern Sie den Aufruf addActionListener() des okButton so, dass eine anonyme innere Klasse aufgerufen wird die einen Stacktrace ausdruckt
Hierdurch entfällt die Analyse der auslösenden Aktion in der ursprünglichen Implementierung. Für jede Aktion wurde jetzt eine eigene Methode implementiert
- 8379 views
Lösungen (Swing)
Lösungen (Swing)Ausnahmefenster
package s2.swing;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class AusnahmeFensterLoesung implements ActionListener {
final private JFrame hf;
final private JButton okButton;
final private JButton exitButton;
final private Exception myException;
/**
* Aufbau des Fensters zur Ausnahmebehandlung
*
* @param fehlermeldung ein beliebiger Fehlertext der angezeigt wird
* @param e Die Ausnahme die angezeigt werden soll
*/
public AusnahmeFensterLoesung(String fehlermeldung, Exception e) {
JLabel logo;
JPanel buttonPanel;
myException = e;
hf=null;
okButton=null;
exitButton=null;
// 1. Erzeugen einer neuen Instanz eines Swingfensters
hf = new JFrame("Anwendungsfehler");
// 8. Labelerzeugung
logo = meinLogo();
// 4. Beenden bei Schliesen des Fenster
hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 5. Anlegen der Buttons
okButton = new JButton();
okButton.setText("Stack Trace");
exitButton = new JButton();
exitButton.setText("Beenden");
// 10. Hinzügen der Eventbehandlung
okButton.addActionListener(this);
exitButton.addActionListener(this);
// 6. Aufbau des Panels
buttonPanel = new JPanel(new GridLayout(1, 0));
buttonPanel.add(exitButton);
buttonPanel.add(okButton);
JTextArea fehlertextArea = new JTextArea(2, 20);
fehlertextArea.append(fehlermeldung + "\n");
fehlertextArea.append("Exception: "+ myException);
// 7. Aubau des ContentPanes
Container myPane = hf.getContentPane();
myPane.setLayout(new BorderLayout());
myPane.add(logo, BorderLayout.NORTH);
myPane.add(fehlertextArea, BorderLayout.CENTER);
myPane.add(buttonPanel, BorderLayout.SOUTH);
// 2.1 Das Layout des JFrame berechnen.
hf.pack();
// 3. Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel
// 2. Parameter: vertikale Größe
hf.setSize(350, 300);
// 2.2 Sichtbar machen des JFrames. Immer im Vordergrund
hf.setVisible(true);
hf.setAlwaysOnTop(true);
}
/**
* Implementieren des Logos
* 9.ter Schritt
* @return Zeiger auf das Logoobjekt
*/
private JLabel meinLogo() {
URL logoURL;
JLabel logoLabel;
String myURL = "https://upload.wikimedia.org/wikipedia/commons/0/01/DHBW_MA_Logo.jpg;
try {
logoURL = new URL(myURL);
ImageIcon myImage = new ImageIcon(logoURL);
logoLabel = new JLabel(myImage);
} catch (java.net.MalformedURLException e) {
System.out.println(e);
System.out.println("Logo URL kann nicht aufgelöst werden");
logoLabel = new JLabel("Logo fehlt");
}
return logoLabel;
}
/**
* Behandlung der JButton Ereignisse
* 11. ter Schritt
* @param e
*/
public void actionPerformed(ActionEvent e) {
JButton source = (JButton) (e.getSource());
if (source == exitButton) {
System.exit(0);
}
if (source == okButton) {
System.out.println("OK Button clicked");
myException.printStackTrace();
}
}
/**
* Hauptprogramm zum Testen des Ausnahmefensters
* @throws Exception
*/
public static void main(String[] args) {
AusnahmeFensterLoesung dasFenster;
try {myTestMethod();}
catch (Exception e) {
dasFenster = new AusnahmeFensterLoesung("Hier läuft etwas schief",e);
}
}
/**
* Eine Testmethode die eine durch eine Division durch Null eine
* Ausnahme provoziert
* @throws Exception
*/
public static void myTestMethod() throws Exception {
int a = 5;
int b = 5;
int c = 10;
c = c / (a-b);
System.out.println("Programm regulär beendet");
}
}
Taschenrechner
Die Universität Bielefeld (Herr Jan Krüger) bieten die Lösung der Programmieraufgabe an unter:
Innere und anonyme Klasse
Aufgabe 1
Klasse SystemExitListener
package s2.swing; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * * @author s@scalingbits.com * Implementierung eines ActionListener der als Aktion die Anwendung * beendet */ public class SystemExitListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }
Einfügen eines Menüs (Optional)
import javax.swing.*; ... JMenuItem jmi = new JMenuItem("Beenden"); jmi.addActionListener(new SystemExitListener()); JMenu jm = new JMenu("Ablage"); jm.add(jmi); JMenuBar jmb = new JMenuBar(); jmb.add(jm); hf.setJMenuBar(jmb);
Aufgabe 2
Hinweis: Die Klasse wurde in AusnahmeFensterInnerer umbenannt
}
hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- 5399 views
Zu Übung 1 (Swing)
Wie in der Veranstaltung: hf.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); sollte sein hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); damit das Programm beim Schließen des Fensters beendet wird. Viele Grüße
- Log in to post comments
Danke für die Erinnerung.
habe den Code verbessert.
Kommentar für die nicht Eingeweihten:
Mit diesen beiden Optionen kann man steuern ob die gesamte Anwendung bei schliesen des Fensters beendet wird. In der Übung macht das Beenden der Anwendung Sinn, da man sonst nichts mehr machen kann. Bei realen Anwendungen ist es oft besser wenn man die Anwendung nicht beendet weil es oft Ikonen oder Menüleisten gibt, die es erlauben neue Fenster zu öffnen.
- Log in to post comments
Lernziele (Swing, innere und anonyme Klassen)
Lernziele (Swing, innere und anonyme Klassen)
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu beantworten:Fragen zur graphischen Programmierung (Swing)
- 3547 views
Generische Klassen (Generics)
Generische Klassen (Generics)
Generische (engl. generic) Klassen, Schnittstellen und Methoden wurden in Java in der Version 5.0 eingeführt. Generisch bedeutet in diesem Zusammenhang, dass die entsprechenden Klassen, Methoden und Schnittstellen parametrisierbare Typen verwenden. Ein Übergabetyp ist also nicht im Quellcode festgelegt, er kann bei der Verwendung zum Übersetzungszeitpunkt verschiedene Ausprägungen annehmen. |
Das Konzept der generischen Klassen erhöht die Typsicherheit im Zusammenhang mit Polymorphismus und Vererbung. Eine Erhöhung der Typsicherheit bedeutet, dass der Entwickler weniger explizite Casts verwenden muss um Typkonversionen und Anpassungen zu erzwingen.
Generische Klasse |
---|
Verwendet eine Klasse formale Typ-Parameter so nennt man sie generische Klasse. Der formale Typ-Parameter ist ein symbolischer Typ der wie ein normaler Bezeichner in Java aufgebaut ist. Er wird nach dem Klassennamen in spitzen Klammern angegeben. |
Am einfachsten lässt sich das Konzept an einem Beispiel einer Klasse Koordinate mit einem parametrisierbaren Typen <T> veranschaulichen:
public class Koordinate<T> { private T x; private T y; Koordinate(T p1, T p2) { x = p1; y = p2; } ... }
In dieser Implementierung der Klasse wird der eigentliche Typ der Komponenten (x und y) der Klasse Koordinate nicht konkret festgelegt. Ein Konsument dieser Klasse kann sich entscheiden diese Implementierung für Integer, Float oder Double zu verwenden. Die allgemeine Syntaxregel für die Deklaration einer generischen Klasse lautet:
Deklaration einer generischen Klasse |
---|
class Klassenname < Typvariablenliste > {//Body} |
Will man die Implementierung der generischen Klasse Koordinate für große Fließkommazahlen verwenden, so benutzt man die folgende Syntax:
Koordinate<Double> eineKoordinate = new Koordinate<Double>(2.2d, 3.3d);
Der Typ ist parametrisierbar und wird Teil des Variablennamens bzw. des Klassennamens. Die allgemeine Syntax zum Erzeugen eines Objekts einer generischen Klasse lautet:
Instanziieren einer generischen Klasse |
---|
new Klassenname < Typliste > ( Parameterliste); |
Will man kleinere Fließkommazahlen benutzen, so kann man die Klasse Koordinate auch mit dem Typ Float parametrisieren:
Koordinate<Float> nochEineKoordinate = new Koordinate<Float>(4.4f, 5.5f);
Beispiel einer einfachen generischen Klasse
Die Klasse Koordinate hat ein Hauptprogramm main() welches zwei Instanzen mit unterschiedlichen Ausprägungen erzeugt und bei der Ausgabe der Werte die Methode toString() implizit aufruft:
package s2.generics; /** * * @author s@scalingbits.com * @param <T> Generischer Typ der Klasse Koordinate */ public class Koordinate<T> { private T x; private T y; public T getX() {return x;} public void setX(T x) {this.x = x;} public T getx() {return x;} public void setY(T y) { this.y = y;} public T getY() {return y;} public Koordinate(T xp, T yp ) { x = xp; y = yp; } @Override public String toString() {return "x: " + x + "; y: " + y;} public static void main (String[] args) { Koordinate<Double> k1 = new Koordinate<Double>(2.2d, 3.3d); System.out.println(k1); Koordinate<Integer> k2 = new Koordinate<Integer>(2, 3); System.out.println(k2); Koordinate<Number> k3 = new Koordinate<Number>(4.4f, 5.5f); System.out.println(k3); } // Ende main() } // Ende Klasse Koordinate
Bei der Ausführung ergibt sich die folgende Konsolenausgabe:
x: 2.2; y: 3.3 x: 2; y: 3 x: 4.4; y: 5.5
- 12311 views
Generics zur Übersetzungs- und Laufzeit
Generics zur Übersetzungs- und LaufzeitDer javac Übersetzer erzeugt aus der gegebenen generischen Klasse in der Datei Koordinate.java nur genau eine Datei Koordinate.class mit Bytecode für alle möglichen Instanzierungen.
package s2.generics;/**
*
* @author s@scalingbits.com
* @param <T> Generischer Typ der Klasse Koordinate
*/
public class Koordinate<T> {
private T x;
private T y;public T getX() {return x;}
public void setX(T x) {this.x = x;}
public T getx() {return x;}
public void setY(T y) { this.y = y;}
public T getY() {return y;}public Koordinate(T xp, T yp ) {
x = xp;
y = yp;
}@Override
public String toString() {return "x: " + x + "; y: " + y;}public static void main (String[] args) {
Koordinate<Double> k1 = new Koordinate<Double>(2.2d, 3.3d);
System.out.println(k1);
Koordinate<Integer> k2 = new Koordinate<Integer>(2, 3);
System.out.println(k2);
Koordinate<Number> k3 = new Koordinate<Number>(4.4f, 5.5f);
System.out.println(k3);
}
}
Der erzeugte Bytecode enthält nicht mehr den Formalparameter <T>. Er enthält den Referenztyp Object. Aus diesem Grund kann der Bytecode mit allen Instanzierungen arbeiten solange sie aus der Klasse Object abgeleitet sind. Direkte Basistypen wie int oder long können daher nicht direkt in generischen Klassen verwendet werden. Die Stellvertreterklassen Integer und Long in Verbindung mit dem "Autoboxing" sind der Ersatz für die direkte Verwendung.
Zur Laufzeit kann dann der Bytecode verwendet werden um den aktuell parametrisierten Parametern zu arbeiten.
Das Ersetzen der Parametrisierung <T> durch die Klasse Object nennt man Type-Erasure (engl. Auslöschen).
Polymorphismus und Vererbung von generischen Klassen |
---|
Aufgrund der Möglichkeit die Klassen mit unterschiedlichen aktuellen Parametern zu benutzen sind generische Klassen polymorph. Die aktuell parametrisierten Objekte stehen jedoch auf der gleichen Vererbungsebene. Sie haben keine Vererbungsbeziehung! |
- 5342 views
Generics, Autoboxing, Subtyping
Generics, Autoboxing, SubtypingDie im vorhergehenden Beispiel benutzte Klasse kann aber auch als aktuellen Parameter eine abstrakte Klasse wie Number verwenden:
Mit Hilfe des Java Autoboxing kann man die Variablen k3 und k4 auch wie folgt belegen:
Koordinate<Number> k3 = new Koordinate<Number>(2l, 3l); System.out.println(k3);
k3 = new Koordinate<Number>(4.4f, 5.5f);
System.out.println(k3);
Die Variable k3 hat den formalen Parametertyp Number. Die aktuellen Parameter 21 und 31 sind int Typen. Sie werden automatisch in Instanzen von Integer umgewandelt und sind daher Spezialisierungen der Klasse Number. Die Variable k3 zeigt hier zuerst auf eine Koordinate die aus ganzen Zahlen besteht und anschließend auf eine Koordinate die aus Fließkommanzahlen bestehen
Ohne Autoboxing würde man die Variable k3 so belegen:
Koordinate<Number> k3 = new Koordinate<Number>(new Integer(2l), new Integer(3l));
Subtyping
Die parametrisierte Klasse Koordinate<Number> kann zwar wahlweise auf verschiedene Varianten von Objekten zugreifen die parametrisiert mit Koordinate<Number> erzeugt wurden. Sie kann aber nicht auf Objekte gleichen Inhalts aus Koordinate<Integer> zugreifen. Das folgende Implementierungsbeispiel erlaubt nicht die letzte Zuweisung von k2 auf k3:
Koordinate<Double> k1 = new Koordinate<Double>(2.2d, 3.3d); Koordinate<Integer> k2 = new Koordinate<Integer>(2, 3); Koordinate<Number> k3 = new Koordinate<Number>(2l, 3l); k3 = new Koordinate<Number>(4.4f, 5.5f); k3 = k2; // Fehler
Die Vererbungsbeziehung besteht nicht zwischen den generischen Klassen selbst. Der Javaübersetzer erzeugt den folgenden Fehler:
found : Kurs2.Generics.Koordinate<java.lang.Integer> required: Kurs2.Generics.Koordinate<java.lang.Number> k3 = k2;
In diesem Beispiel wird das "Liskov Substitution Principle" verletzt! Der Übersetzer javac erkennt diese Fehler und übersetzt diesen Quellcode nicht.
- 5382 views
Vererbung und generische Klassen
Vererbung und generische KlassenDie Vererbung zwischen generischen Klassen untereinander und mit nicht generischen Klassen ergibt eine zweidimensionale Tabelle von Möglichkeiten:
Oberklasse generisch | Oberklasse nicht generisch | |
---|---|---|
Unterklasse generisch | ||
Unterklasse nicht generisch |
|
Eine generische Klasse erweitert eine generische Klasse
Will man eine generische Klasse aus einer anderen generischen Klasse ableiten so gibt es zwei Möglichkeiten:
- der formale Typ-Parameter der Oberklasse wird weitervererbt
- der formale Typ-Parameter der Oberklasse wird durch einen aktuellen Parameter ersetzt.
Formaler Parameter der Oberklasse ersetzt formalen Parameter der Unterklasse
Bei dieser Form der Vererbung hat die Klassendeklaration der Unterklasse die folgende Noatation:
public class Unterklasse<T> extends Oberklasse<T>
Ein Beispiel ist die Erweiterung der zweidimensionalen Klasse Koordinate zu einer drei dimensionalen Koordinate in der Klasse Koordinate3DGen
package s2.generics;/**
*
* @author s@scalingbits.com
* @param <T> Generischer Typ der Klasse Koordinate
*/
public class Koordinate3DGen<T> extends Koordinate<T> {
private T z;public T getZ() {return z;}
public void setZ(T z) {this.z = z;}public Koordinate3DGen (T x, T y, T z) {
super (x,y);
this.z = z;
}@Override
public String toString() {return super.toString()+", z: "+ z;}public static void main (String[] args) {
Koordinate3DGen<Double> k1 = new Koordinate3DGen<Double>(1.1d, 2.2d, 3.3d);
System.out.println(k1);Koordinate3DGen<Integer> k2 = new Koordinate3DGen<Integer>(1,2,3);
System.out.println(k2);
}
}
Die z Dimension kann in dieser Klasse immer nur mit dem aktuellen Parameter der beiden anderen Dimensionen instanziiert werden.
Formaler Typ-Parameter der Oberklasse wird durch aktuellen Parameter ersetzt
Eine andere Möglichkeit besteht darin den formalen Typparameter der Oberklasse durch einen aktuellen zu ersetzen und gleichzeitig einen neuen formalen Typparameter einzuführen. Hier haben die Klassendeklarationen die folgende Form:
public class Unterklasse<T> extends Oberklasse<konkrete-Klasse>
Ein Beispiel hierfür sei eine zweidimensionale Koordinate die über ein generisches Gewichtsattribut verfügt. Als aktueller Typparameter wird hier der Typ Double ausgewählt:
package s2.generics;
/**
*
* @author s@scalingbits.com
* @param <T> Die Dimensionen der Koordinate
*/
public class Koordinate2DGewicht<T> extends Koordinate<Double> {
private T gewicht;public T getGewicht() {return gewicht;}
public void setGewicht(T g) {gewicht = g;}public Koordinate2DGewicht (Double x, Double y, T g) {
super (x,y);
gewicht = g;
}@Override
public String toString() {return super.toString()+", Gewicht: "+ gewicht;}public static void main (String[] args) {
Koordinate2DGewicht<Double> k1 = new Koordinate2DGewicht<Double>(1.1d, 2.2d, 9.9d);
double dx = k1.getX();
System.out.println(k1);Koordinate2DGewicht<Integer> k2 = new Koordinate2DGewicht<Integer>(1.1d,2.2d,9);
System.out.println(k2);
}
}
Achtung: Der formale Typparamter T der Klasses Koordinate2Gewicht wurde an dieser Stelle neu eingeführt. Er ist ein anderer als der formale Typparameter T der Oberklasse Koordinate!
Generische Unterklasse leitet aus nicht generischer Oberklasse ab
Eine generische Klasse auch eine nicht generische Klasse erweitern. Hier wird der Typ-Parmater neu in die Klassenhierarchie eingeführt. Die Klassendeklarationen genügen dann der folgenden Form:
public class Unterklasse<T> extends Oberklasse
Ein Beispiel hierfür ist eine nicht generische Oberklasse Koordinate2D aus der eine generische Unterklasse Koordinate2DGewichtGen<T> abgeleitet wird:
package s2.generics;
/**
*
* @author s@scalingbits.com
*/
public class Koordinate2D {
private Double x;
private Double y;public Double getX() {return x;}
public void setX(Double x) {this.x = x;}
public Double getY() {return y;}
public void setY(Double y) { this.y = y;}public Koordinate2D(Double xp, Double yp ) {
x = xp;
y = yp;
}@Override
public String toString() {return "x: " + x + "; y: " + y;}public static void main (String[] args) {
Koordinate2D k1 = new Koordinate2D(2.2d, 3.3d);
System.out.println(k1);
}
}
public class Koordinate2DGewichtGen<T> extends Koordinate2D {
private T gewicht;
public T getGewicht() {return gewicht;}
public void setGewicht(T g) {gewicht = g;}public Koordinate2DGewichtGen (Double x, Double y, T g) {
super (x,y);
gewicht = g;
}@Override
public String toString() {return super.toString()+", Gewicht: "+ gewicht;}public static void main (String[] args) {
Koordinate2DGewichtGen<Double> k1 = new Koordinate2DGewichtGen<Double>(1.1d, 2.2d, 9.9d);
double dx = k1.getX();
System.out.println(k1);Koordinate2DGewichtGen<Integer> k2 = new Koordinate2DGewichtGen<Integer>(1.1d,2.2d,9);
System.out.println(k2);
}
}
Nicht generische Unterklasse leitet aus generischer Oberklasse ab
Die letzte Variante besteht aus nicht generischen Klassen die aus einer generischen Oberklasse ableiten in dem sie einen aktuellen Typ-Parameter für die Unterklasse aus der Oberklasse wählen. Die Klassendeklarationen dieser Klassen haben die folgende Schreibweise:
public class Unterklasse extends Oberklasse<konkrete-Klasse>
Ein Beispiel hierfür sei die Klasse Koordinate3DDouble die nicht generisch ist und für die x und y Koordinate die Klasse Koordinate mit dem aktuellen Typparameter Double verwendet:
package s2.generics;
/**
*
* @author s@scalingbits.com
*/
public class Koordinate3DDouble extends Koordinate<Double> {
private Double z;public Double getZ() {return z;}
public void setZ(Double z) {this.z = z;}@Override
public String toString() {return super.toString()+", z: "+ z;}public Koordinate3DDouble (Double x, Double y, Double z) {
super (x,y);
this.z = z;
}public static void main (String[] args) {
Koordinate3DDouble k1 = new Koordinate3DDouble(1.1d, 2.2d, 3.3d);
double d1 = k1.getX(); //Generezität nicht mehr sichtbar
System.out.println(k1);
}
}
- 13254 views
allgemeine Sytax-Regeln
Müssten die Syntax-Regeln nicht eigentlich Oberklasse
- Log in to post comments
Wildcards
Wildcards"Wildcards" sind ein Begriff aus dem Englischen und beziehen sich auf die Joker im Pokerspiel, die vielfältig eingesetzt werden können. Der Begriff wird im Computerumfeld verwendet wenn es um Platzhalter für andere Zeichen geht.
Unbound Wildcards
Die Wildcards werden benötigt um mit Referenzen auf generische Objekte zu zeigen und deren Ausprägung mehr oder weniger allgemein zu definieren. Eine Referenz auf ein Objekt der verwendeten generischen Klasse Koordinate<T> kann man mit einer Wildcard so beschreiben:
Koordinate<?> zeiger;
Ein Fragezeichen ? ist eine "unbound Wildcard". Sie erlaubt es auf jeden beliebigen Typ der Klasse Koordinate<T> zu zeigen.
Beispiel:
package s2.generics; /** * * @author s@scalingbits.com */ public class Koordinate<T> { private T x; private T y; public T getX() {return x;} public void setX(T x) {this.x = x;} public T getY() {return y;} public void setY(T y) { this.y = y;} public Koordinate(T xp, T yp ) { x = xp; y = yp; } @Override public String toString() {return "x: " + x + "; y: " + y;} public static void main (String[] args) { Koordinate<Double> k1 = new Koordinate<Double>(2.2d, 3.3d); System.out.println(k1); Koordinate<Integer> k2 = new Koordinate<Integer>(2, 3); System.out.println(k2); Koordinate<Number> k3 = new Koordinate<Number>(2l, 3l); System.out.println(k3); k3 = new Koordinate<Number>(4.4f, 5.5f); System.out.println(k3); Koordinate<?> zeiger; zeiger = k1; zeiger = k2; zeiger = k3; } } // Ende der Klasse Koordinate
Die Referenzvariable zeiger kann im gezeigten Beispiel auf beliebig parametrisierte Objekte der Klasse Koordinate<T> zeigen.
Wichtig |
---|
Die Wildcard kann nicht in der Sektion des Typ-Parameter einer Klasse, Methode oder Schnittstelle stehen! Sie kann nur im Kontext von Referenzvariablen verwendet werden! |
Umgangssprachlich: Die Wildcard findet man immer nur links des Zuweisungsoperators oder in einer Variablendeklaration.
Die Upper Bound Wildcard
Die bisher verwendeten Typparameter erlauben die Instanziierung einer Klasse mit jeder beliebigen Klasse (die aus der Klasse Object abgeleitet wird). Dies ist oft zu allgemein und kann kontraproduktiv sein.
Bei der Klasse Koordinate macht es keinen Sinn sie mit dem Typ-Parameter Boolean zu parametrisieren:
Koordinate<Boolean> k = new Koordinate<Boolean>(true, true);
Das oben gezeigte Beispiel ist eine korrekte Zuweisung die man übersetzen und ausführen kann. Man kann eine solche Verwendung unterbinden wenn die Typenstruktur Klassenhierachie von Java für Zahlen nutzt:
Man kann die Klasse Number als Oberklasse für alle erlaubten Parametrisierungen wählen und den generischen Typ der Klasse Koordinate<T> einschränken:
public class Koordinate<T extends Number>
Durch diese Notation wird der formale Typparameter auf die Klasse Number oder Spezialisierungen daraus beschränkt. Man spricht von einer "Upper Bound Wildcard" weil die Verwendung von Klassen nach oben (zur Wurzel der Klasse) hin beschränkt ist.
Die Lower Bound Wildcard
Neben der "Upper Bound Wildcard" gibt es auch eine "Lower Bound Wild Card". Sieht wird trotz des ähnlichen Namens, sehr unterschiedlich verwendet.
Sie wird ausschließlich mit der "Unbound Wildcard" in der Schreibweise "? super Lowerbound" bei der Typfestlegung von Referenzen verwendet.
Die generische Klasse selbst ist hier beliebig. Sie sei:
public class MyGenericClass<T> { // Inhalt der Klasse ist nicht wichtig }
Weiterhin sei eine Klassenhierarchie mit Klassen von A bis F gegeben:
mit Hilfe der "Lower Bound Wildcard" kann die Verwendung einer Referenz so eingeschränkt werden, dass nur Instanzen der Klasse C oder Klassen von denen C abgeleitet wurde, verwendet werden darf.
public class TestClass { ... public void testCreate(MyGenericClass<? super C> zeiger) { .... } public void test () { TestClass.testCreate(new MyGenericClass<C>()); // Korrekt TestClass.testCreate(new MyGenericClass<A>()); // Korrekt TestClass.testCreate(new MyGenericClass<Object>()); // Korrekt TestClass.testCreate(new MyGenericClass<F>()); // Fehler!! TestClass.testCreate(new MyGenericClass<D>()); // Fehler!! } }
Die Typ-Parameter der Klassen E, F und B können nicht übergeben werden, da sie nicht in der Typhierarchie der Klasse C vorkommen.
Im Java API werden auch "Lower Bound Wildcards" verwendet. Ein Beispiel ist die drainTo() Methode der Schnittstelle BlockingQueue:
int drainTo(Collection<? super E> c, int maxElements)
- 8712 views
Der "Diamondoperator" in Java
Der "Diamondoperator" in JavaBei der Verwendung generischer Datentypen ensteht zuätzlicher Schreibaufwand, da man den Typ inklusive seines generischen Typs an vielen Stellen nennen muss. Ein Bespiel hierfür ist:
Koordinate<Double> meinPunkt = new Koordinate<Double>(1.1D,2.2D);
Man muss den generischen Typ Double in der Deklaration der Referenzvariable, wie auch im Konstruktoraufruf beim Anlegen des Objekts nennen.
Seit Java 7 ist es erlaubt eine verkürzte Schreibweise zu verwenden. Man nennt diese verkürzte Schreibweise den "Diamondoperator" weil das Kleiner- und Größerzeichen an einen Kristall erinnern. Das obige Beispiel lässt sich auch verkürzt so programmieren:
Koordinate<Double> meinPunkt = new Koordinate<>(1.1D,2.2D);
Der Übsetzer leitet sich die notwendige Typinformation beim Konstruktoraufruf aus dem generischen Typ der Referenzvariable ab.
Bemerkung: Dies ist eine stark verkürzte Erklärung.
Die automatische Bestimmung des Typs durch den Übersetzer kann recht kompliziert sein, da die Konzepte der Vererbung, Interfaces (Schnittstellen), Casts und Autoboxing beachtet werden müssen.
Weiterführende Ressourcen
- 3014 views
Übungen, Fragen (Generics)
Übungen, Fragen (Generics)1. Frage: Instanziierungen
Gegeben seien die folgenden Klassen:
public class Kaefig<T> {
private T einTier;
public void setTier(T x) {
einTier = x;
}
public T getTier() {
return einTier;
} }
public class Tier{ }
public class Hund extends Tier { }
public class Vogel extends Tier { }
Beschreiben Sie was mit dem folgenden Code geschieht. Die Möglichkeiten sind
- er übersetzt nicht
- er übersetzt mit Warnungen
- er erzeugt Fehler während der Ausführung
- er übersetzt und läuft ohne Probleme
1.1
Kaefig<Tier> zwinger = new Kaefig<Hund>();
1.2
Kaefig<Vogel> voliere = new Kaefig<Tier>();
1.3
Kaefig<?> voliere = new Kaefig<Vogel>();
voliere.setTier(new Vogel());
1.4
Kaefig voliere = new Kaefig();
voliere.setTier(new Vogel());
2. Umwandeln einer nicht generischen Implementierung in eine generische Implementierung
In diesem Beispiel werden Flaschen mit verschiedenen Getränken gefüllt und entleert. Das Befüllen und Entleeren erfolgt in der main() Methode der Klasse Flasche.
Die vorliegende Implementierung erlaubt das Befüllen der Flaschen f1 und f2 mit beliebigen Getränken.
Aufgabe:
Ändern Sie die Implementierung der Klasse Flasche in eine generische Klasse die für unterschiedliche Getränke parametrisiert werden kann.
- passen Sie alle Methoden der Klasse Flasche so an das sie mit einem parametrisierten Getränk befüllt werden können (Bier oder Wein)
- Ändern Sie die main() Methode derart, dass f1 nur mit Bier befüllt werden kann und f2 nur mit Wein.
2.1 Klasse Flasche
Die Klasse dient als Hauptprogramm. In dieser Übung müssen ausschließlich die Methoden dieser Klasse und das Hauptprogramm angepasst werden.
package s2.generics;
/**
*
* @author s@scalingbits.com
*/
public class Flasche {Getraenk inhalt = null;
public boolean istLeer() {
return (inhalt == null);
}public void fuellen(Getraenk g) {
inhalt = g;
}public Getraenk leeren() {
Getraenk result = inhalt;
inhalt = null;
return result;
}public static void main(String[] varargs) {
// in generischer Implementierung soll
// f1 nur für Bier dienen
Flasche f1 = new Flasche();
f1.fuellen(new Bier("DHBW-Bräu"));
System.out.println("f1 geleert mit " + f1.leeren());
f1 = new Flasche();
f1.fuellen(new Bier("DHBW-Export"));
System.out.println("f1 geleert mit " + f1.leeren());
// In der generischen Implementierung soll f2 nur für
// Weinflaschen dienen
Flasche f2;
f2 = new Flasche();
f2.fuellen(new Weisswein("Pfalz"));
System.out.println("f2 geleert mit " + f2.leeren());
f2 = new Flasche();
f2.fuellen(new Rotwein("Bordeaux"));
System.out.println("f2 geleert mit " + f2.leeren());
}
}
2.2 Klasse Getraenk
package s2.generics;public abstract class Getraenk {
}
2.3 Klasse Bier
package s2.generics;public class Bier extends Getraenk {
private String brauerei;
public Bier(String b) { brauerei = b;}
public String getBrauererei() {
return brauerei;
}
public String toString() {return "Bier von " + brauerei;}
}
2.4 Klasse Wein
package s2.generics;public class Wein extends Getraenk {
private String herkunft;public String getHerkunft() {
return herkunft;
}public String toString(){ return ("Wein aus " + herkunft);}
public Wein (String origin) {
herkunft = origin;
}}
2.5 Klasse Weisswein
package s2.generics;public class Weisswein extends Wein {
public Weisswein(String h) {super(h);}}
2.6 Klasse Rotwein
package s2.generics;public class Rotwein extends Wein {
public Rotwein(String h) {super(h);}}
3. Typprüfungen
Welche Zeilen in der main() Methode werden vom Übersetzer nicht übersetzt?
Markieren Sie die Zeilen und nennen Sie den Fehler:
package s2.generics;
/**
*
* @author s@scalingbits.com
* @param <T> ein Getraenk
*/
public class KoordinateTest<T extends Number> {public T x;
public T y;public KoordinateTest(T xp, T yp) {
x = xp;
y = yp;
}public static void main(String[] args) {
KoordinateTest<Double> k11, k12;
KoordinateTest<Integer> k21, k22;
Koordinate<String> k31;
KoordinateTest<Number> k41, k42;
KoordinateTest<Float> k81, k82;
k81 = new KoordinateTest<Float>(2.2f, 3.3f);
k12 = new KoordinateTest<Double>(2.2d, 3.3d);
k21 = new KoordinateTest<Integer>(2, 3);
//k31 = new Koordinate<String>("11","22");
k41 = new KoordinateTest<Number>(2l, 3l);
k41 = new KoordinateTest<Number>(4.4d, 5.5f);
k11 = new KoordinateTest<Double>(3.3d,9.9d);
KoordinateTest<? super Double> k99;
k99 = k11;
k99 = k41;
k99 = k31;
k11 = k12;
k12 = k21;
KoordinateTest k55 = new KoordinateTest<Number>(7.7f, 8.8f);
KoordinateTest k66 = new KoordinateTest(7.7d, 3.d);
}
}
- 10684 views
Lösungen, Antworten
Lösungen, Antworten1. Antwort: Instanziierungen
Gegeben seien die folgenden Klassen:
public class Kaefig<T> { private T einTier; public void setTier(T x) { einTier = x; } public T getTier() { return einTier; } public class Tier{ } public class Hund extends Tier { } public class Vogel extends Tier { } }
Beschreiben Sie was mit dem folgenden Code geschieht. Die Möglichkeiten sind
- er übersetzt nicht
- er übersetzt mit Warnungen
- er erzeugt Fehler während der Ausführung
- er übersetzt und läuft ohne Probleme
1.1
Kaefig<Tier> zwinger = new Kaefig<Hund>();
Übersetzungsfehler: Hund ist zwar eine Unterklasse von Tier. Kaefig<Tier> und Kaefig<Hund> sind keine kompatiblen Typen.
1.2
Kaefig<Vogel> voliere = new Kaefig<Tier>();
Übersetzungsfehler: Vogel ist zwar eine Unterklasse von Tier. Kaefig<Tier> und Kaefig<Vogel> sind keine kompatiblen Typen.
1.3
Kaefig<?> voliere = new Kaefig<Vogel>(); voliere.setTier(new Vogel());
Übersetzungsfehler in der zweiten Zeile. Die erste Zeile ist in Ordnung. Man kann einen Kaefig eines unbekannten aktuellen Parametertyps erzeugen. Die zweite Zeile kann nicht übersetzen, da der Übersetzer nicht wissen kann welche Tiere in voliere verwaltet werden sollen. Der folgende Code würde übersetzen:
Kaefig<?> voliere = new Kaefig<Vogel>(); Kaefig<Vogel> k= new Kaefig<Vogel>(); k.setTier(new Vogel()); voliere=k;
Anmerkung: Man darf nur Referenzen auf Zeiger mit Wildcards (hier voliere) zuweisen. Man darf keine Objektmethoden aufrufen weil hierfür der Typ zur Übersetzungszeit nicht bekannt ist.
1.4
Kaefig voliere = new Kaefig(); voliere.setTier(new Vogel());
Der Übersetzer übersetzt den Quellcode mit einer Warnung. Er kann nicht wissen welchen Typ er benutzt. Er erzeugt deshalb eine Warnung, weil es beim Zuweisen eines Vogels zu Problemen kommen kann. Man verliert die Vorteile der Typprüfung der generischen Klassen. Dieser Codierstil sollte deshalb vermieden werden.
2. Umwandeln einer nicht generischen Implementierung in eine generische Implementierung
package s2.generics; /** * * @author s@scalingbits.com * @param <T> ein Getraenk */ public class FlascheGeneric<T extends Getraenk> { T inhalt = null; public boolean istLeer() { return (inhalt == null); } public void fuellen(T g) { inhalt = g; } public T leeren() { T result = inhalt; inhalt = null; return result; } public static void main(String[] varargs) { // in generischer Implementierung soll // f1 nur für Bier dienen FlascheGeneric<Bier> f1 = new FlascheGeneric<Bier>(); f1.fuellen(new Bier("DHBW-Bräu")); System.out.println("f1 geleert mit " + f1.leeren()); f1 = new FlascheGeneric<Bier>(); f1.fuellen(new Bier("DHBW-Export")); System.out.println("f1 geleert mit " + f1.leeren()); // In der generischen Implementierung soll f2 nur für // Weinflaschen dienen FlascheGeneric<Wein> f2; f2 = new FlascheGeneric<Wein>(); f2.fuellen(new Weisswein("Pfalz")); System.out.println("f2 geleert mit " + f2.leeren()); f2 = new FlascheGeneric<Wein>(); f2.fuellen(new Rotwein("Bordeaux")); System.out.println("f2 geleert mit " + f2.leeren()); } }
3. Typprüfungen
Welche Zeilen in der main() Methoder werden vom Übersetzer nicht übersetzt?
Markieren Sie die Zeilen und nennen Sie den Fehler:
package s2.generics; public class KoordinateTest<T extends Number> { public T x; public T y; public KoordinateTest(T xp, T yp) { x = xp; y = yp; } public static void main(String[] args) { KoordinateTest<Double> k11, k12; KoordinateTest<Integer> k21, k22; //Koordinate<String> k31; Die Klasse String ist nicht aus Number abgeleitet. Siehe extends Klausel KoordinateTest<Number> k41, k42; //k11 = new KoordinateTest<Float>(2.2d, 3.3d); Die Eingabeparameter sind vom Typ double und nicht vom Typ Float k12 = new KoordinateTest<Double>(2.2d, 3.3d); k21 = new KoordinateTest<Integer>(2, 3); //k31 = new Koordinate<String>("11","22"); Nicht erlaubt, da der Typ weiter oben nicht für die Variable erlaubt war k41 = new KoordinateTest<Number>(2l, 3l); k41 = new KoordinateTest<Number>(4.4d, 5.5f); //k11 = new Koordinate<Double>(3.3f,9.9d); Der erste Parameter ist ein Float und nicht ein Double wie gefordert KoordinateTest<? super Double> k99; //k99 = k11; Nicht erlaubt, da der Typ weiter oben nicht für die Variable erlaubt war k99 = k41; //k99 = k31; Nicht erlaubt, da der Typ weiter oben nicht für die Variable erlaubt war k11 = k12; //k12 = k21; k21 ist vom Typ KoordinateTest<Integer>. k12 muss aber vom Typ KoordinateTest<Double> sein KoordinateTest k55 = new KoordinateTest<Number>(7.7f, 8.8f); KoordinateTest k66 = new KoordinateTest(7.7f, 8.8f); } }
- 7123 views
Beispiel: Von einer nicht generischen Klasse zu einer generischen Klasse
Beispiel: Von einer nicht generischen Klasse zu einer generischen KlasseDie Klassen MerkerX implementieren eine Warteschlange der Länge 2. Sie ist in der Lage sich den letzten und den vorletzten Wert zu merken.
Im Folgenden erhalten die Klassen neue Namen (Merker2, Merker3 etc.). Dies erlaubt die Klassen gleichzeitig im Paket zu verwalten
Von Basistypen zu Objekten
Die Klasse Merker1 ist in der Lage Basistypen vom Typ int zu verwalten. Die Werte werden als Teil der Instanzen der Klasse Merker1 verwaltet.
Die Klasse Merker2 verwaltet Objekte vom Typ Integer. Verschiedene Instanzen von Merker2 könne also auf die gleichen Instanzen der Klasse Integer zeigen.
Klasse Merker1 | Klasse Merker2 |
---|---|
package Kurs2.Generics; |
package Kurs2.Generics; |
Von Integer-Objekten zur Verwaltung beliebiger Objekte
Die Klasse Merker3 kann nun nicht nur Instanzen der Klasse Integer verwalten. Sie kann beliebige Objekte verwalten.
Klasse Merker2 | Klasse Merker3 |
---|---|
package Kurs2.Generics; |
package Kurs2.Generics; |
Von der unsicheren Verwaltung beliebiger Objekte zu einer generischen Klasse
Die Klasse Merker4 ist jetzt generisch. Sie kann typsicher Objekte verwalten. In der main() Methode der Klassse Merker4 wird jedoch diese neue gewonnen Fähigkeit ignoriert. Die Klasse Merker4 wird benutzt wie eine Klasse die beliebige Typen verwalten kann.
Klasse Merker3 | Klasse Merker4 |
---|---|
package Kurs2.Generics; public class Merker3 { |
package Kurs2.Generics; public class Merker4<T> { |
Typsichere Nutzung der generischen Klasse
Die Klasse Merker5 ist mit Ausnahme der main() Methode identisch zur Klasse Merker4. In der main() Methode der Klassse Merker4 werden jetzt Objekte f1, f2, f3 angelegt die nur die Verwaltung von bestimmten Typen in der Klasse Merker5 erlauben. Da die aktuellen Typen von f1, f2, f3 unterschiedlich sind kann man Sie nicht mehr beliebig aufeinander zuweisen.
Klasse Merker4 | Klasse Merker5 |
---|---|
package Kurs2.Generics; public class Merker4<T> { |
package Kurs2.Generics; |
Die Klassenhierarchie der verschiedenen Varianten der generischen Klasse Merker5:
Einschränkung der generischen Klasse Merker6 auf bestimmte Typen
Die Klasse Merker6 verwendet das Schlüsselwort extends um die Verwendung auf zahlenartige Typen (Number) einzuschränken. Die Einschränkungen bei den Zuweisungen zwischen den Variablen f1, f2, und f3 sind zu beachten.
Anbei die Klassen Hierarchie des Java API zur Klasse Number und ihrer Unterklassen:
Klasse Merker4 | Klasse Merker5 |
---|---|
package Kurs2.Generics; |
package Kurs2.Generics; public class Merker6<T extends Number> { private T letzter; private T vorletzter; |
Wild Cards
Die Klasse Merker7 benutzt in der main() Methode eine Variable merkeAlles die mit einer Wildcard versehen ist.
Die Variable kann auf alle Varianten der Klasse Merker7 zeigen, sie kann aber nicht alle Methoden nutzen da die Methoden zur Übersetzungszeit nicht bekannt sein müssen.
package Kurs2.Generics;
public class Merker7<T extends Number> {private T letzter;
private T vorletzter;public Merker7() {
letzter = null;
vorletzter = null;
}public T getLetzter() {
return letzter;
}public T getVorLetzter() {
return vorletzter;
}public void merke(T wert) {
vorletzter = letzter;
letzter = wert;
}public String toString() {
return "[" + letzter + ";" + vorletzter + "]";
}public static void main(String[] args) {
// Teil 1: Integer Verwalten
Merker7<Integer> f1 = new Merker7<Integer>();
Integer i10 = 10; // Erzeugung mit Autoboxing
f1.merke(i10);
Integer i11 = new Integer(11); // Reguläre Erzeugung
f1.merke(i11);
Integer i12 = new Integer(12); // Reguläre Erzeugung
f1.merke(i12);
System.out.println(f1);
// Teil 2: Float verwalten
Merker7<Float> f2 = new Merker7<Float>();
Float i20 = 100.1f;
f2.merke(i20);
Float i21 = 101.2f;
f2.merke(i21);
Float i22 = 102.3f;
f2.merke(i22);
System.out.println(f2);
// Teil 3: Alles Verwalten
Merker7<Number> f3 = new Merker7<Number>();
Integer i30 = 200;
f3.merke(i30);
Float i31 = 201.f;
f3.merke(i31);
String i32 = "Zeichenkette";
//f3.merke(i32); // Warum ist dies nicht erlaubt?
System.out.println(f3);
// Teil 4: Alles Verwalten
Merker7 f4 = new Merker7();
Integer i40 = 400;
f4.merke(i40);
Float i41 = 401.f;
f4.merke(i41);
String i42 = "Zeichenkette";
//f4.merke(i42); // Warum ist dies nicht erlaubt?
System.out.println(f4);
f2 = f4;
Merker7<?> merkeAlles;
merkeAlles = f1;
// Hier wird implizit die Methode toString() aufgerufen
System.out.println(merkeAlles);
int m;
// Warum ist diese Zuweisung nicht erlaubt?
//m = merkeAlles.letzter;
// Warum ist diese Zuweisung erlaubt?
m = 3 * f1.letzter;
System.out.println(merkeAlles.letzter);
merkeAlles = f2;
System.out.println(merkeAlles);
merkeAlles = f3;
System.out.println(merkeAlles);
}
}
- 4915 views
Lernziele (Generics)
Lernziele (Generics)
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu beantworten: Übungsfragen zu generischen Typen
- 3423 views
Java Collections Framework
Java Collections FrameworkDer Begriff "Collection" wird in Java für alle Klassen verwendet, mit denen man Objekte zusammenfassen und verwalten kann. Die Java-Collections sind Großbehälter die genau gesehen, Referenzen auf Objekte verwalten.
Felder (Arrays) sind ein Teil der Javasprache und erlauben die extrem effiziente Verwaltung von statischen Daten über ihren Feldindex. Felder erlauben nur die Verwaltung von Daten eines genauen Typs mit einer vorher festgelegten Größe. Das Java Collections Framework hat nicht diese Einschränkungen und erlaubt die Verwaltung von Objekten vieler Arten. Das Java Collections Framework ist daher sehr interessant, wenn man vorab nicht weiß wieviele Objekte man verwalten, oder die Objekte nach ganz bestimmten Kriterien verwaltet werden müssen.
Die Klassen im Java Collection Framework implementieren alle gängigen abstrakten Datenstrukturen wie sie in der Vorlesung vorgestellt wurden.
Die Klassen des Java Collection Framework's sind im Paket java.util zu finden. Sie bestehen aus den drei Gruppen:
- Algorithmen: effiziente Algorithmen zur Verwaltung von Objekten
- Implementierungen: Klassenhierarchie mit abstrakten Klassen zur Implementierung eigener Klassen und konkrete Implementierungen zur direkten Benutzung
- Schnittstellen (Interfaces): Hierarchie von Schnittstellen zur einheitlichen Behandlung von Objekten über die Collectionklassen hinweg. Die Interfaces des Collection Frameworks werden hier wie abstrakte Datentypen verwendet.
Generizität
Alle Klassen des Java Collection Framework sind seit JDK 5.0 generische Klassen. Hierdurch können die Klassen sehr viel allgemeiner verwendet werden. Es gibt weniger Situationen in denen man die Klassen selbst spezialisieren muss um Typsicherheit zu gewährleisten.
Die Benutzung generischer Typen hat die folgenden Vorteile:
- Rückgabewerte werden typsicher
- Hierdurch geht die Typinformation der verwalteten Objekte nicht verloren
- Es werden keine expliziten Typkonversionen (Casts) mehr benöigt um beim Auslesen von Objekten diese zuzuweisen.
- 8377 views
Überblick Java Collections
Überblick Java CollectionsDie Klassen des Java Collection Framework in java.util stellen vier Familien von abstrakten Datentypen und Funktionen zur Verfügung:
- Listen (abstrakte Klasse List): geordnete Datenstrukturen auf die man wie auf Felder mit einem numerischen Index zugreifen kann. Im Unterschied zu Feldern (Array) erlauben sie das listentypische Einfügen an einer beliebigen Stelle
- Mengen (abstrakte Klasse Set): Eine Implementierung mathematischer Mengen. Objekte können nur einmal in einer Menge vorkommen. Man kann prüfen ob bestimmte Objekte in einer Menge enthalten sind. Eine Reihenfolge oder Ordnung in der Menge ist nicht relevant.
- Verzeichnisse (abstrakte Klasse Map): Verzeichnisse können als verallgemeinerte Felder angesehen werden. Felder erlauben einen direkten zugriff mit einem numerischen Index. Verzeichnisse erlauben die Wahl einer beliebigen Zugriffskriteriums. Man kann zum Beispiel Personen nach ihrem Nachnamen verwalten und eine Person mit Hilfe eines gegebenen Nachnamens aurufen.
- Warteschlangen (abstrakte Klasse Queue): Warteschlangen sind Listen die nach dem FIFO Prinzip (First In , First Out) aufgebaut sind. Sie verfügen über keinen wahlfreien Zugriff.
Hinzu kommt die Hilfsklasse Arrays zum Verwalten von Feldern. Die Klasse fasst viele nützliche statische Methoden zum Suchen und Sortieren von Feldern zusammen. Die Klasse Arrays gehört zu diesem Paket, da Felder logisch zu den abstrakten Datentypen gehören. Da Felder wegen der benötigten Effizienz ein Hybrid zwischen Basistypen und komplexen Typen sind, passen Sie nicht sauber in die hier vorgestellte Vererbunghierarchie.
Die vier Familien werden durch die folgende Hierarchie von Schnittstellen in Java implementiert:
Jede dieser Familien wird durch eine abstrakte Klasse und eine Schnittstelle implementiert. Die Aufteilung in diese beiden Aspekte findet nach dem folgenden Prinzip statt:
- Schnittstelle: Sie definiert welche Operationen für eine bestimmte Familie erlaubt sind (funktionale Eigenschaft). Diese funktionalen Eigenschaften sind:
- sortiert oder unsortiert
- geordnet oder ungeordnet. Bleibt die Einfügereihenfolge erhalten?
- sequenziell oder wahlfreier Zugriff
- abstrakte Basisklasse: Die abstrakte Klasse implementiert die Schnittstelle und bestimmt damit den Algorithmus, der zu einer bestimmten Effizienz führt.
Zur Auswahl der optimalen Klasse kann man sich an der folgenden Tabelle von Klassen und Eigenschaften orientieren:
Klasse | Famlie (implem. Schnittst.) | Zugriff | Duplikate erlaubt | Ordnung | Kommentar |
---|---|---|---|---|---|
ArrayList | Liste(List) | wahlfrei über Index | Ja | geordnet | effizientes Lesen über Index |
LinkedList | Liste(List) | wahlfrei über Index | Ja | geordnet | effizientes Einfügen |
Vector | Liste(List) | wahlfrei über Index | Ja | geordnet | synchronisiert(!) |
Stack | Liste(List) | sequentiell (letztes Element) | Ja | geordnet | LIFO: Last In First Out |
HashSet | Menge(Set) | ungeordnet; Test auf Existenz | Nein | ungeordnet | schnelles Lesen, Einfügen, Löschen |
TreeSet | Menge(Set) | sortiert; Test auf Existenz | Nein | sortiert | |
LinkedList | Warteschlange(Queue) | sequentiell, Nachfolger | Ja | geordnet | effizientes Einfügen (am Rand) |
PriorityQueue | Warteschlange(Queue) | sequentiell, Nachfolger | Ja | sortiert | |
HashMap | Verzeichnis(Map) | wahlfrei über Schlüssel | Keine Schlüsselduplikate, Werteduplikate erlaubt | ungeordnet | effizientes Lesen, Einfügen, Löschen |
TreeMap | Verzeichnis(Map) | wahlfrei über Schlüssel | Keine Schlüsselduplikate, Werteduplikate erlaubt | sortiert |
In Bezug auf die Spalte Ordnung:
- geordnet: Elemente tauchen beim Iterieren in der gleichen Reihenfolge auf
- sortiert: Elemente tauchen beim Iterieren entsprechend ihre Kardinalität auf.
Frage: Die Klasse LinkedList taucht in der Tabelle zweimal auf. Wieso eigentlich? |
Iteratoren
Neben dem spezifischen Zugriff auf diese abstrakten Datentypen bietet das Java Collection Framework Iteratoren die es Erlauben, nacheinander alle Objekte einer Collection auszulesen. Hierzu dient die Schnittstelle Iterator. Iteratoren werden hier im Detail im Skript vorgestellt.
Beispiel
Im folgenden Beispiel werden Studenten in einer verzeigerten Liste verwaltet. Die Implementierung ist eine nicht generische Verwendung.
Klasse LinkedListDemo | Klasse Student |
---|---|
package s2.collection; import java.util.Iterator; /** public static void main(String[] args) { System.out.println("Element auf Index 1:" + ll.get(1)); Iterator<Student> iter = ll.iterator(); System.out.println("Inhalt der Liste:"); |
package s2.collection; public class Student { int matrikelnr; String name; public Student (String n, int nr) { public String toString() {
|
Konsolenausgabe:
Element auf Index 1:(17,Merian Maria-Sybilla, 3) Inhalt der Liste: Student: (19,Curie Marie, 1) Student: (17,Merian Maria-Sybilla, 3) Student: (16,Noether Emmi, 1) Student: (15,Herschel Caroline, 2) Inhalt der Liste: Student: (19,Curie Marie, 1) Student: (17,Merian Maria-Sybilla, 3) Student: (16,Noether Emmi, 1) Student: (15,Herschel Caroline, 2)
In diesem Beispiel wurde eine gängige Technik angewendet:
- Die Variable ll ist vom Schnittstellentyp List.
- Die erzeugte Instanz ist vom Typ LinkedList. LinkedList ist eine spezielle Implementierung die man bei Bedarf gegen eine andere Implementierung austauschen kann ohne die anschließende Verwendung der Variable ll zu modifizieren. Dies würde hier durch die Auswahl der Klasse ArrayList geschehen. Man muss hierzu in der Methode main() nur eine Zeile ändern um das Laufzeitverhalten zu ändern:
List ll = new ArrayList();
- 38056 views
Überbl J-Collections Konsolenausgabe & EIngabe stimm nicht übere
Im Bereich wo die angebliche Konsolenausgabe abgebildet sein sollt, passt das Ergebnis also die Konsolenausgabe weder zu den Eingabe Parametern, noch zu der Klasse und damit Bedingung für die Parameterliste die für "new Student" gilt, da die Signaturen unterschiedlich sind.
In der Klasse Student haben wir eine Signatur Student(String, int)
In der LinkedListIterDemo Klasse haben wir aber eine Signatur von folgender Art Student(String, String, int, int) und in der Konsolenausgabe ist die Signatur Student(int, String, String, int).
- Log in to post comments
Schnittstellen (Interfaces) des Collection Framework
Schnittstellen (Interfaces) des Collection FrameworkZiel des Java Collections Frameworks ist es alle wichtigen abstrakten Datentypen als Schnittstellen für den Entwickler anzubieten.
Der Entwickler soll möglichst nur gegen die Methoden der Schnittstelle implementieren. Dies erlaubt es bei der Objekterzeugung eine optimale Implementierung des entsprechenden Datentyps zu wählen. Aus diesem Grund werden in der Vorlesung die zu benutzenden Schnittstellen immer gemeinsam mit den unterschiedlichen Implementierungen beschrieben. Anbei die Hierachie der Schnittstellen, die aus der gemeinsamen Schnittstelle java.util.Collection abgeleitet werden:
Maps sind sehr komplexe Klassen. Sie werden Aufgrund ihres sehr speziellen Zugriffs mit freigewählten Schlüsseln nicht aus der Klasse java.util.Collection spezialisiert.
Schnittstellen und Implementierungen
Entwickler sollten wann immer möglich gegen die Schnittstellen (z.Bsp. Interface List) implementieren und nicht gegen die Implementierungen ( z.Bsp. Klasse ArrayList, LinkedList). Hierdurch können Sie Implementierung ihres Codes leichter an geänderte Anforderungen anpassen. Sie müssen dann nur bei der Objekterzeugung einen anderen Typ verwenden.
Guter Stil
List meineListe = new ArrayList<String>(); // Später im Code: meineListe.add(0,"DHBW"); meineListe.add(0,"Mannheim"); List neueReferenz = meineListe;
Häufiges Einfügen am Anfang der Liste ist bei einer ArrayList sehr aufwendig. Für den Wechsel der Implementierung muß nur die Erzeugung des Objekts in der ersten Zeile geändert werden:
List meineListe = new LinkedList<String>();
Schlechter Stil
ArrayList meineListe = new ArrayList<String>(); // Später im Code: meineListe.add(0,"DHBW"); meineListe.add(0,"Mannheim"); ArrayList neueReferenz = meineListe;
Wählen Sie als Datentyp List für meineListe, können Sie in der Regel nicht absehen wie diese Referenz später verwendet wird. Es können die folgenden Probleme beim Wechsel der Implementierung auftreten:
- Zuweisungen auf andere Variablen haben Typprobleme (siehe Beispiel)
- Es wurden eventuell Methoden verwendet die es nur in der Implementierung der Klasse ArrayList gab und nicht in der Klasse LinkedList.
- 10106 views
Listen (Collections)
Listen (Collections)Das Java Collections Framework bietet eine Schnittstelle List<E> mit der man uniform auf verschiedene Implementierungen von Listen zugreifen kann.
Listen sind geordnete Folgen die man in der Informatik als Sequenz (engl. sequence) bezeichnet.
Die Schnittstelle List<E> definiert die Methoden mit denen man auf Listen zugreifen kann:
- public void add(int index, E element): fügt ein Element auf einer gegebenen Position ein. Die Nachfolger werden verschoben.
- public E get(int index): wahlfreier Zugriff auf ein Objekt auf eine Postion index
- public E set (int index, E element): wahlfreier schreibender Zugriff auf ein Listenobjekt
- public List<E> sublist(int von, int bis): Erzeugt eine Teilliste im vorgegebenen Intervall
- public E remove(int index): Entfernen eines Objekts von der Position index.
Die Schnittstelle List<E> besitzt noch mehr Methoden als die hier aufgeführten. Sie sind in der API Dokumentation zu finden.
Das Java Collections Framework bietet zwei empfohlene Implementierungen und die Klasse Vector für diese Schnittstelle an, die nach Bedarf gewählt werden können:
Klasse ArrayList
Fie Klasse ArrayList ist im Normalfall die bessere Wahl. Sie verhält sich wie ein Feld (Array). Im Unterschied zu einem Feld kann man die Größe jedoch dynamisch verändern. Die Operationen zum dynamischen Ändern der Größe sind nur in der Klasse ArrayList zu finden. Benutzt man die Schnittstelle List<E> hat man keinen Zugriff auf die zusätzlichen Methoden der Klasse ArrayList .
Die Klasse ArrayList verhält sie weitgehend wie die Klasse Vector. Die Klasse ArrayList ist jedoch nicht synchronisiert.
Eigenschaften:
- Methoden mit einem konstanten Aufwand bei der Ausführung: size(), isEmpty(), get(), set(), iterator(), listIterator()
- Die Methode add() wird im Durchschnitt mit einer konstanten Zeit ausgeführt
- Alle anderen Methoden haben einen linearen Aufwand mit einem niedrigen konstanten Faktor im Vergleich zur Klasse LinkedList.
Klasse Vector
Die Klasse Vector verhält sich wie die Klasse ArrayList . Die Klasse Vector ist historisch älter als das Java Collections Framework. Sie wurde in JDK 1.2 eingeführt und dann später überarbeitet um mit den Schnittstellen des Java Collections Framework konform zu sein. Die Klasse ist daher aus Kompatibilitätsgründen synchronisiert. Hierdurch ist sie zum einen sicher bei der Verwendung bei Anwendungen mit mehreren Threads. Sie ist hierdurch aber auch deutlich langsamer. Die Klasse Vector sollte in neuen Implementierungen zugunsten der Klasse ArrayList vermieden werden.
Klasse LinkedList
Die Klasse LinkedList ist eine Implementierung einer doppelt verzeigerten Liste.
Sie ist sehr gut geeignet für Anwendungen in denen sehr oft Elemente eingefügt oder entnommen werden müssen. Beim indexierten, wahlfreien Zugriff auf ein Objekt muss jedoch die Liste traversiert werden.
Die Klasse LinkedList ist wie die anderen Klassen des Java Collections Frameworks nicht synchronisiert.
- 7189 views
Mengen (Collection)
Mengen (Collection)Das Java Collections Framework stellt mit der Schnittstelle Set<E> einen Behälter zur Verfügung der keine Elemente mehrfach enthält. Die Schnittstelle Set<E> stellt die folgenden Methoden für die unterschiedlichen Implementierungen zur Verfügung:
package java.util; public interface Set<E> extends Collection<E> {
// Grundlegende Operationen
int size();
boolean isEmpty();
boolean contains(Object element);
boolean add(E element);
boolean remove(Object element);
Iterator<E> iterator(); // Massenoperationen boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); // Feld Operationen Object[] toArray(); <T> T[] toArray(T[] a); }
Neben der Schnittstelle Set<E> existiert auch eine Schnittstelle SortedSet<E> in der die Mengenelemente nach ihrer natürlichen oder einer definierten Ordnung sortiert sind.
package package java.util; public interface SortedSet<E> extends Set<E> {
// Range-view
SortedSet<E> subSet(E fromElement, E toElement);
SortedSet<E> headSet(E toElement);
SortedSet<E> tailSet(E fromElement); // Endpoints E first(); E last(); // Comparator access Comparator<? super E> comparator(); }
Als konkrete Implementierungen können die folgenden Klassen benutzt werden:
Klasse TreeSet
Die Klasse TreeSet verwaltet alle Elemente in ihrer natürlichen Ordnung oder in der Ordnung die mit der Schnittstelle Comparator definiert wurde.
Die Kosten für die Zugriffsmethoden add(), remove(), contains() liegen bei O(log(n)).
Die Klasse TreeSet ist nicht synchronisiert.
Anwendungsfälle:
- Man muß über die Menge mit einem bestimmten Schlüsselkriterium iterieren können
- Die erhöhten Kosten für Einfügen und Entfernen von Objekten sind nicht relevant.
Klasse EnumSet
Die Klasse EnumSet ist eine Spezialimplementierung für Aufzählungen (enum). Der Aufzählungstyp enum ist extrem effizient mit Hilfe von Bitvektoren implementiert. Die Klasse EnumSet unterstützt extrem effiziente Mengenoperationen auf einem gegegebenen Aufzählungstyp.
Die Iteratoren aller anderen Collections sind "fail safe". Das heißt sie werfen eine ConcurrentModificationException Ausnahme falls die Collection an mehreren Stellen gleichzeitig modifiziert wird. Dies ist nicht der Fall bei den Iteratoren der Klasse EnumSet. Ihre Iteratoren sind nur "weakly consistent" (siehe Dokumentation im API).
Klasse HashSet
Die Klasse HashSet verwendet intern eine Implementierung einer HashMap. Die Klasse HashSet hat keine Ordnung der Elemente. Das bedeutet, dass beim Iterieren über die Menge die Elemente der Menge nicht in einer sortierten Reihenfolge präsentiert werden. Es gibt nicht einmal eine Garantie, dass bei mehrfachem Iterieren über die Menge die Reihenfolge konstant ist.
Der Zugriff der Methoden add(), remove(), contains(), size() haben einen konstanten Aufwand. Diese Operationen sind bei großen Mengen schneller als die Methoden der Klasse TreeSet.
Anwendungsfälle:
- Einfüge-, Entfernungs- und Leseoperationen sollen sehr effizient sein
- Die Ordnung der Menge nach einem Schlüsselkriterium spielt keine Rolle
- 7983 views
Iteratoren
IteratorenIteratoren sind ein universelles Konzept des Java Collections Framework um alle Objekte einer Collection auszulesen. Sie erlauben es Objekte genau einmal aus einer Collection auszulesen und festzustellen ob es noch weitere Elemente gibt.
Iterator ist eine Schnittstelle die drei Methoden für alle Collections anbietet:
- hasNext(): Die Collection hat noch weitere Objekte die ausgelesen werden können
- next(): Liefert das nächste Objekt der Collection
- remove(): entfernt das letzte Objekt welches aus einer Collection gelesen wurde.
Jedes Collectionobjekt ist in der Lage ein Iteratorobjekt mit Hilfe der iterator() Methode anzubieten. Dies kann wie im folgenden Bespiel geschen:
List<Student> ll = new LinkedList<Student>();
... Iterator<Student> iter = ll.iterator();
Beispiel
Der Iterator iter erlaubt über die generische Liste mit Studenten zu iterieren und alle Objekte der Liste auszulesen:
package s2.collection;import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;public class LinkedListIterDemo {
public static void main(String[] args) {
List<Student> ll = new LinkedList<>();
Student s;
s = new Student("Curie","Marie", 19,1);
ll.add(s);
s = new Student("Merian","Maria-Sybilla", 17,3);
ll.add(s);
s = new Student("Noether","Emmi", 16,1);
ll.add(s);
s = new Student("Herschel","Caroline", 15,2);
ll.add(s);
Iterator<Student> iter = ll.iterator();
System.out.println("Inhalt der Liste:");
while (iter.hasNext()){ // Prüfen ob noch Elemente vorhanden sind
s = iter.next(); // Holen des nächsten Listenelements
System.out.println("Student: " +s);
}
}
}
Iterieren mit der erweiterten for-Schleife
Seit JDK 5 kann man auch mit der Syntax der erweiterten for-Schleife über alle Objekte einer Collection iterien.
Der im vorherigen Beispiel vorgestellte Iterator mit der while-Schleife kann sehr elegant mit der erweiterten for-Schleife implementiert werden:
System.out.println("Inhalt der Liste:");
for (Student st : ll)
System.out.println("Student: " +st);
Der Iterator ist hier in die for-Schleife integriert.
Vorsicht: Stabilität von Iteratoren und Collections
Beim Bearbeiten einer Collection mit einem Iterator darf die Collection selbst nicht verändert werden. Operationen mit dem aktuellen Operator sind jedoch als Ausnahme erlaubt. Wird eine Collection beim iterieren verändert so wird eine ConcurrentModifikationException geworfen.
- 6226 views
Verzeichnisse-Maps (Collections)
Verzeichnisse-Maps (Collections)Die Schnittstelle Map im Java Collections Framework erlaubt die Benutzung von Verzeichnissen (engl. Dictionary). Verzeichnisse sind Datenstrukturen in denen man Objekte organisiert nach einem beliebigen Schlüssel verwalten kann. Maps sind Modelle mathematischer Funktionen die einen gegebenen Wert einer Schlüsselmenge auf eine Zielmenge abbilden.
Maps funktionieren wie ein Wörterbuch. Man kann man sie als Tabellen mit zwei Spalten verstehen. In der ersten Spalte steht der Zugriffsschlüssel mit dem man nach einem Objekt sucht, in der zweiten Spalte steht das Objekt welches zu einem gegebenen Schlüssel gehört. Hierdurch werden zwei Objekte in eine Beziehung gesetzt.
Anwendungsbeispiel
In einer Java-Map kann man zum Beispiel Kraftfahrzeuge und deren Halter verwalten. Da ein Kraftfahrzeug genau einen Halter hat, verwendet man das Kraftfahrzeug als Schlüsselobjekt. Die Person die das Fahrzeug besitzt wird als Wert verwaltet. Fügt man dieses Tupel von Schlüsselobjekt und Zielobjekt in die Datenstruktur ein, kann man zu jedem gegebenen Fahrzeug den Halter ausgegeben.
In Java-Maps müssen die Schlüssel eindeutig sein. Jedes Kfz kann daher nur einmal vorkommen. Die verwalteten Werte müssen nicht eindeutig sein. Im Beispiel oben besitzt die Person Müller zwei Kfz.
Abgrenzung zu Objektattributen
Die oben aufgeführten Beispiel benutzte Beziehung zwischen einem Kraftfahrzeug und einem Halter kann man natürlich ebenso gut direkt mit Hilfe von Referenzen in den zwei Klassen modellieren.
Der Vorteil einer Java Map besteht darin, dass man diese Beziehung dynamisch in einem Javaobjekt modelliert und nicht permanent als Objekteigenschaft. Das Verwalten dieser Beziehung in einem Objekt hat die folgenden Vorteile:
- Man kann die Beziehung modellieren ohne die Klassen zu modifizieren
- Man kann jederzeit neue Beziehungen modellieren (z.Bsp Kraftfahrzeug<->Fahrer, Kraftfahrzeug<->Betreuer etc.)
- Man kann zusätzliche Operationen des Java Collection Frameworks ausnutzen um die Daten zu bearbeiten. Man kann z.Bsp.
- über alle Schlüssel iterieren (was ist die Menge der Kraftfahrzeuge?)
- über alle Objekte iterieren (was ist die Menge der Halter? Es kann mehrere Krfatfahrzeuge für einen gegebenen Halter existieren!)
Klassenstruktur
Die Map-Klassen des Java Collections Framework werden nicht wie die anderen Klassen des Frameworks aus der Klasse Collection abgeleitet. Man kann jedoch mit Hilfe der Methoden keySet() und values() über die die Schlüssel oder über die eigentlichen Objekte iterieren.
Klasse HashMap
HashMap<K,V> benutzt wie die Klasse HashSet einen Hash-Code für die Schlüssel mit deren Hilfe die Objekte verwaltet werden. Dadurch hat sie auch die gleichen Leistungseigenschafen wie die Klasse HashSet (in Bezug auf die Schlüssel).
Die Klasse HashMap erlaubt es beliebige Klassen als Schlüssel zu verwenden. Die einzige Bedingung ist, dass die Methoden equals() und hashCode() für diese Klassen implementiert sind (triviale Implementierungen existieren bereits in der Klasse Object).
Klasse TreeMap
Die Klasse TreeMap implementiert die Schnittstelle SortedMap. Hierdurch wird sicher gestellt, das Objekte beim Einfügen direkt sortiert eingefügt werden. Die Schlüssel sind also bereits sortiert und erlauben eine effiziente Iteration in der Sortierreihenfolge.
Durch die sortierten Schlüssel ist es zum Beispiel möglich mit Hilfe der Schnittstellenmethoden von SortedMap einen Teilbereich auszuwählen. Ein Verzeichnis welches Zeichketten (Strings) als Schlüssel verwendet kann man z.Bsp. auf die folgende Weise einen Bereich erzeugen der alle Zeichenketten zwischen den Stringvariablen low und high enthält:
SortedMap<String, V> sub = m.subMap(low, high+"\0");
Weitere Klassen
Die Klasse EnumMap erlaubt das Verwalten von Aufzählungstypen (wie auch die Klasse EnumSet).
Die Klasse WeakHashMap erlaubt die Verwaltung schwacher Referenzen. Schwach referenzierte Objekte können in Java bei Bedarf vom Garbage Collector gelöscht werden. Sie benötigen daher eine gewisse Sonderbehandlung.
Beide Klassen werden im Rahmen der Vorlesung nicht weiter behandelt.
Ausgewählte Methoden für Maps
Die Schnittstelle Map hat sehr viele Methoden, die man am besten in der Online API Dokumentation nachliest. Die wichtigsten Methoden werden hier kurz vorgestellt:
Konstruktor
Die Konstruktoren sind nicht Teil der Schnittstelle. Sie hängen von den individuellen Ausprägungen ab. Anbei zwei Beispiele zum Erzeugen von Map-objekten wie sie im Beispiel oben gezeigt wurden:
import java.util.HashMap; import java.util.TreeMap; import Person; import Kfz; Map<Kfz,Person> halterMapsortiert = new TreeMap<Kfz,Person>(); Map<Kfz,Person> halterMapunsortiert = new HashMap<Kfz,Person>();
Hinzufügen: put(K key, V value)
Tupel von Schlüsseln und Werten können mit der put(K key,V value) Methode hinzugefügt werden. Zwei Tupel für das obige Beispiel werden mit der folgenden Syntax eingefügt:
Person p = new Person("Müller"); Kfz wagen1 = new Kfz("D-U-3",3456789); Kfz wagen2 = new Kfz("M-V-4",4567890); halterMapsortiert.put(wagen1,p); halterMapsortiert.put(wagen2,p);
Methode get(Object key): Auslesen eines Wertes zu einem gegebenen Schlüssel
Die "klassische" Verzeichnisoperation ist das Auslesen eines Wertes zu einem gegebenen Schlüssel. Hierzu dient die get(Object key) Methode. Im obigen Beispiel kann man die Person "Müller" finden wenn man eines der Fahrzeuge dieser Person kennt. Die geschieht mit der folgenden Syntax:
Person p = halterMapsortiert.get(wagen2);
Methode keySet(): Auslesen alle Schlüsselelemente
Java-Maps erlauben auch das Auslesen aller Schlüssel mit der Methode keySet(). Man kann sich im obigen Beispiel die Menge (Javatyp Set) aller Kfz-objekte dem folgenden Kommando auslesen: Set<Kfz> KfzSet = halterMapsortiert.keySet(); Die neu erzeugte Menge zeigt auf die gleichen Schlüsselobjekte wie die Map: Zum Auslesen der Mengenelemente muss man dann einen Mengeniterator anwenden. |
Methode values(): Auslesen aller Werte
Die Werte der Map werden mit der Methode values() ausgelesen. Im Beispiel von oben würde man die Werte der Map mit dem folgenden Befehl auslesen:
Person p; Collection<Person> personColl = halterMapsortiert.values(); Iterator<Person> iterPerson = personColl.iterator(); while (iterPerson.hasNext()) { p = iterPerson.next(); System.out.println("Person: " + p); } Die zurückgebene Datenstruktur hat den Typ Collection. Der genaue Typ der Collection hängt von der gewählten Mapimplementierung ab. |
Weiterführende Quellen
- 9446 views
Warteschlangen (Collections)
Warteschlangen (Collections)Das Java Collections Framework besitzt Klassen zur Implementierung von Warteschlangen (engl. Queue).
Die Warteschlangen implementieren das FIFO Prinzip (First In, First Out). Warteschlangen werden oft zur Kommunikation zwischen Threads verwendet. Ein weitere typischer Anwendungsfall ist das Verwalten von Alarmnachrichten.
Da Alarmnachrichten eventuell mit unterschiedlichen Prioritäten verwaltet werden müssen erlauben es die Klassen des Java Collections Frameworks Objekten unterschiedliche Prioritäten zuzuweisen. Innerhalb einer Priorität gilt dann wieder das FIFO Prinzip.
Einfache Warteschlangen
Einfache Warteschlangen implementieren die Schnittstelle Queue:
package java.util; public interface Queue<E> extends Collection<E> {
E element();
boolean offer(E e);
E peek();
E poll();
E remove();
}
- offer(): Erlaubt das Einfügen in eine Warteschlange. Sie gibt den Wert false zurück wenn die Warteschlange ihre maximale Kapazität erreicht hat
- peek(): Liefert das nächste Element es wird jedoch nicht entfernt
- element(): Identisch zur Methode peek()
- poll(): Entfernt das nächste Element der Warteschlange und gibt es zurück
- remove(): Identisch zur Methode poll(). wirft jedoch eine Ausnahme NoSuchElementException falls die Warteschlange leer ist.
Hinweis: Die Klasse LinkedList implementiert die Queue Schnittstelle. Sie ist somit in der Lage sich wie eine Warteschlange zu verhalten.
Priorisierte Warteschlangen: Klasse PriorityQueue
Die Klasse PriorityQueue benutzt zur Priorisierung von Objekten deren Sortierkriterium die entweder durch die Schnittstelle Comparable<E> oder durch ein spezielles Vergleichsobjekt der Klasse Comparator<E> implementiert wird.
Blockierende Warteschlangen: Schnittstelle BlockingQueue<E>
Es gibt Fälle in denen der Produzent und der Konsument unabhängig voneinander arbeiten und der Produzent nicht in der Lage ist, die Warteschlange immer mit Objekten gefüllt zu halten. Das Gegenteil ist genauso möglich. Ein Konsument ist nicht in der Lage die Warteschlange schnell genug auszulesen. In den beiden Fällen einer leeren, sowie er vollen Warteschlange erlaubt die Schnittstelle BlockingQueue dem Produzenten oder dem Konsumenten eine bestimmte Zeit zu warten. Die Schnittstelle fordert die Implementierung der Einfüge-, Lese- und Entfernmethoden mit Angabe eines Zeitintervalls für das der Thread warten sollen:
Die Klasse LinkedBlockingQueue implementiert diese Schnittstelle.
Priorisierte und blockierende Warteschlangen: Klasse PriorityBlockingQueue
Die Klasse PriorityBlockingQueue vereint die beiden Eigenschaften einer priorisierten und blockierenden Warteschlange.
Weitere Informationen
- 6723 views
Übungen (Collections)
Übungen (Collections)Fragen
Was sind günstige, bzw. ungünstige Anwendungsszenarien für die folgenden Klassen des Java Collection Frameworks?
- LinkedList
- ArrayList
- TreeSet
- HashSet
Klasse Student
Die Übungen dieser Seite verwenden die Klasse Student. Studenten haben eine Matrikelnummer, einen Vornamen, einen Nachnamen und eine aktuelles Semester. Für die Klasse wurden alle Methoden zum Vergleichen überladen. Zwei Studenten sind identisch wenn sie die gleiche Matrikelnummer besitzen.
package s2.collection; public class Student implements Comparable { int matrikelnr; String name; String vorname; int semester; public Student(String n, String vn, int nr, int sem) { name = n; vorname = vn; matrikelnr = nr; semester = sem; } public String toString() { return "(" + matrikelnr + "," + name + " " + vorname + ", " + semester + ")"; } /** * Studenten werden anhand Ihre Matrikelnr verglichen * @param o * @return */ public int compareTo(Object o) { int diff = 0; if (o.getClass() == getClass()) { diff = matrikelnr - ((Student) o).matrikelnr; } if (diff > 0) { diff = 1; } if (diff < 0) { diff = -1; } return diff; } public boolean equals(Object o) { return (compareTo(o) == 0); } public int hashCode() { return matrikelnr; } }
Mengen von Studenten
Das Programm soll eine Menge von Studenten verwalten. Vervollständigen Sie das das folgende Programm:
- main() Methode
- Setzen Sie die Objekterzeugung für die Variablen mengeUnsortiert und mengeSortiert ein
- Fügen Sie alle Studenten in beide Mengen ein indem Sie die Platzhalter-Konsolenausgaben ersetzen
- ausgaben() Methode: Iterator über eine Menge implementieren
- Deklarieren und initialisieren Sie ein Iteratorobjekt für das Listenobjekt welches als Parameter übergeben wurde
- Implementieren Sie korrekte Abbruchbedingung für die while-Schleife
- Implementieren die while-Schleife. Weisen Sie der Variablen s einen jeweils neuen Student der Menge zu
Beobachtungen:
- Welche der beiden Mengen ist sortiert?
- Welche Mengenimplementierungen nutzen Sie für welche Aufgabenstellung?
- Was geschieht mit Caroline Herschel?
package s2.collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; public class SetIterDemo { public static void main(String[] args) { Set<Student> mengeUnsortiert = null; // Hier HashSetobjekt einfügen Set<Student> mengeSortiert = null; // Hier TreeSetobjekt einfügen Student s; s = new Student("Curie", "Marie", 19, 1); System.out.println("Hier Student " + s + "in beide Mengen einfügen"); s = new Student("Merian", "Maria-Sybilla", 17, 3); System.out.println("Hier Student " + s + "in beide Mengen einfügen"); s = new Student("Noether", "Emmi", 16, 1); System.out.println("Hier Student " + s + "in beide Mengen einfügen"); s = new Student("Liese", "Meitner", 15, 2); System.out.println("Hier Student " + s + "in beide Mengen einfügen"); s = new Student("Herschel", "Caroline", 15, 2); System.out.println("Hier Student " + s + "in beide Mengen einfügen"); ausgaben(mengeUnsortiert); ausgaben(mengeSortiert); } public static void ausgaben(Set<Student> menge) { Student s; System.out.println("Hier Iterator deklarieren"); System.out.println("Inhalt der Menge (" + menge.getClass() + "):"); // Nicht ersetzen while (true) { // Abbruchbedingung ersetzen. Die Schleife terminiert so nicht! System.out.println("Hier Student aus Iterator in Schleife auslesen"); s = null; System.out.println("Student: " + s); // Nicht ersetzen } } } // Ende der Klasse
Maps von Studenten
- main() Methode
- Setzen die die Objekterzeugung für die Variablen matrikelMap und nachName ein (In den beiden Kommentarzeilen)
- matrikelMap soll als Schlüssel die Matrikelnummer des Studenten verwenden, der Inhalt soll aus Studenten bestehen
- nachnameMap soll als Schlüssel den Nachnamen des Studenten verwenden, der Inhalt soll aus Studenten bestehen
- Welche Typen müssen für die Platzhalter xxx? bei der Variablen und dem Map-Parameter eingesetzt werden?
- Fügen Sie alle Studenten in beide Mengen ein indem Sie die Platzhalter-Konsolenausgaben ersetzen
- Setzen die die Objekterzeugung für die Variablen matrikelMap und nachName ein (In den beiden Kommentarzeilen)
- ausgabenMatrikelnr() Methode:
- Iterieren Sie über die Matrikelnummern. Die Matrikelnummern sind die Schlüssel der Map mp
- Legen Sie einen Iterator an;
- Definieren Sie die Abbruchbedingung für die while Schleife
- Lesen Sie die nächste Matrikelnummer in der while Schleife aus (Variable s)
- Finden Sie die Studenten zur Matrikelnummern 15 und 16.
- Iterieren Sie über die Studenten. Die Studenten sind die eigentlichen Objekte der Map mp
- Legen Sie einen Iterator an;
- Definieren Sie die Abbruchbedingung für die while Schleife
- Lesen Sie die nächste Matrikelnummer in der while Schleife aus (Variable st)
- Iterieren Sie über die Matrikelnummern. Die Matrikelnummern sind die Schlüssel der Map mp
- ausgabenNamen() Methode:
- Iterieren Sie über die Nachnamen. Die Nachnamen sind die Schlüssel der Map mp
- Legen Sie einen Iterator an;
- Definieren Sie die Abbruchbedingung für die while Schleife
- Lesen Sie den nächsten Nachnamen in der while Schleife aus (Variable str)
- Finden Sie die Studenten mit den Namen Herschel und Merian.
- Iterieren Sie über die Studenten. Die Studenten sind die eigentlichen Objekte der Map mp
- Legen Sie einen Iterator an;
- Definieren Sie die Abbruchbedingung für die while Schleife
- Lesen Sie die nächste Matrikelnummer in der while Schleife aus (Variable st)
- Iterieren Sie über die Nachnamen. Die Nachnamen sind die Schlüssel der Map mp
- Welche der beiden Maps ist sortiert?
- Sind die Schlüssel sortiert?
package s2.collection; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class MapIterDemo { public static void main(String[] args) { Map<xxx?,Student> matrikelMap = new TreeMap<xxx?,Student>(); // Ersetzen Sie xxx? Map<xxx?, Student> nachnameMap = new HashMap<xxx?,Student>();// Ersetzen Sie xxx? Student s; s = new Student("Curie", "Marie", 19, 1); System.out.println("Einsetzen: Studenten "+ s + " in die beiden Maps eintragen. Schlüssel beachten!"); s = new Student("Merian", "Maria-Sybilla", 17, 3); System.out.println("Einsetzen: Studenten "+ s + " in die beiden Maps eintragen. Schlüssel beachten!"); s = new Student("Noether", "Emmi", 16, 1); System.out.println("Einsetzen: Studenten "+ s + " in die beiden Maps eintragen. Schlüssel beachten!"); s = new Student("Meitner", "Liese", 15, 2); System.out.println("Einsetzen: Studenten "+ s + " in die beiden Maps eintragen. Schlüssel beachten!"); s = new Student("Herschel", "Caroline", 20, 2); System.out.println("Einsetzen: Studenten "+ s + " in die beiden Maps eintragen. Schlüssel beachten!"); ausgabenMatrikelnr(matrikelMap); ausgabenNamen(nachnameMap); } public static void ausgabenMatrikelnr(Map<Integer,Student> mp) { int s; Student st; System.out.println("Einsetzen: Vorbereitungen zum Auslesen der Matrikelnr"); Iterator<Integer> iterMatrikel = null; // Einsetzen: Zuweisen des Iterators System.out.println("Name (" + mp.getClass() + "):"); while (null) { // Einsetzen: Iteratorbedingung einfügen s = null // Einsetzen: Auslesen des Iterators System.out.println("Matrikelnummer: " + s); } int mnr = 15; System.out.println("Student mit Matrikelnummer " + mnr + " ist:" + null); // Einsetzen: Student mit Matrikelnr mnr mnr = 16; System.out.println("Student mit Matrikelnummer " + mnr + " ist:" + null ); // Einsetzen: Student mit Matrikelnr mnr System.out.println("Alle Werte der MatrikelMap:"); Collection<Student> l = null; // Einsetzen: Collection mit den Studenten Iterator<Student> iterStudi = l.iterator(); System.out.println("Name (" + mp.getClass() + "):"); while (null) { // Einsetzen: Schleifenbedingung des Iterators // Einsetzen: Auslesen des nächsten Studenten System.out.println("Student: " + st); } } public static void ausgabenNamen(Map<String,Student> mp) { String str; Student st; System.out.println("Einsetzen: Vorbereitungen zum Auslesen der Nachnamen"); System.out.println("Namen (" + mp.getClass() + "):"); while (null) { // Einsetzen: Iteratorbedingung einfügen str = null// Einsetzen: Auslesen des Iterators System.out.println("Nachname: " + str); } String nme = "Merian"; System.out.println("Student mit Name " + nme + " ist:" + null); // Einsetzen der Operation zum Auslesen der Studentin mit Namen nme nme = "Herschel"; System.out.println("Student mit Name " + nme + " ist:" + + null); // Einsetzen der Operation zum Auslesen der Studentin mit Namen nme System.out.println("Alle Werte der NamenMap:"); Collection<Student> l = null; // Einsetzen: Auslesen der gesamten Collection Iterator<Student> iterStudi = l.iterator(); System.out.println("Name (" + mp.getClass() + "):"); while (null) { // Einsetzen: Iteratorbedingung einfügen st = null// Einsetzen: Auslesen des Iterators System.out.println("Student: " + st); } } }
- 10396 views
Lösungen (Collections)
Lösungen (Collections)Antworten zu den Fragen
Klasse | Günstig | Ungünstig |
---|---|---|
LinkedList |
|
|
ArrayList |
|
|
TreeSet |
|
|
HashSet |
|
|
Mengen von Studenten
package s2.collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class SetIterDemo {
public static void main(String[] args) {
Set<Student> mengeUnsortiert = new HashSet<Student>();
Set<Student> mengeSortiert = new TreeSet<Student>();
Student s;
s = new Student("Curie", "Marie", 19, 1);
mengeUnsortiert.add(s);
mengeSortiert.add(s);
s = new Student("Merian", "Maria-Sybilla", 17, 3);
mengeUnsortiert.add(s);
mengeSortiert.add(s);
s = new Student("Noether", "Emmi", 16, 1);
mengeUnsortiert.add(s);
mengeSortiert.add(s);
s = new Student("Meitner","Liese", 15, 2);
mengeUnsortiert.add(s);
mengeSortiert.add(s);
s = new Student("Herschel", "Caroline", 15, 2);
mengeUnsortiert.add(s);
mengeSortiert.add(s);
ausgaben(mengeUnsortiert);
ausgaben(mengeSortiert);
}
public static void ausgaben(Set<Student> menge) {
Student s;
Iterator<Student> iter = menge.iterator();
System.out.println("Inhalt der Menge ("
+ menge.getClass() + "):");
while (iter.hasNext()) {
s = iter.next();
System.out.println("Student: " + s);
}
}
}
Konsolenausgaben
Inhalt der Menge (class java.util.HashSet): Student: (17,Merian Maria-Sybilla, 3) Student: (16,Noether Emmi, 1) Student: (19,Curie Marie, 1) Student: (15,Meitner Liese, 2) Inhalt der Menge (class java.util.TreeSet): Student: (15,Meitner Liese, 2) Student: (16,Noether Emmi, 1) Student: (17,Merian Maria-Sybilla, 3) Student: (19,Curie Marie, 1)
Maps von Studenten
package s2.collection;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class MapIterDemo {
public static void main(String[] args) {
Map<Integer,Student> matrikelMap = new TreeMap<Integer,Student>();
Map<String, Student> nachnameMap = new HashMap<String,Student>();
Student s;
s = new Student("Curie", "Marie", 19, 1);
matrikelMap.put(s.matrikelnr,s);
nachnameMap.put(s.name,s);
s = new Student("Merian", "Maria-Sybilla", 17, 3);
matrikelMap.put(s.matrikelnr,s);
nachnameMap.put(s.name,s);
s = new Student("Noether", "Emmi", 16, 1);
matrikelMap.put(s.matrikelnr,s);
nachnameMap.put(s.name,s);
s = new Student("Meitner", "Liese", 15, 2);
matrikelMap.put(s.matrikelnr,s);
nachnameMap.put(s.name,s);
s = new Student("Herschel", "Caroline", 20, 2);
matrikelMap.put(s.matrikelnr,s);
nachnameMap.put(s.name,s);
ausgabenMatrikelnr(matrikelMap);
ausgabenNamen(nachnameMap);
}
public static void ausgabenMatrikelnr(Map<Integer,Student> mp) {
int s;
Student st;
Set<Integer> matrikelSet = mp.keySet();
Iterator<Integer> iterMatrikel = matrikelSet.iterator();
System.out.println("Name ("
+ mp.getClass() + "):");
while (iterMatrikel.hasNext()) {
s = iterMatrikel.next();
System.out.println("Matrikelnummer: " + s);
}
int mnr = 15;
System.out.println("Student mit Matrikelnummer " + mnr +
" ist:" +mp.get(mnr));
mnr = 16;
System.out.println("Student mit Matrikelnummer " + mnr +
" ist:" +mp.get(mnr));
System.out.println("Alle Werte der MatrikelMap:");
Collection<Student> l = mp.values();
Iterator<Student> iterStudi = l.iterator();
System.out.println("Name ("
+ mp.getClass() + "):");
while (iterStudi.hasNext()) {
st = iterStudi.next();
System.out.println("Student: " + st);
}
}
public static void ausgabenNamen(Map<String,Student> mp) {
String str;
Student st;
Set<String> nameSet = mp.keySet();
Iterator<String> iterName = nameSet.iterator();
System.out.println("Namen ("
+ mp.getClass() + "):");
while (iterName.hasNext()) {
str = iterName.next();
System.out.println("Familienname: " + str);
}
String nme = "Merian";
System.out.println("Student mit Name " + nme +
" ist:" +mp.get(nme));
nme = "Herschel";
System.out.println("Student mit Name " + nme +
" ist:" +mp.get(nme));
System.out.println("Alle Werte der NamenMap:");
Collection<Student> l = mp.values();
Iterator<Student> iterStudi = l.iterator();
System.out.println("Student: ("
+ mp.getClass() + "):");
while (iterStudi.hasNext()) {
st = iterStudi.next();
System.out.println("Student: " + st);
}
}
}
Konsolenausgabe
Name (class java.util.TreeMap): Matrikelnummer: 15 Matrikelnummer: 16 Matrikelnummer: 17 Matrikelnummer: 19 Matrikelnummer: 20 Student mit Matrikelnummer 15 ist:(15,Meitner Liese, 2) Student mit Matrikelnummer 16 ist:(16,Noether Emmi, 1) Alle Werte der MatrikelMap: Name (class java.util.TreeMap): Student: (15,Meitner Liese, 2) Student: (16,Noether Emmi, 1) Student: (17,Merian Maria-Sybilla, 3) Student: (19,Curie Marie, 1) Student: (20,Herschel Caroline, 2) Namen (class java.util.HashMap): Matrikelnummer: Meitner Matrikelnummer: Curie Matrikelnummer: Herschel Matrikelnummer: Noether Matrikelnummer: Merian Student mit Name Merian ist:(17,Merian Maria-Sybilla, 3) Student mit Name Herschel ist:(20,Herschel Caroline, 2) Alle Werte der NamenMap: Name (class java.util.HashMap): Student: (15,Meitner Liese, 2) Student: (19,Curie Marie, 1) Student: (20,Herschel Caroline, 2) Student: (16,Noether Emmi, 1) Student: (17,Merian Maria-Sybilla, 3)
- 6970 views
Lernziele (Java Collections)
Lernziele (Java Collections)
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zu Collections
- 3466 views
Ein- und Ausgabe über Streams
Ein- und Ausgabe über Streams
Programme müssen Daten von unterschiedlichen Datenquellen lesen können und sie müssen Daten auf unterschiedliche Datenziele schreiben können. Hierbei kann es sich um die unterschiedlichsten Quellen und Ziele handeln:
|
Alle Ein- und Ausgaben in Java laufen "stromorientiert" ab, sie folgen dem "Stream-Konzept".
Ein Stream ist die Verbindung zwischen einer Datenquelle und einem Datenziel. Die Verbindung erfolgt immer in nur eine Richtung (von der Quelle zum Ziel). |
Um Eingaben in Java zu erhalten muß man einen Strom öffnen der mit einer Datenquelle verbunden ist und anschließend die Daten sequentiel lesen.
Für Ausgaben muß man einen Datenstrom zu einem Datenziel öffnen, dann die Daten sequentiell schreiben.
Dieses Konzept ist einem Wasserschlauch ähnlich: Sie benötigen jemand der das Wasser einspeißt und einen anderen Konsumenten der das Wasser wieder entnimmt.
Kategorien von Streams
In Java werden zwei Kategorien unterschieden:
- Character-Streams: Transportieren 16 Bit Daten. Sie arbeiten mit dem Javatyp char der Unicodezeichen darstellt
- Byte-Stream: Transportieren 8 Bit Daten. Sie arbeiten mit dem Javatyp byte.
- Ihre Basisfunktionalität wird durch die abstrakten Klassen InputStream und OutputStream bereitgestellt.
Wichtig: Alle Klassen die von diesen vier Basisklassen spezialisiert werden haben als Endung den Namen der abstrakten Klasse!
Bsp.: Bei der Klasse FileWriter handelt handelt es sich um einen Ausgabestrom auf Dateien.
Bsp.: Bei der Klasse FileWriter handelt handelt es sich um einen Ausgabestrom auf Dateien. Frage: Verwaltet er Bytes oder Zeichen? |
Interessant:Man kann Streams (Datenströme) verketten! Das bedeutet, dass ein Streamsobjekt in der Regel einen anderen Stream als Eingabe oder Ausgabe akzeptiert. Hiermit kann man Datenströme elegant umkonvertieren. |
java.nio und mehr...
Seit der Version JDK 1.4 gibt es in Java das Paket java.nio mit einer Reihe neuer Klassen. Diese Paket ist nicht Gegenstand der Vorlesung. In diesem Abschnitt werden nur einige wenige, ausgewählte Methoden der Klassen vorgestellt. Bitte benutzen Sie die Hyperlinks zur Java API Dokumention und einen vollständigen Überblick zu bekommen. |
- 1718 views
Die Klasse File
Die Klasse FileViele Operationen werden auf Dateien ausgeführt. Die Klasse File im Paket java.io erlaubt das manipulieren von Dateien und Verzeichnissen.
Im Beispiel der Klasse s2.io.DateiTest (github) kann man sehen wie man Verzeichnisse anlegt und ausliest und neue Dateien anlegt. Methoden zum Manipulieren und Auslesen von Dateiattributen sind in der Java Dokumentation zur Klasse File beschrieben.
package s2.io; /** * @author s@scalingbits.com */ import java.io.File; import java.io.IOException; public class DateiTest { /** * Hauptprogamm * @param args wird nicht verwendet... */ public static void main(String[] args) { String d ="testDir"; String f1 = "datei1.txt"; String f2 = "datei2.txt"; File dir = new File(d); File file1 = new File(d + "/" + f1); File file2 = new File(d + "/" + f2); if (dir.exists()) System.out.println("Verzeichnis " + d + " existiert bereits"); else dir.mkdir(); try { file1.createNewFile(); System.out.println("Datei wurde angelegt in : " + file1.getAbsolutePath() ); file2.createNewFile(); System.out.println("Datei wurde angelegt in : " + file2.getAbsolutePath() ); System.out.println("Dateien im Verzeichnis " + dir.getAbsolutePath()); String[] alleDateien = dir.list(); for ( String f : alleDateien) System.out.println("* " + f); } catch (IOException ex) { System.out.println("Probleme im IO Subsystem. Scotty beam me up!"); } } }
Übung: Erweitern Sie Anwendung so, dass die Dateien und das Verzeichnis wieder gelöscht werden! Frage: Welche Methoden brauche ich hierfür? Sie werden doch nicht auf diesen Hyperlink klicken: github s2.io.DateiTestLoesung.java |
- 1001 views
Einführung in Reader- und Writerklassen
Einführung in Reader- und WriterklassenDie Reader und Writer Klassen in Java erlauben es Zeichen-Stöme zu verwalten.
Die spezialisierten Klassen FileReader und FileWriter haben Konstruktoren in denen man als Quelle bzw. Ziel eine Datei angeben kann:
Ihre Oberklassen InputStreamReader und OutputStreamWriter haben Konstruktoren die die Konvertierung von Streams erlauben.
Hiermit kann man einen Byte-Stream (inputStream) in einen Zeichen-Stream (Reader) verwandeln. Dies ist nützlich wenn man eine Binärdatei (Byte-Stream) in eine Datei (Zeichen-Stream) schreiben möchte.
Im folgenden Beispiel wird gezeigt wie man einen Zeichen-Stream von der Konsole lesen kann und ihn dann in eine Datei schreibt. Die Java Klasse System bietet mit dem Attribute System.in Zugriff auf einen InputStream mit Konsoleneingaben und auf Konsolenausgaben (System.out).
/* * Ein Beispielprogramm welches von der Konsole liest und in eine Datei schreibt */ package s2.io; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.Writer; /** * * @author s@scalingbits.com */ public class SchreibInDatei { /** * Hauptprogamm * @param args wird nicht verwendet... */ public static void main(String[] args) { try { String f = "scalingbits.txt"; File datei = new File(f); Reader rein = new InputStreamReader(System.in); Writer raus = new FileWriter(datei); System.out.println("Der Text der jetzt eingegeben wird, wird in " + "der Datei " + f + " gespeichert"); System.out.println("Abschliessen mit Strg-Z oder Ctrl-Z"); System.out.println("Abschliessen Auf Unix/Linux mit Ctrl-D"); System.out.println("Abschliessen auf Mac mit IntelliJ mit Cmd-D"); umkopieren(rein,raus); } catch (IOException ex) { System.out.println("Probleme im IO Subsystem. Scotty beam me up"); } } /** * Umkopieren zwischen zwei Streams * @param r Eingabestream * @param w Ausgabestream * @throws IOException */ public static void umkopieren(Reader r, Writer w) throws IOException{ int c; while ((c= r.read()) != -1 ) { w.write(c); } r.close(); w.close(); } }
Übung: Erweitern Sie Anwendung so, dass sie die geschriebene Datei wieder ausliest und auf der Konsole druckt. Die Methoden umkopieren() kann man wiederverwenden... Frage: Welche Klassen brauche ich hierfür? Sie werden doch nicht auf diesen Hyperlink klicken: github s2.io.SchreibInDateiLoesung.java |
Gepufferte Reader- und Writer Klassen
Das Lesen bzw. Schreiben einzelner Bytes oder Zeichen vom Netzwerk oder von Dateien ist oft sehr ineffizient. Zum effizienteren Behandlung größerer Datenmengen stellt Java die Klassen BufferedReader und BufferedWriter zur Verfügung. Diese Klassen speichen die Daten zwischenzeitlich in einem Puffer und Lesen bzw. Schreiben die Daten in größeren Blöcken. Diese gepufferten Klassen müssen mit den elementaren Klassen verkettet werden. Deshalb haben sie die folgenden Konstruktoren:
- public BufferedReader(Reader in): zur Erzeugung eines gepufferten Stroms zum Lesen
- public BufferedWriter(Writer out): zur Erzeugung eines gepufferten Stroms zum Schreiben
Sie Verfügen über zusatzliche Methoden zum gepufferten Lesen und Schreiben
- BufferedReader.readLine(): Liest eine Textzeile bis zum nächsten Zeilenumbruch ('\n' oder '\r')
- BufferedWriter.newLine(): Schreibt einen Zeilenwechsel in den Puffer.
Nutzt man diese Klassen im Beispiel, gibt es eine Reihe von Veränderungen:
- mit BufferedReader.readLine() erhält man eine Zeichenkette als Ergebnis (oder einen Nullzeiger!)
- man sollte beim Schreiben den Zeilenimbruch selbst einfügen
Die Klasse SchreibInDateiGepuffered
/* * Eins Beispielprogramm welches von der Konsole liest und in eine Datei schreibt */ package s2.io; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; /** * * @author s@scalingbits.com */ public class SchreibInDateiGepuffered { /** * Hauptprogamm * @param args wird nicht verwendet... */ public static void main(String[] args) { try { String f = "scalingbits.txt"; File datei = new File(f); BufferedReader rein = new BufferedReader( new InputStreamReader(System.in)); BufferedWriter raus = new BufferedWriter( new FileWriter(datei)); System.out.println("Der Text der jetzt eingegeben wird, wird in " + "der Datei " + f + " gespeichert"); System.out.println("Abschliessen mit Strg-Z oder Ctrl-Z"); System.out.println("Abschliessen Auf Unix/Linux mit Ctrl-D"); System.out.println("Abschliessen auf Mac mit IntelliJ mit Cmd-D"); umkopieren(rein,raus); rein = new BufferedReader( new FileReader(f)); raus = new BufferedWriter( new OutputStreamWriter(System.out)); System.out.println("Ausgabe des in Datei " + f + " gespeichertem Texts"); umkopieren(rein,raus); } catch (IOException ex) { System.out.println("Probleme im IO Subsystem. Scotty beam me up"); } } /** * Umkopieren zwischen zwei Streams * @param r Eingabestream * @param w Ausgabestream * @throws IOException */ public static void umkopieren(BufferedReader r, BufferedWriter w) throws IOException{ String z; while ((z= r.readLine()) != null) { w.write(z); w.newLine(); } r.close(); w.close(); } }
Die Datei SchreibInDateiGepuffered in github.
- 4872 views
Behandlung von Byte-Strömen
Behandlung von Byte-StrömenVon den Byte-Streams werden hier nur wenige ausgewählte vorgestellt
Die abstrakte Basisklasse InputStream hat für die Eingabe von Byte-Streams unter anderem die folgenden Methoden:
- public abstract int read(): sie liefert das nächste Byte aus dem Strom (im Format int!)
- public int read(byte[] b): füllt das Feld der Bytes und gibt als Rückgabe die Anzahl der gelesenen Bytes
- public int read(byte[] b, int off, int n): füllt das Feld der Bytes ab der Position off mit n Bytes und liefert die Anzahl der gelesenen Bytes zurück
- public void close(): Schliest den Strom
Die abstrakte Basisklasse OutputStream besitzt ähnliche Methoden
- public abstract void write(int b): schreibt das Byte b in den Strom
- public void write(byte[] b): schreibt alles von b referenzierten Bytes in den Strom
- public void write(byte[] b, int off, int n): schreibt ab dem Index off die nächsten n Bytes in den Strom
- public void close(): schliest den Strom
- public void flush(): leert den Puffer und schreibt alle Daten in den Strom
DataInput- und DataOutputStream
Die Klassen DataInputStream und DataOutputStream erlauben das Lesen und Schreiben von Java-Basistypen. Ihre Konstruktoren arbeiten mit den Basisklassen:
- public DataInputStream(InputStream in): Erlaubt das Lesen von einem InputStream
- public DataOutputStream(OutputStream out): Schreibt in einen OutputStream
Diese Klassen stellen dann typspezifische Methoden mit der folgenden Notation zur Verfügung. Zum Beispiel DataOutputStream:
- public xxx readxx(): Lies den Typ xxx und gib ihn aus
Serialisierung und Deserialisierung von Objekten
Die Klassen ObjectInputStream und ObjectOutputStream erlauben es Objekte aus dem Hauptspeicher in einen Stream zu schreiben.
Sie wandeln den systemabhängigen Zustand eines Objects in eine neutrale Binärdarstellung in einem Datenstrom um. Diesen Vorgang nennt man Serialisierung in Java. Den umgekehrten Vorgang nennt man Deserialiserung.
Wichtig: Es könne nur Objekte serialiert werden die die Schnittstelle Serializable implementieren. Diese Schnittstelle verlangt es nicht Methoden zu implementieren.
Alle referenzierten, serialisierbaren Objekte werden auch serialisiert.
Möchte man Instanzvariablen nicht serialisieren, muss man sie mit Schlüsselwort transient kennzeichnen.
Die beiden Klassen ObjectInputStream und ObjectOutputStream haben Konstruktoren, die es erlauben aus bzw. in andere Ströme zuschreiben:
- public ObjectInputStream(InputStream in): lesen aus einem InputStream
- public ObjectOutputStream(OutputStream out): schreiben in einen OutputStream.
In geöffnete Ströme vom Typ ObjectInputStream kann man mit der folgenden Methoden lesen:
- public final Object readObject(): liest ein Objekt aus dem Datenstrom und gibt einen Zeiger zurück.
Ähnlich kann in ObjectOutputStream mit der folgenden Methode ein Objekt schreiben
- public final void writeObject(Object obj): schreibt ein Object und alle Unterobjekte in den Strom
- 1284 views
Weitere Klassen
Weitere KlassenDas java.io Paket hat noch viele weiter Klassen. Hier eine kurze Liste der interessantesten Klassen
- RandomAccessFile: wahlfreier Zugriff auf Dateien
- InflaterInputStream, DeflaterOutputStream: checken der Integrität von Dateien mit Hilfe von Prüfsummen
- ZipInputStream, ZipOutputStream: komprimierende, dekomprimierende Ströme. Für gzip gibt es analoge Klassen
- 778 views
Übungen ( Streams)
Übungen ( Streams)Vokalverschiebung
Ersetzen Sie alle Vokale in einer Textdatei durch bestimmte andere und erzeugen Sie eine neue Datei mit den verschobenen Vokalen.
Eine Musterlösung ist in github zu finden.
- 1323 views
Lernziele
Lernziele
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zur Streams
- 743 views
Threads (Nebenläufigkeit)
Threads (Nebenläufigkeit)
EinleitungJava bietet als Programmiersprache von Beginn an eine aktive Unterstützung für das Programmieren mit Threads (engl der Faden, das Fädchen). Hierdurch ist es in Java relativ einfach nebenläufige Programme zu implementieren. |
Hiermit ist die Implementierung von Javaanwendungen mit den folgenden Vorteilen möglich:
- man kann sehr viel mehr Aufgaben abarbeiten, da man es gleichzeitig tun kann.
- man kann Aufgaben schneller zu erledigen, da man es parallel tun kann.
- man kann Aufgaben asynchron, parallel im Hintergrund abarbeiten ohne auf andere Aufgaben warten zu lassen.
Prozesse und Threads im Betriebssystem
Mit Java-Threads kann man nebenläufige Programme programmieren die es erlauben mehrere Dinge gleichzeitig zu tun. Ein sehr einfaches Beispiel ist eine Programm mit einer graphische Benutzeroberfläche. Dieses Programm lädt typischerweise in einem Thread eine Datei aus dem Internet während gleichzeitig ein anderer Thread auf dem Bildschirm einen Fortschrittsbalken vergrößert.
Betriebssysteme verwalten die Ressourcen eines Rechners. Beim Programmieren mit nebenläufigen Java-Threads ist es wichtig zu verstehen, wie die beiden wichtigen Resourcen Hauptspeicher und Prozessoren vom Javalaufzeitsystem und dem Betriebsystem verwaltet werden. Bei der Betrachtung des Speichers spielt nur der virtuelle Speicher des Betriebsystems eine Rolle, da der Javaentwickler in der Regel keinen direkten Einfluss auf dem physischen Speicher nehmen kann. Moderne Betriebsysteme sind in der Lage Programme gleichzeitig bzw. nebenläufig auszuführen.
Prozess(Informatik) |
---|
Ein Prozess bezeichnet in der Informatik ein im Ablauf befindliches Computerprogramm (siehe Wikipedia). Zum Prozess gehört das Programm samt seiner Daten und dem Prozesskontext (siehe Wikipedia). |
Das Javalaufzeitsystem ist aus der Sicht des Betriebssystems während seiner Ausführung ein Prozess.
Prozesse besitzen während ihrer Ausführung typischerweise:
- ein Programm welches sie ausführen. Mehrere Prozesse können durchaus das gleiche Programm ausführen
- einen eigenen Speicherbereich zur Verwaltung der Daten. Das Betriebsystem verwaltet diesen Speicher und sorgt dafür das alle Prozesse ihren eigenen Speicher benutzen können ohne sich gegenseitig zu beinflussen. Bei Java ist der Heap der bekannteste gemeinsame Speicherbereich.
- Zumindest einen Programmstack (Stapel) zur Verwaltung der Daten von Methoden und einen dazugehörige Befehlszähler/zeiger
- Zugriff auf Betriebssystemressourcen (Bildschirm, Tastatur, Massenspeicher, Netzwerk etc.)
Threads (engl. der Faden, das Fädchen) sind leichtgewichtige Ausführungseinheiten eines Prozesses:
Thread (Informatik) |
---|
Ein Thread (auch: Aktivitätsträger oder leichtgewichtiger Prozess) bezeichnet in der Informatik einen Ausführungsstrang oder eine Ausführungsreihenfolge in der Abarbeitung eines Programms. Ein Thread ist Teil eines Prozesses. (siehe Wikipedia) |
Das Javalaufzeitsystem ist typischerweise ein Prozess des Betriebssystems. Die Java-Threads werden normalerweise auf Betriebssystem-Threads abgebildet. Dies war in frühen Javaimplementierungen (1.1) nicht der Fall. Hier wurden die Threads von Java selbst verwaltet (siehe Green Threads) .
Ein Thread besitzt typischerweise
- keinen eigenen Speicher (Heap). Er und alle anderen Threads des Prozesses haben Zugriff auf den gemeinsamen Hauptspeicher seines Prozesses. Durch das gemeinsame, gleichzeitige Arbeiten auf den gemeinsamen Daten ist der Datenaustausch zwischen Threads sehr einfach. Die Konsistenz der Daten muss jedoch aktiv verwaltet werden.
- einen eigenen Programmstack. Er dient der Verwaltung der aktuell aufgerufenen Methodenvariablen.
- einen eigenen Befehlszähler
Prozesse bestehen aus mindestens einem Thread (dem Haupt-Thread, Main-Thread) und eventuell zusätzlichen Threads die eigene Programmstacks zur parallelen Ausführung besitzen. Die Lebensdauer von Threads ist durch die Lebensdauer des dazugehörigen Prozesses beschränkt. Sie enden mit dem Beenden des Pozesses.
Nebenläufige Ausführung im Betriebssystem
Betriebsysteme weisen den Prozessen die Prozessoren zur Ausführung zu. Hierfür verwendet man die englischen Begriffe des "scheduling" oder "dispatching". Ziel des Betriebssystems ist es die Prozessoren möglichst gut auszunutzen und eine faire, vorteilhafte Abarbeitung aller Programme in Prozessen zu gewährleisten.
Die (historisch) einfachste Art der Verwaltung von Prozessen durch das Betriebsystem ist der Batchbetrieb (Stapelbetrieb). Ein Rechner hat typischerweise nur einen Prozessor. Das Betriebssystem kann nur ein Programm gleichzeitig als Prozess ausführen. Es weist dem Prozessor eine Programm A zu dieses läuft bis es beendet wird. Anschließend wird das nächste Programm ausgeführt:
Multi tasking: Um interaktive Benutzer zu bedienen ist es geschickter laufende Prozesse zu unterbrechen und andere Prozesse teilweise abzuarbeiten. Aufgrund der hohen Prozessorgeschwindigkeit hat der menschliche Betrachter den Eindruck, die Prozesse laufen gleichzeitig. Alle moderne Betriebsysteme arbeiten nach diesem Prinzip. Das Unterbrechen der Prozesse ist oft problemlos möglich, da sie sich oft selbst blockieren. Sie müssen da si relativ lange auf Daten von Benutzern, Festplatten, dem Netzwerk warten müssen. In diesen vielen Zwangpausen kann das Betriebssyteme andere, lauffähige Prozesse abarbeiten.
Die Prozesse laufen jetzt verschränkt und sie werden in vielen einzelnen Blöcken abgearbeitet. Sie werden quasi-parallel abgearbeitet.
Multi tasking-Multiprozessor: Da alle modernen Prozessoren mehrere Ausführungseinheiten besitzen können die Betriebsysteme Prozesse parallel abarbeiten. Die Prozesse müssen nicht zwangsweise auf dem gleichen Prozessor ausgeführt werden (siehe Beispiel). Die Gesamtausführungszeiten können bei mehren Prozessoren entsprecht verkürzen.
Multithreaded-Multiprozessor: Da Threads leichtgewichtige Prozesse mit einem Programmstack und einem eigenen Programmablauf sind, werden sie bei der Prozessorvergabe wie Prozesse behandelt. Ein Prozess kann jetzt mehrere Prozessoren gleichzeitig verwenden wenn er nur über mehrere Theads verfügt. Anwendungen können jetzt innerhalb eines Prozesses skalieren. Dies bedeutet, sie können (threoretisch) beliebig viele Prozessoren benutzen und damit beliebig viele Aufgaben in einer bestimmten Zeit abarbeiten. Der Durchsatz eines Prozesses, bzw. die Abarbeitungsgeschwindigkeit ist nicht mehr direkt an die Geschwindigkeit eines einzelnen Prozessors gebunden.
- 11633 views
Thread Zustandsübergänge
Thread ZustandsübergängeThreads und Prozesse haben unterschiedliche Zustände, die von den verfügbaren Betriebsmitteln abhängen:
- blocked: ein Thread der auf Daten von einem Gerät wie Festplatte, Tastatur, Maus oder Netzwerk wartet ist blockiert. Das Betriebssystem wird ihm keinen Prozessor zuweisen da er ihn nicht nutzen kann solange ihm die notwendigen Daten fehlen.
- ready-to-run: Der Thread hat alle Betriebsmittel, mit Ausnahme des Prozessors, die er zum Laufen benötigt. Er wartet bis der Dispatcher einen Prozessor zuweist.
- running: Der Thread hat alle nötigen Betriebsmittel inklusive eines Prozessors. Er führt sein Programm aus bis er eine nicht vorhandene Ressource benötigt oder bis er vom Dispatcher den Prozessor entzogen bekommt.
Die Übergänge zwischen den vereinfachten Zuständen eines Threads sind im folgenden Diagramm dargestellt:
Threads haben ähnlich wie Prozesse eine Reihe von Zuständen. Sie besitzen jedoch mehr Zustände, da ihre Lebensdauer kürzer als die des Prozesses ist und sie sich miteinandere synchronisieren müssen. Threads haben die folgenden fünf Zustände:
- new: Der Thread wurde mit dem new Operator erzeugt. Er befindet sich im Anfangszustand. Auf seine Daten kann man zugreifen. Er ist noch nicht ablauffähig.
- ready-to-run: Der Thread ist lauffähig und wartet auf eine Prozessorzuweisung
- running: Der Thread hat einen Prozessor und führt das Programm aus
- blocked: Der Thread wartet auf Ressourcen
- dead: Der Thread kann nicht wieder gestartet werden
Eine Reihe dieser Zustände kann durch Methodenaufrufe vom Entwickler beeinflusst werden:
- start(): Ein Thread wechselt vom Zustand "new" zu "ready-to-run"
- sleep(): Ein laufender Thread wird für eine bestimmte Zeit blockiert
- join(): Ein Thread blockiert sich selbst bis der Thread dessen join() Methode aufgerufen wurde sich beendet hat
- yield(): Ein Thread gibt freiwillig den Prozessor auf und erlaubt der Ablaufsteuerung den Prozessor einem anderen Thread zuzuweisen
- interrupt(): Erlaubt es Threads die wegen eines sleep() oder join() blockiert sind wieder in den Zustand "ready-to-run" zu versetzen
- 9005 views
Programmieren mit Threads
Programmieren mit ThreadsJava erlaubt das Erzeugen und Verwalten von Threads mit Hilfe der Systemklasse Thread. Beim Starten einer Javaanwendung bekommt die Methode main() automatisch einen Thread erzeugt und zugewiesen der sie ausführt. Mit Hilfe der Klasse Thread kann man selbst zusätzliche Threads erzeugen und starten.
Erzeugen und Start mit der Klasse Thread
Man kann einen neuen Thread starten indem man ein Objekt von Thread erzeugt. Hiermit wird parallel im Hintergrund ein Javathread erzeugt. Das Aufrufen der Methode start() startet dann den neuen Thread. Dies geschieht wie im folgenden Beispiel gezeigt:
public static void main(String[] args { ... Thread t1= new Thread(...); t1.start(); ... }
Im Laufzeitsystem wird hierdurch zuerst ein neuer Thread erzeugt und dann gestartet:
Es verbleibt die Frage welchen Programmcode der neue Thread ausführt.
Der ausgeführte Programmcode steht in einer Methode mit den Namen run() und muss von einer Klasse nach den Vorgaben der Schnittstelle Runnable implementiert werden.
Hierfür muss beim Erzeugen des Thread-objekts eine Referenz auf ein Objekt mitgegeben werden welches diese Schnittstelle implementiert. Geschieht dies nicht wird die Methode run() des Tread-objekts aufgerufen. Hierdurch ergeben sich zwei Möglichkeiten einen eigenen Thread mit einem bestimmten Programm zu starten:
Starten eines Threads durch Erweitern der Klasse Thread
Die erste Möglichkeit besteht im Erweitern der Klasse Thread und im überschreiben der Methode run().
Die Klasse Thread implementiert schon selbst die Schnittstelle Runnable. Ruft man die Methode start() ohne einen Parameter auf, so wird bei einer angeleiteten Klasse die überschriebene Methode run() in einem eigenen neuen Thread aufgerufen.
Im unten aufgeführten Beispiel wurde die Klasse myThread aus der Klasse Thread abgeleitet:
Die Klasse myThread verwaltet die Threads in ihrer main() Methode. Das Erzeugen und Starten der Threads der Klasse myThread könnte auch aus jeder anderen beliebigen Klasse erfolgen.
package s2.thread; /** * * @author s@scalingbits.com */ public class MyThread extends Thread { @Override public void run() { System.out.println("Hurra ich bin myThread in einem Thread mit der Id: " + Thread.currentThread().getId()); } public static void main(String[] args) { System.out.println("Start MyThread.main() Methode im Thread mit der Id: " + Thread.currentThread().getId()); MyThread t1 = new MyThread(); t1.start(); System.out.println("Ende MyThread.main() Methode im Thread mit der Id: " + Thread.currentThread().getId()); MyThread t2 = new MyThread(); // t2 ist zwar ein Threadobjekt und repräsentiert einen Thread // da das Objekt nicht mit start() aufgerufen läuft es im gleichen // Thread wie die main() Routine! t2.run(); } } // Ende der Klasse
Das Programm erzeugt die folgende Konsolenausgabe:
Start MyThread.main() Methode im Thread mit der Id: 1 Ende MyThread.main() Methode im Thread mit der Id: 1 Hurra ich bin myThread in einem Thread mit der Id: 1 Hurra ich bin myThread in einem Thread mit der Id: 9
Im Beispielprogramm wird für die Referenz t1 ein neues Threadobjekt erzeugt. Anschliesend wird es durch seine start() Methode in einem eigenen Thread gestartet. Die run() Methode wird nach dem Starten im eigenen Thread ausgeführt.
Das Objekt mit der Referenz t2 ist zwar auch ein Thread. Das es aber direkt mir der run() Methode aufgerufen wird, läuft es im gleichen Thread wie die main() Methode.
Im UML Sequenzdiagramm ergibt sich der folgende Ablauf:
Starten eines Threads durch Implementieren der Schnittstelle Runnable
Das Erweitern einer Klasse aus der Klasse Thread ist nicht immer möglich. Man kann einen Thread auch starten indem man ein Thread-objekt erzeugt und ihm die Referenz auf eine Instanz der Schnittstelle Runnable mitgibt. Die Programmierabfolge ist dann:
- Erzeugen eine Threadobjekts mit Referenz auf eine Instanz von Runnable.
- Aufrufen der start() Methode des Threadobjekts
- die Methode run() der Runnable-objekts wird automatisch aufgerufen
Die Klasse myRunnable:
package s2.thread; /** * * @author s@scalingbits.com */ public class MyRunnable implements Runnable { @Override public void run() { System.out.println("Hurra ich bin myRunnable in einem Thread mit der Id: " + Thread.currentThread().getId()); } }
Die Klasse ThreadStarter die als Hauptprogramm dient:
package s2.thread; /** * * @author s@scalingbits.com */ public class ThreadStarter{ public static void main(String[] args) { System.out.println("Start ThreadStarter.main() Methode im Thread mit der Id: " + Thread.currentThread().getId()); MyRunnable r1 = new MyRunnable(); MyRunnable r2 = new MyRunnable(); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); System.out.println("Ende ThreadStarter.main() Methode im Thread mit der Id: " + Thread.currentThread().getId()); // r2 ist zwar ein Runnableobjekt , da das Objekt aber nicht von einem // Threadobjekt indirekt aufgerufen wirdläuft es im gleichen // Thread wie die main() Routine! r2.run(); } }
Das Programm erzeugt die gleichen Ausgaben wie das vorherige Programm:
Start ThreadStarter.main() Methode im Thread mit der Id: 1 Ende ThreadStarter.main() Methode im Thread mit der Id: 1 Hurra ich bin myRunnable in einem Thread mit der Id: 1 Hurra ich bin myRunnable in einem Thread mit der Id: 9
Das Aufrufen von r2.run() startet keinen eigenen Thread. Der Vorteil der Benutzung der Schnittstelle Runnable liegt darin, dass man die Methode run() in jeder beliebigen Klasse implementieren kann.
Die wichtigsten Methoden der Klasse Thread
- Konstruktor: Thread(Runnable target) : Erzeugt einen neuen Thread und übergibt ein Objekt dessen run() Methode beim Starten des Threads aufgerufen (anstatt die eigene run() Methode aufzurufen.
- static Thread currentThread(); liefert den aktuellen Thread der ein Codestück gerade ausführt
- long getId(): Liefert die interne Nummer des Threads
- join(): Hält den aktuellen Thread an bis der referenzierte Thread beendet ist.
- static void sleep(long millis): Lässt den aktuellen Thread eine Anzahl von Millisekunden schlafen.
- start(): Lässt die VM den referenzierten Thread starten. Dieser ruft dann die run() Methode auf.
- 17866 views
Synchronisation
SynchronisationDa Threads auf die gleichen Objekte auf im Heap zugreifen, können sie so sehr effizient Daten austauschen. Es besteht jedoch das Risiko der Datenkorruption, da man oft mehrere Daten gleichzeitig verändern muss um sie von einem konsistenten Zustand in den nächsten konsistenten Zustand zu überführen.
Die Sitzplatzreservierung in einem Flugzeug ist hierfür ein typisches Beispiel:
Mehrere Reisebüros prüfen ein Flugzeug auf die Verfügbarkeit von 10 Plätzen für eine Reisegruppe. Ergibt das Lesen der Belegungsvariable 20 freie Plätze, so fährt Reisebüro 1 fort und liest weitere Daten um die Buchung vorzubereiten. Verzögert sich die endgültige Buchung so kann es vorkommen, dass ein zweites Reisebüro die Verfügbarkeit von 15 Plätzen abfragt und die 15 Plätze bucht. Das zweite Reisebüro erhöht den Belegungszähler also um 15 Plätze.
Kommt das erste Reisebüro nun endlich mit seiner Buchung vorran und erhöht die ursprünglich ausgelesene Variable um 10 ergibt sich ein inkonsistenter Zustand.
Man muss also in Systemen mit parallelen Zugriff auf Daten die Möglichkeit schaffen nur einen Thread über eine gewisse Zeit auf einem Datensatz (Objekt) arbeiten zu lassen um wieder einen konsistenten Zustand herzuführen.
Darf nur ein Thread gleichzeitig auf einer Variablen arbeiten, so nennt man diese eine kritische Variable. Die Zeit die ein Thread mit der Bearbeitung einer solchen Varriablen verbringt nennt man den kritischen Abschnitt oder auch den kritischen Pfad.
Das oben geschilderte Problem beim gleichzeitigen Zugriff nennt man auch "Reader/Writer" Problem, da das Lesen und Schreiben auf dem Datum atomar erfolgen muss. Da in in nebenläufigen Systemen diese Datenkorruption ausschlieslich von der Geschwindigkeit und dem zufälligen paralleln Zugriff abhängt nennt man ein solches Problem auch eine "Race Condition". Die Datenkorruption tritt zufällig und abhängig von der Ausführungsgeschwindigkeit auf.
Beispiel: Nichtatomares Inkrement in Java
Der ++ Operator ist Java ist nicht atomar. Dies bedeutet, dass zwei Threads einen bestimmten Wert auslesen können und der erste Thread schreibt den um 1 erhöhten Wert zurück während eventuell der zweite Thread noch etwas Zeit benötigt. Schreibt der zweite Thread dann den gleichen inkrementierten Wert zurück wurde die Zahl nur einmal inkrementiert. Es liegt eine Datenkorruption vor.
Im Programm ParaIncrement wird eine gemeinsame Variable zaehler von zwei Threads gleichzeitig inkrementiert. Der Wert der Variablen sollte immer doppelt so groß wie die Anzahl der Durchläufe (Konstante MAX) eines einzelnen Threads sein.Die Korruption im Programm ParaIncrement findet relativ selten selten statt. Man muss die Konstante K für die Anzahl der Durchläufe eventuell abhängig vom Rechner und der Java virtuellen Maschine anpassen:
package s2.thread; /** * * @author s@scalingbits.com */ public class ParaIncrement extends Thread { public static int zaehler=0; public static final int MAX= Integer.MAX_VALUE/10; public static void increment() { zaehler++; } /** * Starten des Threads */ public void run() { for (int i=0; i < MAX; i++) { increment(); } } public static void main(String[] args) { ParaIncrement thread1 = new ParaIncrement(); ParaIncrement thread2 = new ParaIncrement(); long time = System.nanoTime(); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { } time = (System.nanoTime() -time)/1000000L; // time in milliseconds if ((2* ParaIncrement.MAX) == ParaIncrement.zaehler) System.out.println("Korrekte Ausführung: " + + ParaIncrement.zaehler + " (" + time + "ms)"); else System.out.println("Fehler! Soll: " + (2* ParaIncrement.MAX) + "; Ist: " +ParaIncrement.zaehler + " (" + time + "ms)"); } }
Wechselseitiger Ausschluss
Zur Vermeidung der oben genannten Korruption ist es wichtig sicher zustellen, dass nur ein Thread gleichzeitig Zugriff auf diese Daten hat.
Das oben gezeigte Programm zeigt die Millisekunden an die es benötigt. Wieviel sind es? Man kann das Schlüsselwort synchronized als Modifizierer in die Methode increment() einpflegen. Läuft das Programm jetzt langsamer oder schneller? Läuft es korrekt oder inkorrekt? |
Kritischer Abschnitt/ Kritischer Pfad |
---|
Ein kritischer Abschnitt ist eine Folge von Befehlen, die ein Thread nacheinander vollständig abarbeiten muss, auch wenn er vorübergehend die CPU and einen anderen Thread abgibt. Kein anderer Thread darf einen kritischen Abschnitt betreten, der auf die gleichen Variablen zugreift, solange der erstgenannte Thread mit der Abarbeitung der Befehlsfolge noch nicht fertig ist. (siehe: Goll, Seite 744) |
Einfachste Lösung: Verwendung von Typen die den kritischen Pfad selbst schützen
Das Java Concurrency Paket bietet reichhaltige Möglichkeiten und Klassen. Das oben gezeigte Beispiel kann mit Hilfe der Klasse AtomicInteger sicher implementiert werden. Die Klasse AtomicInteger erlaubt immer nur einem Thread Zugriff auf das Datum. Die entsprechende Implementierung ist:
package s2.thread; import java.util.concurrent.atomic.AtomicInteger; /** * * @author s@scalingbits.com */ public class ParaIncrementAtomicInt extends Thread { public static AtomicInteger zaehler; public static final int MAX= Integer.MAX_VALUE/100; public static void increment() { zaehler.getAndIncrement(); } /** * Starten des Threads */ public void run() { for (int i=0; i < MAX; i++) { increment(); } } public static void main(String[] args) { zaehler = new AtomicInteger(0); ParaIncrementAtomicInt thread1 = new ParaIncrementAtomicInt(); ParaIncrementAtomicInt thread2 = new ParaIncrementAtomicInt(); long time = System.nanoTime(); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { } time = (System.nanoTime() -time)/1000000L; // time in milliseconds if ((2* ParaIncrementAtomicInt.MAX) == zaehler.get()) System.out.println("Korrekte Ausführung: " + + ParaIncrementAtomicInt.zaehler.get() + " (" + time + "ms)"); else System.out.println("Fehler! Soll: " + (2* ParaIncrementAtomicInt.MAX) + "; Ist: " +ParaIncrementAtomicInt.zaehler.get() + " (" + time + "ms)"); } }
Vergleichen Sie die Laufzeiten beider Anwendungen! Die Anwendung bei der immer nur ein Thread auf das Objekt zugreifen kann ist erheblich langsamer, jedoch korrekt.
Sperren durch Monitore mit dem Schlüsselwort synchronized
Um die oben gezeigten Möglichkeiten von Korruptionen zu vermeiden verfügen Javaobjekte über Sperren die Monitore genannt werden. Jedes Javaobjekt besitzt einen Monitor der gesetzt ist oder nicht gesetzt ist.
- Ein Monitor wird gesetzt wenn eine Instanzmethode des Objekts aufgerufen wird, die mit dem Schlüsselwort synchronized versehen ist.
- Kein anderer Thread kann eine synchronisierte Instanzmethode des gleichen Objekts aufrufen, solange der Thread der den Monitor erworben hat noch in der synchronisierten Methode arbeitet.
- Alle anderen Threads werden beim Aufruf einer synchronisierten Instanzmethode des gleichen Objekts blockiert und müssen warten bis der erste Thread die synchronisierte Methode verlassen hat.
- Nach dem der erste Thread die synchronisierten Methode verlassen hat, wird der Monitor wieder freigegeben.
- Der nächste Thread, der eventuell schon wartet kann den Monitor erwerben.
Es ist wichtig zu verstehen, dass in Java immer die individuellen Objekte mit einem Monitor geschützt sind. Sind zum Beispiel die Sitzplätze eines Flugzeuges durch Java-Objekte implementiert, so kann man mit der gleichen synchronisierten Methode auf untrschiedlichen Objekte parallel arbeiten.
Am Beispiel der Klasse Sitzplatz kann man sehen wie man den Monitor für einen bestimmten Sitzplatz setzen kann:
package s2.thread; /** * * @author s@scalingbits.com */ public class Sitzplatz { private boolean frei = true; private String reisender; boolean istFrei() {return frei;} /** * Buche einen Sitzplatz für einen Kunden falls er frei ist * @param kunde Name des Reisenden * @return erfolg der Buchung */ synchronized boolean belegeWennFrei(String kunde) { boolean erfolg = frei; // Kein Erfolg wenn nicht frei if (frei) { reisender = kunde; frei = false; } return erfolg; } }
Die Methode belegeWennFrei() kann jetzt nur noch von einem Thread auf einem Objekt gleichzeitig aufgerufen werden. Die Methode istFrei() ist nicht synchronisiert und in einer parallelen Umgebung nicht sehr relevant. Man kann sich nicht darauf verlassen, dass bei der nächsten Operation der freie Zustand noch gilt.
Monitore und Schutz von Daten |
---|
Monitore schützen nur die synchronsierten Methoden eines Objekts. Dies bedeutet
|
Statische synchronisierte Methoden
Das Schlüsselwort synchronized kann auch verwendet werden um einen Monitor für die Klasse zu setzen. Dieser Monitor hat aber keinen Einfluss auf den Zugriff auf die Objekte einer Klasse! Er schützt nur statische Methoden der Klasse.
Beispiel: Man kann das Korruptionsproblem in der Klasse ParaIncrement beheben in dem man die statische Methode increment() synchronsiert:
public static synchronized void increment() { zaehler++; }
Die Variable zaehler ist in diesem Beispiel eine statische Variable. Sie gehört nicht zu einem der beiden erzeugten Objekten.
Synchronisierte Blöcke
Man muss nicht notwendigerweise eine gesamt Methode synchroniseren. Java bietet auch die Möglichkeit einzelne Blöcke zu synchronisieren. Das Synchronsieren eines Blocks erfolgt ebenfalls mit Hilfe des Schlüsselworts synchronized. Hier muss man jedoch das Objekt angeben für welches man einen Monitor erwerben will.
Man kann die Sitzplatzreservierung auch mit Hilfe eines synchronisierten Blocks implementieren:
boolean belegeWennFrei(String kunde) { boolean erfolg; synchronized (this) { erfolg = frei; // Kein Erfolg wenn nicht frei if (frei) { reisender = kunde; frei = false; } } return erfolg; }
In dieser Implementierung kann die Methode belegeWennFrei() parallel aufgerufen werden. Beim Schlüsselwort synchronized muss jedoch für das aktuelle Objekt der Monitor erworben werden bevor der Thread fortfahren kann.
Referenzen
- Goll,Heinisch, Müller-Hoffman: Java als erste Programiersprache, Teubner Verlag
- IBM Developerworks: Going Atomic: Gute Erklärung zu synchronisierten Zählern (AtomicInteger)
- 5010 views
Beispiel: Kritischer Pfad
Beispiel: Kritischer PfadDas hier benutzte Beispielprogramm visualiert 15 Threads die sich auf einem synchronisierten Objekt serialisieren.
Threads in grüner Farbe befinden sich nicht im kritischen Pfad. Threads in roter Farbe befinden sich im kritischen Pfad.
Der kritische Pfad in in der Klasse EinMonitor in der Methode buchen() implementiert.
Die einzige Instanz von EinMonitor verfügt über zwei Variablen a und b die Konten darstellen sollen.
Die Methode buchen() "verschiebt" mehrfach einen Betrag zwischen den beiden Konten. Die Summe der beiden Variablen sollte am Ende der Methode stets gleich sein.
Die Methode buchen() enthält etwas Overhead zur Visualierung des Konzepts:
- am Anfang und am Ende muss das GUI Subsystemen über den Eintritt und das Verlassen des kritischen Pfads informiert werden
- zwischen allen Buchungen werden kleine Schlafpausen eingelegt um die Zeit im kritischen Pfad künstlich zu verlängern
Die Methode buchen() enthält eine Konsistenzprüfung am Ende der Methode die bei einem Fehler in der Buchführung eine Konsolenmeldung ausdruckt. Sie kommt in der auf dieser Seite gezeigten Variante nicht zum Zuge!
Die Klasse MainTest dient zum Starten des Programms. Sie erzeugt und startet die 15 Threads. Jeder Thread führt nur eine gewisse Anzahl von Buchungen durch und beendet sich dann.
Aufgaben
- Übersetzen Sie die Klassen und starten Sie das Hauptprogramm
- Der Schieberegler erlaubt das Einstellen der Schlafpausen im kritischen Pfad. Was geschieht wenn der kritische Pfad verkürzt wird?
- Entfernen die das Schlüsselwort synchronized in der Methode buchen(). Was geschieht?
- Was würde geschehen geschehen wenn die künstlichen sleep Aufrufe entfernt werden?
- Hinweis: Sie müssen dann auch die Anzahl der Durchläufe pro Thread stark erhöhen. Da die Zeit im kritischen Pfad sehr kurz wird.
- Was geschieht wenn man den yield() Aufruf für in der run() Methode von MainTest entfernt
Kommentar
Da die aktuelle Ausführungsgeschwindigkeit, 4-5 Zehnerpotenzen, jenseits der menschlichen Wahrnehmungsfähigkeit liegt ist, es sehr schwer die echten Abläufe im Zeitlupentempo zu visualisieren. Ein künstlicher sleep() Aufruf blockiert den Prozess und gibt den Prozessor an das Betriebssystem zurück. Der Scheduler des Betriebssystems trifft bei dieser künstlichen Verlangsamung eventuell andere Entscheidungen in Bezug auf den Thread den er ausführt. Das gleiche Problem besteht beim Debuggen von Javaprogrammen. Durch das Bremsen bestimmter Threads können existierende Fehler nicht mehr reproduzierbar sein oder bisher nicht aufgetretene Fehler in der Synchronsiation können sichtbar werden.
Starten des Programms
Die benötigten Klassen sind in Threading.jar zusammen gefaßt.
Man kann diese jar Datei mit
java -jar Threading.jar
von der Kommandozeile nach dem Runterladen starten. Vielleicht reicht auch ein Doppelklick auf die Datei im Download-Ordner...
Nach dem Übersetzen der Dateien oder nach dem Starten der jar-Datei erscheint ein GUI wie es im folgenden Bild zu sehen ist:
Klasse MainTest
Hauptprogramm der Anwendung.
package s2.thread; /** * * @author s@scalingbits.com */ public class MainTest extends Thread { public static final int INCRITICALPATH = 0; public static final int NOTINCRITICALPATH = 1; public static final int ENDED = 2; public static int anzahlThreads = 15; public static MainTest[] mt; public int threadStatus = NOTINCRITICALPATH; private static EinMonitor myMonitor; public static int sleepPeriod = 500; public int meineID; public static ThreadingPanel tp; public static ThreadFenster tg; public boolean stop = false; public boolean synchron = true; public MainTest(int id) { meineID = id; } @Override public void run() { long anfangszeit = System.nanoTime(); System.out.println("Thread [" + meineID + "] gestartet"); //GUIupdate(NOTINCRITICALPATH); for (long i = 0; i < 200; i++) { Thread t = Thread.currentThread(); // Erlaube anderen Threads die CPU zu holen t.yield(); if (tg.synchron) myMonitor.buchen(10); else myMonitor.parallelbuchen(10); } threadStatus = ENDED; System.out.println("Thread [" + meineID + "] beendet..."); } public static void main(String[] args) { // Anlegen des Monitorobjekts myMonitor = new EinMonitor(1000000L); mt = new MainTest[anzahlThreads]; tg = new ThreadFenster(); tp = tg.tp; // Erzeuge die Threads for (int i = 0; i < anzahlThreads; i++) { mt[i] = new MainTest(i); } // Starte die Threads for (int i = 0; i < anzahlThreads; i++) { mt[i].start(); } } }
Klasse EinMonitor
package s2.thread; /** * * @author s@scalingbits.com */ public class EinMonitor { long invariante; long a; long b; public EinMonitor(long para) { invariante = para; a = para; b = 0L; } synchronized public void buchen(long wert) { GUIupdate(MainTest.INCRITICALPATH); sleepABit(MainTest.sleepPeriod/5); this.a = this.a - wert; sleepABit(MainTest.sleepPeriod/5); this.b = this.b + wert; sleepABit(MainTest.sleepPeriod/5); this.a = this.a + wert; sleepABit(MainTest.sleepPeriod/5); this.b = this.b - wert; sleepABit(MainTest.sleepPeriod/5); GUIupdate(MainTest.NOTINCRITICALPATH); if ((a+b) != invariante) System.out.println("Inkonsistenter Zustand"); } public void parallelbuchen(long wert) { GUIupdate(MainTest.INCRITICALPATH); sleepABit(MainTest.sleepPeriod/5); this.a = this.a - wert; sleepABit(MainTest.sleepPeriod/5); this.b = this.b + wert; sleepABit(MainTest.sleepPeriod/5); this.a = this.a + wert; sleepABit(MainTest.sleepPeriod/5); this.b = this.b - wert; sleepABit(MainTest.sleepPeriod/5); GUIupdate(MainTest.NOTINCRITICALPATH); if ((a+b) != invariante) System.out.println("Inkonsistenter Zustand"); } private void sleepABit(int sleep) { try { Thread.sleep(sleep); } catch (InterruptedException e) {} } private void GUIupdate(int status) { MainTest t = (MainTest) Thread.currentThread(); t.threadStatus = status; t.tp.repaint(); } }
Klasse ThreadFenster
package s2.thread; import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JSlider; import javax.swing.JTextField; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * * @author s@scalingbits.com * */ public class ThreadFenster { final private JFrame hf; private JButton okButton; final private JButton exitButton; JTextField threadDisplay; private final static int SLEEPMIN = 1; private final static int SLEEPMAX = 2000; private final static int SLEEPINIT = 500; private final int threadCurrent = 10; public ThreadingPanel tp; public boolean synchron = true; JRadioButton syncButton; JRadioButton nosyncButton; public class exitActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } } /** * Aufbau des Fensters zur Ausnahmebehandlung * */ public ThreadFenster() { JPanel buttonPanel; // Erzeugen einer neuen Instanz eines Swingfensters hf = new JFrame("Thread Monitor"); //Nicht Beenden bei Schliesen des Fenster hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Anlegen der Buttons exitButton = new JButton("Beenden"); JLabel threadsLabel = new JLabel("sleep(ms):"); JSlider threadSlider = new JSlider (JSlider.HORIZONTAL, SLEEPMIN, SLEEPMAX, SLEEPINIT); threadDisplay = new JTextField(); threadDisplay.setText(Integer.toString(threadCurrent)); threadDisplay.setColumns(4); threadDisplay.setEditable(false); threadSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { JSlider source = (JSlider) e.getSource(); if (!source.getValueIsAdjusting()) { MainTest.sleepPeriod = source.getValue(); threadDisplay.setText(Integer.toString(MainTest.sleepPeriod)); } } }); exitButton.addActionListener(new exitActionListener()); syncButton = new JRadioButton("Synchronisiert"); syncButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { synchron= true; System.out.println("Synchronisiert"); } } ); syncButton.setSelected(true); nosyncButton = new JRadioButton(" Nicht Sync."); nosyncButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { synchron= false; System.out.println("Nicht synchronisiert"); } } ); ButtonGroup group = new ButtonGroup(); group.add(syncButton); group.add(nosyncButton); JPanel syncPanel = new JPanel(); BoxLayout bl = new BoxLayout(syncPanel, BoxLayout.Y_AXIS); syncPanel.setLayout(bl); syncPanel.add(syncButton); syncPanel.add(nosyncButton); //Aufbau des Panels //buttonPanel = new JPanel(new GridLayout(1, 0)); buttonPanel = new JPanel(); buttonPanel.add(threadsLabel); buttonPanel.add(threadSlider); buttonPanel.add(threadDisplay); //buttonPanel.add(okButton); buttonPanel.add(syncPanel); buttonPanel.add(exitButton); tp = new ThreadingPanel(); // Aubau des ContentPanes Container myPane = hf.getContentPane(); myPane.add(buttonPanel, BorderLayout.SOUTH); myPane.add(tp, BorderLayout.CENTER); JMenuBar jmb = new JMenuBar(); JMenu jm = new JMenu("Ablage"); jmb.add(jm); JMenuItem jmi = new JMenuItem("Beenden"); jmi.addActionListener(new exitActionListener()); jmi.setEnabled(true); jm.add(jmi); hf.setJMenuBar(jmb); //Das JFrame sichtbar machen hf.pack(); hf.setVisible(true); hf.setAlwaysOnTop(true); } }
Klasse ThreadingPanel
package s2.thread; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import javax.swing.JPanel; /** * * @author s@scalingbits.com */ public class ThreadingPanel extends JPanel { private final int ziffernBreite = 10; // Breite einer Ziffer in Pixel private final int ziffernHoehe = 20; // Hoehe einer Ziffer in Pixel public ThreadingPanel() { setPreferredSize(new Dimension(200, 100)); setDoubleBuffered(true); } /** * Methode die das Panel überlädt mit der Implementierung * der Treads * @param g */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); int maxWidth = getWidth(); int maxHeight = getHeight(); g.setColor(Color.black); g.drawString("Anzahl threads: " + MainTest.anzahlThreads, 10, 20); for (int i = 0; i < MainTest.anzahlThreads; i++) { paintThread(g, i, 20 + 25 * i, 30); } } /** * Malen eines Threads und seines Zustands * @param g Graphicshandle * @param id Identifier * @param x X Koordinate des Thread * @param y Y Koordinate des Thread */ public void paintThread(Graphics g, int id, int x, int y) { int xOffset = 1; // offset Box zu Text int yOffset = 7; // offset Box zu Text //String wertThread = k.toString(); // Wert als Text if (MainTest.mt[id] != null) { switch(MainTest.mt[id].threadStatus) { case MainTest.ENDED: g.setColor(Color.LIGHT_GRAY); break; case MainTest.NOTINCRITICALPATH: g.setColor(Color.GREEN); break; case MainTest.INCRITICALPATH: g.setColor(Color.RED); break; default: assert(true):"Hier laeuft etwas falsch"; } } int breite = 2 * ziffernBreite; int xNextNodeOffset = 20; int yNextNodeOffset = ziffernHoehe * 6 / 5; // Vertikaler Offset zur naechsten Kn.ebene //g.setColor(Color.); // Farbe des Rechtecks im Hintergrund g.fillRoundRect(x - xOffset, y - yOffset, breite, ziffernHoehe, 3, 3); g.setColor(Color.black); // Schriftfarbe g.drawString(Integer.toString(id), x + xOffset, y + yOffset); } }
- 4765 views
Java Concurrency Paket
Java Concurrency PaketMit JDK 5.0 wurde das Java Concurrency Paket, welches maßgeblich von Doug Lea mitentwickelt wurde (siehe Wikipedia), Teil der Laufzeitumgebung (JRE). Das Paket java.util.concurrent fügt komplexe Klassen zur Laufzeitumgebung die das Steuern der Parallelität auf einem höheren Niveau als das grundlegenden Konstrukte von Java erlauben.
In diesem Abschnitt werden einige, wenige Klassen vorgestellt. Schauen Sie sich dieses Paket an bevor Sie etwas Nebenläufiges implementieren. Hier gibt es sehr viele, sehr mächtige Lösungen, die die Vorlesung bei weitem übersteigen
Referenzen
- 914 views
Atomare Basistypen
Atomare BasistypenIn java.util.concurrent.atomic werden Basistypen zur Verfügung gestellt die atomar sind.
Atomar bedeutet, dass das Lesen des Datums und das anschließende Modifizieren nicht von anderen Operationen unterbrochen werden kann. Das Lesen und Modifizieren erfolgt in einem kritischen Pfad.
Ein Beispiel: Die Klasse AtomicInteger implementiert Number und hat die zum Beispiel Methoden wie:
- int addAndGet(int delta): addiere den Wert delta und gib die Summe aus
- boolean compareAndSet(int expect, int update): setzt den Wert update wenn der Wert expect vorliegt.
Anbei ein Beispiel zum nebenläufigen Addieren:
package s2.thread;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* @author s@scalingbits.com
*/public class ParaIncrementAtomicInt extends Thread {
public static AtomicInteger zaehler;
public static final int MAX= Integer.MAX_VALUE/100;
public static void increment() {
zaehler.getAndIncrement();
}
/**
* Starten des Threads
*/
public void run() {
for (int i=0; i < MAX; i++) {
increment();
}
}
public static void main(String[] args) {
zaehler = new AtomicInteger(0);
ParaIncrementAtomicInt thread1 = new ParaIncrementAtomicInt();
ParaIncrementAtomicInt thread2 = new ParaIncrementAtomicInt();
long time = System.nanoTime();
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
}
time = (System.nanoTime() -time)/1000000L; // time in milliseconds
if ((2* ParaIncrementAtomicInt.MAX) == zaehler.get())
System.out.println("Korrekte Ausführung: " +
+ ParaIncrementAtomicInt.zaehler.get() + " (" + time + "ms)");
else
System.out.println("Fehler! Soll: " + (2* ParaIncrementAtomicInt.MAX) +
"; Ist: " +ParaIncrementAtomicInt.zaehler.get() + " (" + time + "ms)");
}
}
- 983 views
Das "Fork-Join-Framework"
Das "Fork-Join-Framework"Es gibt zwei Möglichkeiten das Threading in Java zu nutzen:
- Erledigen unterschiedlicher Aufgaben parallel
- Bsp: Swing: Oberfläche neu zeichen, Eingaben abarbeiten, eigentliche Programmaufgaben ausführen
- Paralleles Abarbeiten der gleichen Aufgabe
- Bsp: Differentialgleichungen berechnen, Such & Sortieralgorithmen, Bearbeitung von Massendaten (Batch).
Das Fork & Join Framework erleichert das parallele Programmieren durch eine Optimierung der Parallelität und komfortable Klassen.
Im Rahmen der Vorlesung werden wir die Möglichkeiten einer Parallelisierung ohne und mit Rückgaben diskutieren. Die Unterschiede zum Einsatz der einfachen Threadingkonstrukte und der des Frameworks sind im Diagramm unten dargestellt:
Steuerung des Grads der Parallelisieren
Java erlaubt es recht einfach viele Ausführungseinheiten (Threads) zu erzeugen. Das Betriebssystem wird diese Threads auf Hardwarethreads zuordnen. Das macht das Betriebssystem auch fast immer sehr gut. Erzeugt man aber zufiele ausführbare Threads kann der Rechner überlastet werden und ist kaum noch ansprechbar. Das bedeutet, dass Prozesse die mit dem Benutzer interagieren sollen nicht mehr unbedingt die CPU Zeit bekommen die sie benötigen.
Hat man viel, zu viele Softwarethreads und Prozesse wird das Betriebsystem auch noch ineffizient. Es muss die ganzen Zustandänderungen managen. Hier gibt es kritische Pfade und im Überlastungsfall zuwenig CPU-Zeit.
Das Fork & Join Framework erleichtert das professionelle Skalieren in dem es eine Threadpool anlegt und alle Tasks an freie Prozessoren im Threadpool vergibt. Hiermit übernimmt es eine Aufgaben des Betriebsystem und vermeidet eine Überlastung. Gibt man keinen Wert für den Pool an, wird die Anzahl aller Prozessoren im Rechner als Defaultwert verwendet.
Dies ist nur ein kleiner Ausschnitt aus dem Fork & Join Framwork. Wir betrachten hierzu die folgenden Klassen:
Die Klasse ForkJoinPool
Die Klasse java.util.concurrent.ForkJoinPool wurde im JDK 7 eingeführt.
In dieser Klasse werden eine Menge von Threads verwaltet und zur Ausführung von Task (Aufgaben) verwendet. Dies macht es dem Betriebsystem leichter die Threads auf die Prozessoren zu verteilen, da nicht beliebig viele Threads von Java erzeugt werden. Die Klasse ForkJoinPool verwaltet die Zuordnung der Task auf die konfigurierten (Software)threads.
Tasks können auf 3 Arten dem Framework übergeben werden:
Aufrufe von ausserhalb | Aufruf innerhalb einer Berechnung | |
---|---|---|
asynchrone Ausführung | ForkJoinPool.execute(ForkJoinTask) | ForkJoinTask.fork() |
warte auf Ergebnis | ForkJoinPool.invoke(ForkJoinTask) | ForkJoinTask.invoke() |
asynchrone Ausführung mit Ergebnis | ForkJoinPool.submit(ForkJoinTask) ForkJoinPool.submit(Callable) |
ForkJoinTask.fork(ForkJoinTasks sind Futures) |
Die Implementierung wird zu einer RejectedExecutionException Ausnhame führen falls der Threadpool runtergefahren wird oder falls es keine Threads mehr in der VM gibt.
Interessante Konstruktoren:
- public ForkJoinPool(): default Konstruktor, erzeugt einen Pool mit so vielen Threads wie der Rechner Hyperthreads besitzt
- public ForkJoinPool(int parallelism): erzeugt einen Pool mit einer definierten Anzahl Threads
Paralleles Abarbeiten ohne Rückgabewerte
Ein Beispiel hierfür ist der Quicksort. Man kann parallel das linke und das rechte Teilintervall sortieren nachdem die Vorarbeit (seriell) erledigt wurde.
Hierzu nutzt man die abstrakte Klasse RecursiveAction die aus der abstrakten Klasse ForkJoinTask spezialisiert wird. Die Klasse ForkJoinPool kann Tasks die die Bedingungen der Klasse ForkJoinTask erfüllen, parallel ausführen. Hierzu gibt es in der Klasse die Methoden:
- invoke(): startet den Task in dem man sich gerade befindet
- invokeAll(ForkJoinTask<?> ... tasks): startet eine Liste von Tasks (wird im Quicksort verwendet)
Es verbleibt das Problem, dass man einen bestimmten Code ausführen möchte. Hierzu dient die abstrakte Klasse RecursiveAction. Sie besitzt eine abstrakte Methode:
- compute(): führt den Code aus. Diese Methode ist abstrakt!
Wie implementiert man so etwas?
|
Paralleles Abarbeiten mit Rückgabewerte
Die Aufgabe parallelisieren und als Aufgabe starten reicht oft nicht. Oft müssen die Ergebnisse entgegen genommen werden und dann rekursiv zu neuen Ergebnissen aggregiert werden.
Hierzu benötigt man einen Mechanismus der
- die Eingabeparameter für jede Aufgabe (Task) entgegennimmt
- die Aufgaben (Tasks) ausführt
- und die Ergebnisse erst zurückgibt wenn die Ergebnisse vorliegen
Die Schnittstelle Callable
Die Schnittstelle java.util.concurrent.Callable muss implementiert werden um die Ausgabeparameter einer Aufgabe (Task) entgegenzunehmen. Hierfür muss man
- einen generischen Typen wählen der das Ergebnis zur Verfügung stellt
- einen Konstruktor implementieren der alle Eingabeparameter entgegen nimmt.
- alle Parameter als Attribute implementiert
- die Methode V call() die das Ergebnis zurückliefert
Ein Beispiel auf der Programmierübung Ariadnefaden
public class SearchCallable implements Callable<List<Position>> { Position von; Position nach; Ariadne4Parallel a; /** * * @param a Instanz von Ariadne * @param von Start * @param nach Ziel */ SearchCallable(Ariadne4Parallel a,Position von, Position nach) { this.a =a; this.von = von; this.nach = nach; } /** * Führe Task in eigenem Thread aus und nutze Instanzvariablen * als Parameter um Aufgabe auszuführen. * @throws java.lang.Exception */ @Override public List<Position> call() throws Exception { return a.suche(von, nach); } }
In diesem Beispiel ist das Ergebnis eine Liste von Positionen die den Web aus dem Labyrinth weisen.
Die Eingabeparameter sind ein Labyrinth, die Startposition und die Position mit dem Ausgang ais dem Labyrinth.
Die Schnittstelle Future
Die Schnittstelle Future erlaubt es das Ergebnis einer Aufgabe zu analysieren. Die Schnittstelle ist generisch und erlaubt es daher nur einen bestimmten Type einer Klasse auszulesen.
Die beiden wichtigsten Methoden der Schnittstelle sind:
- V get(): wartet falls notwendig auf das Beenden der Aufgabe und liefert das Ergebnis ab
- V get(long timeout, TimeUnit unit): wartet eine bestimmte Zeit auf auf ein bestimmtes Ergebnis
In der Schnittstellendefinition sind noch weitere Methoden zum Kontrollieren und Beenden von Aufgaben (Tasks) vorhanden.
Abschicken von Aufgaben (Tasks)
Die Klasse java.util.concurrent.ForkJoinPool verfügt über eine Methode
- public <T> ForkJoinTask<T> submit(Callable<T> task)
Diese Methode nimmt die Eingaben für die Aufgabe (Task) an solange es eine Spezialierung der Klasse Callable<T> ist. Das Ergebnis ist ein Objekt vom Typ ForkJoinTask. Die Klasse ForkJoinTask implementiert die Schnittstelle Future. Das Ergebnis der Aufgabe (Task) wird hier abgeliefert sobald die Aufgabe abgearbeitet ist.
Wie implementiert man so etwas?
|
- 1854 views
Aufgaben (Threads)
Aufgaben (Threads)Programmieraufgabe JoinAndSleep
Ziel der Aufgabe ist es drei Threads zu programmieren die auf das Beenden des anderen Warten und dann eine Zeit schlafen:
- Sie drucken jeden neuen Zustand auf der Konsole aus
- Als erstes nach ihrem Start warten sie bis ein anderer Thread auf den sie zeigen sich beendet hat. Zeigen sie auf keinen anderen Thread so gehen sie sofort über zum nächstens Schritt.
- Die Threads schlafen für eine vorgegebene Zeit in ms
- Die Threads beenden sich
Die geforderte Aufgabe soll in einer Klasse implementiert werden
- Erweitern Sie die Klasse JoinAndSleep aus der Klasse Thread
- Attribute: Die Klasse hat ein Ganzzahlattribut sleep zur Verwaltung der Schlafzeit
- Die Klasse hat ein Ganzzahlattribut sleep zur Verwaltung der Schlafzeit
- Die Klasse hat eine Referenz auf ein Objekt der Klasse JoinAndSleep
- Konstruktor
- Der Konstruktor der Klasse erlaubt es die Schlafzeit zu übergeben und eine Referenz auf einen anderen Thread. Dies ist der Thread auf den gewartet werden soll.
- run() Methode: Diese Methode implementiert die oben genannte Semantik zum Warten und Schlafen
- Falls ein Thread gegeben ist soll auf sein Ende gewartet werden
- Anschliesend soll eine bestimmte Zeit geschlafen werden
- Fügen Sie zwischen allen Schritten Konsolenausgaben ein um den Fortschritt zu kontrollieren. Geben Sie hier immer auch den aktuellen Thread aus!
- main() Methode
- Erzeuge Thread 3: Er soll auf keinen Thread warten und dann 4000ms schlafen
- Erzeuge Thread 2: Er soll auf Thread 3 warten und dann 3000ms schlafen
- Erzeuge Thread 1: Er soll auf Thread 2 warten und dann 2000ms schlafen
- Starten Sie Thread 1
- Starten Sie Thread 2
- Starten Sie Thread 3
Hinweise:
Ich welchen Thread bin ich gerade?
- Die statische Methode Thread.currentThread() liefert einen Zeiger auf den aktuellen Thread
- Diesen kann man direkt ausdrucken
Wie warte ich auf einen anderen Thread?
Die Methode Thread.join() erlaubt auf das Beenden eines anderen Threads zu warten. Man muss auf eine InterruptedException vorbereitet sein, da man aufgeweckt werden kann:
Thread aThread; ... try { aThread.join(); } catch (InterruptedException e) {}
Wie lasse ich einen Thread schlafen?
Die Methode Thread.sleep() ist eine statische Methode. Man muss seinen eigenen Thread nicht kennen um ihn ruhen zulassen! Auch diese Methode kann eine InterruptedException werfen und muss mit einer Ausnahmebehandlung versehen werden:
try { Thread.sleep(schlafen); } catch (InterruptedException e) {}
Verständnisfragen
- Wer wartet hier auf wen?
- Ist dies an den Konsolenaufgaben zu erkennen?
- Woran erkenne ich bei den Konsolenausgaben, das der Code in einem eigenen Thread läuft?
- 11087 views
Lösungen (Threads)
Lösungen (Threads)Programmieraufgabe JoinAndSleep
package s2.thread;public class JoinAndSleep extends Thread{
/**
* Der Thread auf dessen Beendigung gewartet wird
*/
Thread joinIt;
/**
* Zeit in Millisekunden zum Schlafen
*/
int schlafen;
/**
* Der Konstruktor
* @param sleeptime Die Zeit in msdie der Thread schlafen soll
* @param toJoin der Thread auf den gewartet werden solll bis er beendet ist
*/
public JoinAndSleep(int sleeptime, Thread toJoin) {
joinIt = toJoin;
schlafen = sleeptime;
System.out.println("Thread: " + this + " erzeugt");
}
/**
* Die überschriebene Methode run() sie führt den Code aus
*/
public void run() {
System.out.println("Thread: " +Thread.currentThread() + " gestartet");
try {
if (joinIt!=null) {
joinIt.join();
System.out.println("Thread: " +Thread.currentThread()
+ " join auf " + joinIt + " fertig");
}
} catch (InterruptedException e) {}
System.out.println("Thread: " +Thread.currentThread()
+ " schlaeft jetzt fuer " + schlafen + "ms");
try {
Thread.sleep(schlafen);
} catch (InterruptedException e) {}
System.out.println("Thread: " +Thread.currentThread() + " endet");
}
/**
* Das Hauptprogramm
* @param args keine Parameter benoetigt!
*/
public static void main (String[] args) {
JoinAndSleep s3= new JoinAndSleep(2003, null);
JoinAndSleep s2= new JoinAndSleep(2002, s3);
JoinAndSleep s1= new JoinAndSleep(2001, s2);
s1.start();
s2.start();
s3.start();
}
}
- 4595 views
Lernziele (Threading)
Lernziele (Threading)
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zur Nebenläufigkeit ( Multithreading)
Feedback
- 2809 views