Einführung in Java /* * The HelloWorldApp class implements an * application that simply displays * "Hello World!" to the standard output. */ class HelloWorldApp { public static void main(String[] args) { System.out.println("Hello World!"); } } Hochschule für Angewandte Wissenschaften Hamburg Hamburg University of Applied Sciences Department Informations- und Elektrotechnik 02.2008 Thomas Klinker Inhalt 1 2 OOP I : Klassen und Objekte ______________________________________________ 5 1.1 Erste Java-Programme_____________________________________________________ 5 1.2 Einführung in Klassen und Objekte __________________________________________ 8 Grundelemente von Java_________________________________________________ 20 2.1 Datentypen, Namen, Felder ________________________________________________ 20 2.2 Die Collection ArrayList __________________________________________________ 28 2.3 Kontrollstrukturen _______________________________________________________ 32 2.4 Operatoren _____________________________________________________________ 33 2.5 Ausnahmebehandlung (Exception-Handling) _________________________________ 36 2.6 Einfache Ein- und Ausgabe ________________________________________________ 40 2.7 Parameterübergabe bei Methoden __________________________________________ 45 2.8 Zuweisung von Objekten __________________________________________________ 46 2.9 Vergleich von Objekten ___________________________________________________ 49 2.10 3 4 Übungsaufgaben _______________________________________________________ 51 OOP II : Vererbung_____________________________________________________ 53 3.1 Das Konzept der Vererbung _______________________________________________ 53 3.2 Die Basisklasse Object ____________________________________________________ 63 3.3 Typkonvertierung in Vererbungshierarchien _________________________________ 65 3.4 Verwendung des Operators instanceof____________________________________ 66 3.5 Pakete und Sichtbarkeit ___________________________________________________ 67 3.6 Übungsaufgaben ________________________________ Fehler! Textmarke nicht definiert. Grafikprogramme, Maus und Tastatur _____________________________________ 73 4.1 Maus- und Tastaturereignisse ______________________________________________ 78 4.1.1 4.1.2 4.2 5 6 7 Maus ______________________________________________________________________ 78 Tastatur ____________________________________________________________________ 82 Übungsaufgaben _________________________________________________________ 85 Applets _______________________________________________________________ 86 5.1 Das Einbinden eines Applets _______________________________________________ 89 5.2 Übungsaufgaben _________________________________________________________ 92 OOP III : Polymorphie, Abstrakte Klassen und Interfaces ______________________ 94 6.1 Polymorphie und spätes Binden ____________________________________________ 94 6.2 Abstrakte Klassen und Interfaces ___________________________________________ 99 6.3 Das Interface Cloneable _______________________________________________ 101 6.4 Das generische Interface Comparable<T> _________________________________ 106 6.5 Ereignisbehandlung _____________________________________________________ 107 Timer und Threads ____________________________________________________ 112 7.1 Timer _________________________________________________________________ 112 Inhalt 8 7.2 Threads und das Interface Runnable ______________________________________ 115 7.3 Übungsaufgaben ________________________________________________________ 120 Swing-Komponenten ___________________________________________________ 121 8.1 Übersicht über die Swing-Komponenten ____________________________________ 121 8.2 JButton und JLabel _____________________________________________________ 123 8.3 Layout-Manager ________________________________________________________ 124 8.4 JDialog und JOptionPane ________________________________________________ 131 8.5 JCheckBox und JRadioButton ____________________________________________ 135 8.6 JTextField, JTextArea und JScrollPane_____________________________________ 136 8.7 JSlider und JProgressbar ________________________________________________ 139 8.8 JMenu und JPopupmenu _________________________________________________ 141 8.9 JFileChooser ___________________________________________________________ 144 8.10 9 3 Übungsaufgaben ______________________________________________________ 146 Anhang______________________________________________________________ 147 9.1 Schlüsselwörter (keywords) in Java ________________________________________ 147 9.2 Einige Java-Begriffe _____________________________________________________ 147 9.3 Package java.lang _______________________________________________________ 148 9.3.1 9.3.2 9.3.3 9.4 Class Summary _____________________________________________________________ 148 Class java.lang.Math _________________________________________________________ 149 Class java.lang.String ________________________________________________________ 150 Class java.awt.Graphics __________________________________________________ 153 Literatur Bücher [1] Krüger, G.: Handbuch der Java-Programmierung. Addison-Wesley 2008. (Download von www.gkrueger.com) [2] Eckel, B.: Thinking in Java. Prentice Hall 2006. (Download von www.BruceEckel.com) [3] Samaschke, K.: Java 6 – Einstieg für Anspruchsvolle. Addison-Wesley / Pearson Studium 2007. [4] Merker, E. und Merker, R: Programmieren lernen mit Java. Vieweg 2006. [5] Horstmann, C. und Cornell, G: Java, Band 1 – Grundlagen. Addison-Wesley 2005. [6] Louis, D. und Müller, P: Jetzt lerne ich Java 6. Markt + Technik 2007. [7] Flanagan, D.: Java in a Nutshell. Deutsche Ausgabe für Java 1.2 und 1.3. O'Reilly 2000 Tutorial [8] Java Tutorial. (java.sun.com/docs/books/tutorial/) Entwicklungswerkzeuge [9] Eclipse SDK 3.0.2. (Download von www.eclipse.org/downloads/) [10] Java Development Kit. (Download von java.sun.com) [11] Java Hilfe Dateien. (Download von java.sun.com/docs/) 1 OOP I : Klassen und Objekte Die objektorientierte Programmiersprache Java wurde beginnend Mitte der 90er Jahren bei Sun Microsystems entworfen. − Java greift in seinen grundlegenden Datentypen und Kontrollstrukturen auf C zurück. − Die objektorientierten Konstrukte erinnern an C++. Im Detail unterscheidet sich Java aber recht deutlich von C++. − Applets sind Java-Pogramme, die im Fenster eines HTML-Browsers ausgeführt und dargestellt werden können. 1.1 Erste Java-Programme Ein erstes Java-Programm Datei "Hello.java" /* * The Hello class implements an application that simply * displays "Hello World!" to the standard output */ public class Hello { public static void main(String[] args) { System.out.println("Hello World!"); //Display the string } } Auf der Konsole Hello World! Das Java-Programm besteht aus der Klasse (class) "Hello", die hier lediglich die Methode "main" enthält. Die Ausführung des Programms beginnt bei "main", so wie man es von C her kennt. Die "main" vorausgehenden Schlüsselwörter haben die folgenden Bedeutungen: − "public" bewirkt, dass die Methode "main" auch außerhalb der Klasse bekannt ist. − "static" hat zur Folge, dass "main" aufgerufen werden kann, ohne dass eine Instanz der Klasse angelegt werden muss (vgl. Abschnitt 1.2). − "void" zeigt, dass "main" keinen Funktionswert zurückgibt. Die Methode "main" übernimmt als einzigen Parameter immer ein Feld mit Elementen vom Datentyp "String" (Zeichenkette). Jeder "String" im Feld enthält ein Kommandozeilenargument (vgl. Abschnitt 2). In "main" wird die Methode "println" aus der Klasse "System" aufgerufen. Diese Klasse enthält unter anderem Methoden zur Ausgabe ("out"). Wichtig: − Groß-/Kleinschreibung wird beachtet. − Der Quelltext muss in einer Datei "<Name der Klasse>.java" gespeichert werden. − Die Ausgabedatei des Compilers heißt dann "<Name der Klasse>.class", und enthält Java-Bytecode und nicht etwa Maschinenbefehle für den jeweiligen Prozessor. 1 OOP I : Klassen und Objekte 6 JDK (Java Development Kit) Im JDK [6] sind ein Compiler "javac", ein Interpreter "java", ein Appletbetrachter "appletviewer" und weitere Programme enthalten. Übersetzung und Ausführung von "Hello.java" mit dem JDK javac Hello.java java Hello (→ Java-Bytecode in "Hello.class") (Und nicht "java Hello.class") 1995: JDK 1.0 (1.0.2) Einfache Implementierung mit einer Reihe von Unzulänglichkeiten, grundlegende Änderungen waren notwendig. Folge: Viele nicht mehr aktuelle Methoden und Klassen (deprecated, missbilligen, bemäkeln). 1997: JDK 1.1 (1.1.7) Geändertes Ereignis-Modell für die Benutzeroberfläche (GUI, Graphical User Interface), viele neue Klassenbibliotheken. 1999: JDK 1.2 (1.2.2) Ab hier "Java 2 Plattform", neue GUI-Klassenbibliothek (Swing), neue Container-Klassen. 2000: JDK 1.3 (1.3.1) Fehlerbereinigung, neuer Interpreter (Hotspot Virtual Machine). 2002: JDK 1.4 (1.4.2) Verbesserte Performance, Unterstützung von Mausrad und "drag-and-drop". 2004: JDK 1.5 Zahlreiche Verbesserungren. Die Java-Plattform Bild 1.1 zeigt, dass die Java-Umgebung aus den Programmier- und Anwendungsschnittstellen (Application Programming Interfaces, APIs) und der Java Virtuellen Maschine (JVM) besteht. Java Bytecode Java APIs Java Virtual Machine PC (Windows/Linux/...) Bild 1.1 Umgebung bei der Ausführung von Java-Bytecode − − Java APIs sind Bibliotheken mit kompiliertem Code, die von Java-Programmen eingesetzt werden können. Das "Hello" Programm verwendet zum Beispiel ein Java API, um den Text "Hello World!" auszugeben. Java-Bytecode wird von der Java VM interpretiert. Java-Programme laufen also nicht unmittelbar auf dem jeweiligen Prozessor. Vorteil: "Cross Plattform Design", also bessere Portierbarkeit. Nachteil: Java-Programme werden langsamer als kompilierte Programme ausgeführt. 1 OOP I : Klassen und Objekte 7 Ein erstes Java-Applet Datei "HelloApplet.java" // Provides the classes necessary to create an applet and // the classes an applet uses to communicate with its applet // context import java.applet.*; // Contains all of the classes for painting graphics and // images (awt = abstract windows toolkit) import java.awt.*; public class HelloApplet extends Applet { public void paint(Graphics g) { g.drawString("Hello World!", 25, 50); } } Mit "import" werden für das Applet benötigte Bibliotheken importiert. Das Applet besteht aus der Klasse "HelloApplet", erweitert (extends) die Klasse "Applet" (vgl. Abschnitt 3.1) und enthält hier lediglich die Methode "paint". Diese Methode wird von einem Appletviewer oder von einem Web Browser aufgerufen und ist dazu gedacht, grafische Ausgaben zu erzeugen, also z.B. Texte oder Linien zu zeichnen. Der Parameter "g" liefert den Bezug zum grafischen Kontext der Umgebung und wird hier verwendet, um die Zeichenkette "Hello World!" ab dem linken, unteren Anfangspunkt x = 25 und y = 50 auszugeben. Die Datei "HelloApplet.java" muss kompiliert werden, und als Ergebnis entsteht die Datei "HelloApplet.class", die Java Bytecode enthält. Datei "HelloApplet.html" <HTML> <APPLET CODE="HelloApplet.class" WIDTH=400 HEIGHT=300> </APPLET> </HTML> Um das Applet vom Appletviewer oder vom Web Browser darstellen zu lassen, wird eine kleine HTML Datei (Hyper Text Markup Language) benötigt, die denselben Namen wie das Applet haben sollte. In dieser Datei stellt das sogenannte APPLET Tag (Kennung, Markierung) einen Bezug zum Applet her. Darüber hinaus werden Breite und Höhe des Ausgabefläche festgelegt. Bild 1.2 Ausgabe im Web-Browser 1 OOP I : Klassen und Objekte 8 Aufgabe 1.1 Auf der Konsole soll der folgende Text ausgegeben werden. Fb E/I Hamburg a) Schreiben Sie ein Java Programm "Display.java". b) Schreiben Sie ein Applet "DisplayApplet.java" und eine geeignete HTML Datei "DisplayApplet.html". 1.2 Einführung in Klassen und Objekte Ein Java-Programm besteht aus einer Klasse oder aus mehreren miteinander verbundenen Klassen. Mit einer Klasse werden Eigenschaften und Fähigkeiten von Objekten beschrieben. − − Beispiel, Circle1 public class Circle1 { double radius; double area() {return 3.14159 * radius * radius;} public static void main(String[] args) { //Or "Circle1 circle = new Circle1();" Circle1 circle; circle = new Circle1(); circle.radius = 1.5; System.out.println("Circle area: " + circle.area()); } } Auf der Konsole Circle area: 7.068577499999999 Die Definition der Klasse "Circle1" enthält eine Variable ("radius") und Methoden ("area", "main"). Die Klasse "Circle1" stellt einen neuen Datentyp dar. Circle1 circle; circle Bei der Ausführung der Anweisung wird eine Referenz auf eine Instanz (ein Exemplar) der Klasse "Circle1" angelegt (Bild 1.3). Bild 1.3 Referenz circle = new Circle1(); Mit dem Schlüsselwort "new" wird ein Objekt zur Laufzeit des Programms dynamisch erzeugt. Ein Objekt ist eine Instanz einer Klasse. Die Objektvariable "circle" verweist nun also auf ein Objekt vom Datentyp "Circle1" (Bild 1.4). 1 OOP I : Klassen und Objekte circle 9 Eine Instanz der Circle1-Klasse, also ein Objekt vom Datentyp Circle1 Bild 1.4 Die Variable "circle" verweist nun auf ein Objekt vom Typ "Circle1" circle.radius = 1.5; Der Variablen "radius" des Objekts auf das "circle" verweist, oder kurz "radius" von "circle", wird ein Wert zugewiesen. System.out.println("Circle area: " + circle.area()); Die Methode "area" wird auf das Objekt "circle" angewendet (Objektorientierte Programmierung), es wird also die Fläche von "circle" berechnet. Der Rückgabewert wird hier selbsttätig in eine Zeichenkette umgewandelt und danach mit dem Ausgabetext "Circle area: " verkettet (Operator "+"). double area() {return 3.14159 * radius * radius;} Das Objekt "circle", auf das die Methode hier angewendet wird, wird automatisch übergeben. Mit "radius" ist dann also der Radius von "circle" gemeint. Es ist überflüssig und unzulässig "circle.radius" zu schreiben. Konstruktor circle = new Circle1(); Das Klammernpaar in der Anweisung deutet auf den Aufruf einer Methode hin, und es wird tatsächlich eine spezielle Methode aufgerufen, die Konstruktor genannt wird. Jede Klasse in Java besitzt mindestens einen Konstruktor. Die Aufgabe eines Konstruktors ist es, ein neues Objekt zu initialisieren. Da die Klasse "Circle1" keinen Konstruktor enthalten hat, ist von Java automatisch ein leerer1 Standardkonstruktor angelegt worden. Beispiel, Circle2 public class Circle2 { double radius; // Konstruktor Circle2(double r) {radius = r;} double area() {return 3.14159 * radius * radius;} public static void main(String[] args) { Circle2 circle = new Circle2(1.5); System.out.println("Circle area: " + circle.area()); } } Auf der Konsole Circle area: 7.068577499999999 1 Der Standardkonstruktor enthält genau genommen einen Aufruf des Konstruktors der Oberklasse (vgl. Abschnitt 3.1). 1 OOP I : Klassen und Objekte 10 Ein Konstruktor hat immer den gleichen Namen wie seine Klasse, im vorausgehenden Beispiel also "Circle2". Beim Anlegen des neuen Objekts "circle" in der Zeile Circle2 circle = new Circle2(1.5); wird automatisch der Konstruktor aufgerufen, und "radius" von "circle" wird hier mit dem Wert "1.5" initialisiert. Aufgabe 1.2 public class Cube { int dim1, dim2, dim3; ... ... public static void main(String[] args) { Cube cube1 = new Cube(1, 2, 3); System.out.println("Volume of cube1: " + cube1.volume()); Cube cube2 = new Cube(4, 5, 6); System.out.println("Volume of cube2: " + cube2.volume()); } } Auf der Konsole Volume of cube1: 6 Volume of cube2: 120 Vervollständigen Sie das Programm so, dass es die gegebenen Daten ausgibt. Mehrere Konstruktoren Beispiel, Circle3 public class Circle3 { double radius; Circle3(double r) {radius = r;} // Standard constructor (empty) Circle3() {} double area() {return 3.14159 * radius * radius;} public static void main(String[] args) { Circle3 c1 = new Circle3(1.5); Circle3 c2 = new Circle3(); // Copy object c2 = c1; System.out.println("Area of c2: " + c2.area()); } } 1 OOP I : Klassen und Objekte 11 Auf der Konsole Area of c2: 7.068577499999999 Eine Klasse kann mehrere Konstruktoren besitzen, die dann alle den gleichen Namen wie ihre Klasse haben. Sie müssen sich aber in den Datentypen und/oder in der Reihefolge der Datentypen ihrer Parameter unterscheiden, damit der Compiler den jeweils aufzurufenden Konstruktor eindeutig feststellen kann. Methoden mit dem gleichen Namen aber sich unterscheidenden Parameterlisten zu definieren, wird Überladen (Overloading) von Methoden genannt. Datenkapselung Im folgenden Beispiel ist eine Klasse Rect zur Beschreibung von Rechtecken implementiert. Breite und Höhe können über die Methoden setWidth() und setHeight() geändert werden. Die Fläche wird automatisch neu berechnet. Der direkte Zugriff auf die Membervariablen width und height ist aber immer noch möglich. Dadurch kann eine Instanz der Klasse Rect in einen Zustand gebracht werden, in dem die Membervariable area nicht mehr den korrekten Wert enthält. Beispiel, MyRectApp1 /* Klasse MyRectApp1 * Verstoss gegen Datenkapselung * */ class Rect{ double width, height; double area; double getArea(){ return area; } void setWidth(double width){ this.width=width; calculateArea(); } void setHeight(double height){ this.height=height; calculateArea(); } void calculateArea(){ area = width*height; } } public class MyRectApp1{ public static void main(String s[]){ Rect r = new Rect(); System.out.println("area: " + r.getArea()); r.setWidth(1.0); 1 OOP I : Klassen und Objekte 12 r.setHeight(3.0); System.out.println("area: " + r.getArea()); r.width=0.5; System.out.println("area: " + r.getArea()); } } Auf der Konsole area: 0.0 area: 3.0 area: 3.0 Um die Klasse Rect als brauchbare Software nutzen zu können, werden die Membervariablen mit dem Modifikator private vor dem direkten Zugriff geschützt: Beispiel, MyRectApp2 /* Klasse MyRectApp2 * Kein Verstoss mehr gegen Datenkapselung * */ class Rect{ private double width, height; private double area; double getArea(){ return area; } void setWidth(double width){ this.width=width; calculateArea(); } void setHeight(double height){ this.height=height; calculateArea(); } void calculateArea(){ area = width*height; } } public class MyRectApp2{ public static void main(String s[]){ Rect r = new Rect(); System.out.println("area: " + r.getArea()); r.setWidth(1.0); r.setHeight(3.0); System.out.println("area: " + r.getArea()); r.setWidth(0.5); System.out.println("area: " + r.getArea()); } } 1 OOP I : Klassen und Objekte 13 Auf der Konsole area: 0.0 area: 3.0 area: 1.5 Klassenvariablen (static) Beispiel, MyCircleApp4 /* * * */ MyCircleApp4 Verwendung von Klassenvariablen (static) class Circle { // Klassenvariable numberOfCircles static int numberOfCircles = 0; private double radius; Circle(double r) { radius = r; numberOfCircles++; } double area() {return 3.14159 * radius * radius;} } public class MyCircleApp4 { public static void main(String[] args) { System.out.println("Number of circles: " + Circle.numberOfCircles); Circle c1 = new Circle(1.0); System.out.println("Number of circles: " + Circle.numberOfCircles); System.out.println(" Area of c1: " + c1.area()); Circle c2 = new Circle(2.0); System.out.println("Number of circles: " + Circle.numberOfCircles); System.out.println(" Area of c2: " + c2.area()); } } Auf der Konsole Number Number Area Number Area of of of of of circles: 0 circles: 1 c1: 3.14159 circles: 2 c2: 12.56636 In der Klassendefinition gibt es die Instanzvariable radius. Jede Instanz der Klasse, also jedes Objekt, hat eine eigene Kopie dieser Variablen. Neu ist die Klassenvariable numberOfCircles. 1 OOP I : Klassen und Objekte 14 − Klassenvariablen werden mit dem Schlüsselwort "static" vereinbart. − Es existiert nur ein Exemplar von Klassenvariablen, das schon beim Programmstart angelegt wird. Bild 1.5 verdeutlicht den Unterschied zwischen Klassen- und Instanzvariablen. Klasse Circle4 numberOfCircles Klassenvariable Objekt c1 Objekt c2 radius radius Instanzvariablen Bild 1.5 Klassen- und Instanzvariablen Konstanten (static final) Da in Java kein Präprozessor existiert, sind Vereinbarungen der Art "#define MAX 100" nicht zulässig. Beispiel, MyCircleApp5 /* * * */ MyCircleApp5 Verwendung von Konstanten (static final) class Circle { // Konstante MAXRADIUS static final double MAXRADIUS = 2.0; static int numberOfCircles = 0; private double radius; Circle(double r) { radius = (r <= MAXRADIUS) ? r : MAXRADIUS; numberOfCircles++; } double area() {return 3.14159 * radius * radius;} } public class MyCircleApp5 { public static void main(String[] args) { System.out.println("Number of circles: " + Circle.numberOfCircles); Circle c1 = new Circle(1.0); System.out.println("Number of circles: " 1 OOP I : Klassen und Objekte 15 + Circle.numberOfCircles); System.out.println(" Area of c1: " + c1.area()); Circle c2 = new Circle(3.5); System.out.println("Number of circles: " + Circle.numberOfCircles); System.out.println(" Area of c2: " + c2.area()); } } Auf der Konsole Number Number Area Number Area of of of of of circles: 0 circles: 1 c1: 3.14159 circles: 2 c2: 12.56636 Konstanten werden auf Klassenebene mit dem Schlüsselwort "final" vereinbart. Der Wert wird bei der Vereinbarung zugewiesen und lässt sich danach nicht mehr verändern. Klassenmethoden (static) Beispiel, MyCircleApp6 /* * */ MyCircleApp6 Verwendung von Klassenmethode (static) class Circle { private static int numberOfCircles = 0; private double radius; Circle(double r) { radius = r; numberOfCircles++; } // Klassenmethode getNumberOfCircles() static int getNumberOfCircles() { return numberOfCircles; } double area() {return 3.14159 * radius * radius;} } public class MyCircleApp6 { public static void main(String[] args) { System.out.println("Number of circles: " + Circle.getNumberOfCircles()); Circle c1 = new Circle(1.0); System.out.println("Number of circles: " + Circle.getNumberOfCircles()); System.out.println(" Area of c1: " + c1.area()); 1 OOP I : Klassen und Objekte 16 Circle c2 = new Circle(2.0); System.out.println("Number of circles: " + Circle.getNumberOfCircles()); System.out.println(" Area of c2: " + c2.area()); } } Auf der Konsole Number Number Area Number Area of of of of of circles: 0 circles: 1 c1: 3.14159 circles: 2 c2: 12.56636 In der Klassendefinition gibt es wie in den vorausgehenden Beispielen die Instanzmethode "area()". Instanzmethoden werden auf Objekte angewendet. Schreibweise: <Objekt>.<Instanzmethode(...)>, z. B. c1.area() Zusätzlich gibt es die Klassenmethode "getNumberOfCircle()". Klassenmethoden werden auf die Klasse angewendet. Schreibweise: <Klasse>.<Klassenmethode(...)>, z. B. Circle.getNumberOfCircles() Zusammenfassend: − Klassenmethoden werden mit dem Schlüsselwort "static" vereinbart. − Da Klassenmethoden unabhängig von konkreten Instanzen der Klasse existieren, ist ein Zugriff auf Instanzvariablen nicht möglich! Die Klassenmethode "main()" wird beim Programmstart von der Java-Umgebung aufgerufen. Mehrere Klassen Wie schon in einigen Beispielen zuvor werden im folgenden Beispiel mehrere Klassen verwendet. Sie können in einer Datei gespeichert werden. Dann darf nur eine dieser Klassen als "public" deklariert werden, und der Dateinamen muss mit dem Namen dieser Klasse übereinstimmen. Der Compiler erzeugt für jede Klasse eine Ausgabedatei <Klassennamen>.class. In der Regel sollte jede Klasse in einer eigenen Datei stehen. Beispiel, Circle7 class MyMath { static final double PI = 3.14159; static double square(double value) { return value * value; } } public class Circle7 { double radius; Circle7(double r) {radius = r;} 1 OOP I : Klassen und Objekte 17 double area() {return MyMath.PI * MyMath.square(radius);} public static void main(String[] args) { Circle7 circle = new Circle7(1.5); System.out.println("Area of circle: " + circle.area()); } } Auf der Konsole Area of circle: 7.068577499999999 Das Beispiel zeigt noch einmal, dass Klassenvariablen und Klassenmethoden außerhalb ihrer Klasse mit dem vorgestellten Klassennamen aufgerufen werden. Die Klasse "Math" Im Anhang ist ein Überblick über die Klasse "Math" aus dem Paket "java.lang" zu finden. Für dieses grundlegende Paket ist keine Anweisung "import java.lang.*;" erforderlich. Beispiel, Circle8 public class Circle8 { double radius; Circle8(double r) {radius = r;} double area() {return Math.PI * Math.pow(radius, 2.0);} public static void main(String[] args) { Circle8 circle = new Circle8(1.5); System.out.println("Area of circle: " + circle.area()); } } Auf der Konsole Area of circle: 7.0685834705770345 Es werden "PI" und "pow" aus der Klasse "Math" eingesetzt, die in der Klasse als "static" deklariert worden sind. In beiden Fällen wird deshalb der Klassenname "Math" angegeben. Aufgabe 1.3 public class Circle { double radius; static double area(double r) {return Math.PI * Math.pow(r, 2.0);} public static void main(String[] args) { radius = 1.5; System.out.println("Area of circle: " + area(radius)); } } 1 OOP I : Klassen und Objekte 18 Fehlermeldungen des Compilers Circle.java:7: non-static variable radius cannot be referenced from a static context radius = 1.5; ^ Circle.java:9: non-static variable radius cannot be referenced from a static context System.out.println("Area of circle: " + area(radius)); ^ Warum gibt der Compiler die Fehlermeldungen aus? Korrigieren Sie das Programm. Aufgabe 1.4 public class Tabel_a { public static void main(String[] args) { String angleStr, sinStr, cosStr, tanStr; System.out.println("Angle Sin Cos Tan "); System.out.println("------------------------------"); for(int angle = 0; angle <= 60; angle += 10) { double rad = angle * Math.PI / 180.0; angleStr = String.format("%5d", angle); sinStr = String.format("%8.3f", Math.sin(rad)); cosStr = String.format("%8.3f", Math.cos(rad)); tanStr = String.format("%8.3f", Math.tan(rad)); System.out.println( angleStr + sinStr + cosStr + tanStr); } } } Was gibt das Programm aus? Die Referenz "this" Beim Aufruf einer Instanzmethode wird implizit eine Referenz "this" auf das Objekt übergeben, auf das die Methode angewendet wird. Das folgende Beispiel zeigt, wie man diese Referenz im eigenen Programmcode einsetzen kann. Beispiel, MyRectApp3 /* Klasse MyRectApp3 * Komplett mit getter und setter * */ 1 OOP I : Klassen und Objekte 19 class Rect{ private double width, height; private double area; public double getWidth() { return width; } public double getHeight() { return height; } public double getArea(){ return area; } public void setWidth(double width){ this.width=width; calculateArea(); } public void setHeight(double height){ this.height=height; calculateArea(); } private void calculateArea(){ area = width*height; } } public class MyRectApp3{ public static void main(String s[]){ Rect r = new Rect(); System.out.println("area: " + r.getArea()); r.setWidth(1.0); r.setHeight(3.0); System.out.println("width: " + r.getWidth()); System.out.println("height: " + r.getHeight()); System.out.println("area: " + r.getArea()); } } Auf der Konsole area: 0.0 width: 1.0 height: 3.0 area: 3.0 Hinweis: Instanzvariablen werden automatisch initialisiert: numerische Variablen auf 0, boolesche Variablen auf false, Strings auf Null-String "", Objekte auf null. Lokale Variablen von Methoden hingegen werden nicht automatisch initialisiert. 2 Grundelemente von Java Kommentare /** * */ This class displays a text string at the console /* A C-style comment */ public class Hello1 { // Another C-style comment public static void main(String[] args){ System.out.println("Hello World!"); } } Java unterstützt drei Kommentarformen: − Die von C bzw. C++ vertrauten "/* ... */" und "// ..." Kommentare. − Dokumentationskommentare (doc comments) "/** ... */", die vom Programm "javadoc" ausgewertet werden, um eine einfache Dokumentation zu erstellen. 2.1 Datentypen, Namen, Felder Einfache Datentypen Data Type byte short int long float double char boolean Description Integers Byte-length integer Short integer Integer Long integer Real Numbers Single-precision floating point Double-precision floating point Other Types A single character A boolean value Size/Format Range 8-bit two's compl. 16-bit two's compl. 32-bit two's compl. 64-bit two's compl. -128 to +127 -32 768 to +32 767 9 9 -2.110 to +2.110 18 18 -9.210 to +9.210 32-bit IEEE 754 64-bit IEEE 754 ±3.410 to ±3.410 -308 308 ±1.710 to ±1.710 -38 16-bit Unicode char. true or false Vergleich mit C: − Java kennt keine vorzeichenlosen Datentypen. − Es gibt zusätzlich die Datentypen "byte" und "boolean". − Die Länge von "int" ist auf 32 Bit festgelegt. − Zeichen (character) werden nicht im 8-Bit ASCII Code sondern im 16-Bit Unicode gespeichert. − Es existieren keine Zeiger (z.B. "int*"). − Es gibt keine Strukturen ("struct"), keine Bitfelder und keine Unions ("union"). 38 21 2 Grundelemente von Java Beispiele für Konstanten Literal 257 0x1AF 8864L 37.266 37.266D or 37.266d 87.363F or 87.363f 26.77e3 'c' true false Data Type int int long double double float double char boolean boolean Namen Namen bestehen aus Buchstaben (Unicode, also auch Umlaute zulässig), Ziffern und den Zeichen '_' (Unterstrich) und '$' (Dollar). Das erste Zeichen darf dabei keine Ziffer sein. Die folgenden Konventionen machen Java-Programme lesbarer. Es ist gute Programmierpraxis sich an diese Namenskonventionen zu halten. − Die Namen von Variablen und Methoden beginnen mit einem Kleinbuchstaben, neue Wörter innerhalb des Namens mit einem Großbuchstaben. Variablen: kontoNummer, teilnehmerName, index, temp Methoden: main, paint, getValue, toString − Die Namen von Klassen beginnen mit einem Großbuchstaben, neue Wörter innerhalb des Namens ebenfalls mit einem Großbuchstaben. Beispiele: Hello, Echo, HelloApplet, AgeLimit − Die Namen von Konstanten bestehen aus Großbuchstaben, neue Wörter innerhalb des Namens werden durch einen Unterstrich "_" eingeleitet. Beispiele: PI, MAX_X_VALUE, HINTERGRUND_FARBE Beispiel, AgeLimit public class AgeLimit1 { final static int LOWER_AGE_LIMIT = 12; public static void main(String[] args) { int customerAge = 11; String tooYoung = "Too young"; String ageOk = "Age ok"; if(customerAge < LOWER_AGE_LIMIT) System.out.println(tooYoung); else System.out.println(ageOk); } } Auf der Konsole Too young // if ... else ... 2 Grundelemente von Java 22 Zeichenketten Zeichenketten (Strings) sind in Java Objekte. Der Name eines Strings ist eine Referenzvariable, die auf den String verweist. Es ist zu beachten, dass durch die Klasse String keine dynamischen Zeichenketten implementiert werden. Nach der Initialisierung eines StringObjektes bleiben dessen Länge und dessen Inhalt konstant. Die einzelnen Zeichen lassen sich also nicht mehr ändern. Es gibt drei Konstruktoren: String() Erzeugt ein leeres String-Objekt. String(String value) Erzeugt ein String-Objekt durch kopieren eines Vorhandenen. String(char[] value) Erzeugt ein String-Objekt aus einem vorhandenen Charakter-Array. Wichtig ist, zu beachten, dass Variablen vom Typ String Referenzvariablen sind. Sie verweisen auf ein Objekt, dass mit Hilfe des new-Operators auf dem Heap angelegt wird. Das bedeutet, nach den folgenden Anweisungen String name1 = new String("Anja"); String name2 = new String("Herbert"); name2 = name1; zeigen beide Referenzen name1 und name2 auf die Zeichenkette "Anja". Beim Vergleichen von Strings muss deshalb ebenfalls aufgepasst werden. Nach den Anweisungen String s1 = new String("Hallo"); String s2 = new String("Hallo"); liefert die Anweisung s1 == s2 den Wert false, da die Referenzvariablen s1 und s2 auf verschiedene Objekte im Speicher zeigen, die zwar den gleichen Inhalt haben aber nicht identisch sind. Die Anweisung s1.equals(s2) liefert jedoch, wie erwartet, den Wert true, da sie die Objekte auf inhaltliche Gleichheit prüft. Im Folgenden sind noch einige Methoden der String-Klasse aufgeführt. Dabei ist zu beachten, dass jede Methode der Klasse String, die eine veränderte Zeichenkette erzeugt, wie z.B. die Methode substring(), eine neue Instanz der Klasse String liefert und nicht die Original-Instanz mit einem geänderten Inhalt. Denn es gilt, String-Instanzen können nach ihrer Initialisierung nicht mehr geändert werden. public int length() Gibt die Anzahl der Zeichen einer Zeichenkette zurück. public boolean equals(Object obj) Vergleicht zwei String-Objekte miteinander und gibt true zurück, falls die String-Objekte den gleichen Inhalt haben. 2 Grundelemente von Java 23 public String substring(int anfang, int ende) Schneidet aus dem aufrufenden String-Objekt die Zeichen von der Position anfang bis zur Position ende-1 heraus und gibt den herausgeschnittenen Teil als neues String-Objekt zurück. Man beachte, dass das erste Zeichen den Index 0 hat. Aufgabe 2.1 Erläutern Sie, warum es zu den gegebenen Konsolenausgaben kommt. public class StringApp { public static String s1 = String s2 = String s3 = String s4 = void main(String[] args) { new String("Test"); new String("Test"); "Te"; "st"; System.out.println("1: System.out.println("2: System.out.println("3: System.out.println("4: " " " " + + + + (s1 == "Test")); (s1 == s2)); ((s3+s4) == s1)); ((s3+s4).equals(s1))); String s5 = s2.substring(1, 3); System.out.println("5: " + s5); System.out.println("6: " + (s2.substring(1, 3) == s5)); System.out.println("7: " + (s2.substring(1, 3).equals(s5))); } } Auf der Konsole 1: 2: 3: 4: 5: 6: 7: false false false true es false true Arrays für einfache Datentypen Beispiel, IntArray1 public class IntArray1 { public static void main(String[] args) { int[] anArray; anArray = new int[10]; // Step1: Declare the array // Step2: Allocate storage // Step3: Initialize the array for (int i = 0; i < anArray.length; i++) { anArray[i] = i; System.out.print(anArray[i] + " "); } 2 Grundelemente von Java 24 System.out.println(); } } Auf der Konsole 0 1 2 3 4 5 6 7 8 9 Die Länge eines Arrays wird festgelegt, wenn man zur Laufzeit des Programms mit "new" Speicher für das Array anfordert. Danach hat das Array dann eine feste Länge. Vorsicht: − Die Länge eines Strings erhält man mit "<Name des Strings>.length()". Die Methode "length()" aus der Klasse "String" wird dann also auf den String angewendet. − Die Anzahl der Arrayelemente wird dagegen mit "<Name des Arrays>.length" (ohne Klammernpaar!) ermittelt; "length" ist hier keine Methode sondern eine Instanzvariable des jeweiligen Array-Objektes. Wie in C reicht der zulässige Indexbereich immer von 0 bis "Arraylänge-1", für einen Array der Länge 10 also zum Beispiel von 0 bis 9. Beispiel, IntArray2 public class IntArray2 { public static void main(String[] args) { int[] anArray = {10, 11, 12, 13, 14}; for (int i = 0; i < anArray.length; i++) System.out.print(anArray[i] + " "); System.out.println(); } } Auf der Konsole 10 11 12 13 14 Das vorausgehende Beispiel zeigt, wie sich ein Feld initialisieren lässt. Die Feldlänge entspricht dann der Anzahl der Werte in der Initialisierungsliste. Beispiel, Matrix public class Matrix { static final int ROWS = 4; static final int COLS = 5; public static void main(String[] args) { double[][] aMatrix = new double[ROWS][COLS]; // or "...; i < ROWS; ..." for (int i = 0; i < aMatrix.length; i++) { // or "...; j < COLS; ..." for (int j = 0; j < aMatrix[i].length; j++) { aMatrix[i][j] = i+j/10.0; 25 2 Grundelemente von Java System.out.print(aMatrix[i][j] + " } System.out.println(); "); } } } Auf der Konsole 0.0 1.0 2.0 3.0 0.1 1.1 2.1 3.1 0.2 1.2 2.2 3.2 0.3 1.3 2.3 3.3 0.4 1.4 2.4 3.4 Man hätte mit der folgenden Anweisung die Matrix bei der Vereinbarung auch unmittelbar initialisieren können. double[][] aMatrix = {{0.0, {1.0, {2.0, {3.0, 0.1, 1.1, 2.1, 3.1, 0.2, 1.2, 2.2, 3.2, 0.3, 1.3, 2.3, 3.3, 0.4}, 1.4}, 2.4}, 3.4}}; Aufgabe 2.2 public class IntArray { int[] myArray; IntArray(int dim) { myArray = new int[dim]; for(int i = 0; i < dim; i++) // random() returns a double value with a positive sign, // greater than or equal to 0.0 and less than 1.0 myArray[i] = (int) (100 * Math.random()); } void sortArray() { ... } public static void main(String[] args) { IntArray tab = new IntArray(8); tab.sortArray(); for (int i = 0; i < tab.myArray.length; i++) System.out.print(tab.myArray[i] + " "); System.out.println(); } } Beispiel zur Ausgabe 16 19 21 22 22 57 59 85 Vervollständigen Sie die Methode "sortArray" so, dass sie das Feld "myArray" in aufsteigender Reihenfolge sortiert. 26 2 Grundelemente von Java Arrays für Referenzdatentypen Beispiel, StringArray public class StringArray { public static void main(String[] args) { String[] anArray; anArray = new String[3]; // Declare the array // Allocate storage for references // Do something anArray[0] = "String 0"; anArray[1] = "String 1"; anArray[2] = anArray[1]; anArray[1] += " + new substring"; // // // // Line Line Line Line 1 2 3 4 for (int i = 0; i < anArray.length; i++) System.out.println(anArray[i]); } } Auf der Konsole String 0 String 1 + new substring String 1 Die Anweisung "anArray = new String[3];" fordert Speicherplatz für 3 Referenzen an und nicht etwa für drei Zeichenketten. Die Zuweisungen in "Line 1" bis "Line 3" führen dazu, dass die Referenz (die Anfangsadresse) der jeweiligen Zeichenkette kopiert wird. In "Line 4" wird eine neue Zeichenkette im dynamischen Speicher abgelegt und deren Referenz dann kopiert. Beispiel, Kommandozeilenargumente public class Echo { public static void main (String[] args) { for (int i = 0; i < args.length-1; i++) System.out.print(args[i]+" "); System.out.println(args[args.length-1]); } } Auf der Konsole java Echo Klaus Waller Hamburg Klaus Waller Hamburg 2 Grundelemente von Java 27 Beispiel, RefArray class Circle { double radius; Circle(double r) {radius = r;} double area() {return 3.14159 * radius * radius;} } public class RefArray { public static void main(String[] args) { // Declare the array and allocate storage for references Circle[] anArray = new Circle[2]; // Initialize the array with objects of class Circle anArray[0] = new Circle(1.0); anArray[1] = new Circle(2.0); for (int i = 0; i < anArray.length; i++) System.out.println("Area " + anArray[i].area()); } } Auf der Konsole Area Area 3.14159 12.56636 Hier wird im ersten Schritt dynamischer Speicher für zwei Referenzen auf "Circle" angefordert. Danach werden zwei Instanzen von "Circle" angelegt und ihre Referenzen nach "anArray" kopiert. Aufgabe 2.3 class Point { int x, y; ... } class Rectangle { Point tl; int width, height; // top left ... } class ChessBoard { static final int DIM = 32; // Height and width of squares Rectangle rct[] = new Rectangle[64]; ... } public class Test { 28 2 Grundelemente von Java public static void main(String[] args) { Point p = new Point(225, 240); System.out.println(p); Rectangle r = new Rectangle(p, 122, 234); System.out.println(r); ChessBoard chb = new ChessBoard(); System.out.println(chb); System.out.println("Point " + p + " in square " + chb.getRectInd(p)); } } Auf der Konsole: x=225 y=240 topleft: x=225 y=240, width=122 height=234 ChessBoard: [0]: topleft: x=0 y=0, width=32 height=32 b)[63]: topleft: x=224 y=224, width=32 height=32 Point x=225 y=240 in square 63 Vervollständigen Sie das Programm so, dass es die angegebenen Daten ausgibt. Insbesondere ist in jeder Klasse eine geeignete toString()-Methode zu programmieren. 2.2 Die Collection ArrayList Mit der Klasse "ArrayList" (ab JDK 1.2) kann man Referenzen auf Objekte dynamisch in eine Liste einfügen und aus dieser Liste wieder löschen. Die Liste wird bei Bedarf selbsttätig vergrößert. Beispiel, ListDemo import java.util.*; public class ListDemo { static void printList(ArrayList aList) { for(int i = 0; i < aList.size(); i++) System.out.println(aList.get(i)); System.out.println("- - - -"); } // Get size // Get object public static void main(String[] args) { String str; ArrayList lst1 = new ArrayList(); for(int i = 0; i < 2; i++) lst1.add("Element " + i); lst1.add(0, "First"); lst1.add(lst1.size(), "Last"); printList(lst1); // Append // Insert at position 2 Grundelemente von Java lst1.remove("Last"); lst1.remove(0); lst1.set(1, "Set"); printList(lst1); 29 // Remove element // Remove at position // Replace at position ArrayList lst2 = new ArrayList(lst1); lst1.remove(0); lst2.addAll(lst1); // Append all printList(lst2); str = (String) lst2.get(1); System.out.println(str); // get with typecast lst2.clear(); printList(lst2); // Remove all } } Auf der Konsole: First Element Element Last - - - Element Set - - - Element Set Set - - - Set - - - - 0 1 0 0 Die Klasse ArrayList gibt es ab Java 5 in einer typsicheren Form. Diese Typsicherheit ist mit Hilfe so genannter Generics implementiert worden. Es handelt sich dabei um CollectionKlassen, in die nicht allgemein Objekte vom Typ Object abgelegt werden können, sondern die durch eine vorhergehende Typisierung sicherstellen, dass nur Objekte eines bestimmten Typs (etwa Integer oder String) in die Liste eingefügt werden können. Der Typ der Elemente wird dabei als Typparameter übergeben, z.B. ArrayList<String>. Im folgenden sind einige Listenoperationen dargestellt: Element Hinzufügen, add() import java.util.ArrayList; import java.util.Arrays; public class ArrayList_Add { public static void main(String args[]) { 2 Grundelemente von Java ArrayList<String> list = new ArrayList<String>(); list.add("One"); list.add("Two"); list.add("Three"); // for-each-Schleife for(String item : list) { System.out.println(item); } } } Auf der Konsole: One Two Three Liste Hinzufügen, addAll() import java.util.ArrayList; import java.util.Arrays; public class ArrayList_AddAll { public static void main(String args[]) { ArrayList<String> list = new ArrayList<String>(); list.add("One"); String[] data = { "Two", "Three", "Four", "Five" }; list.addAll(Arrays.asList(data)); for(String item : list) { System.out.println(item); } } } Auf der Konsole: One Two Three Four Five Abrufen von Werten, get() import java.util.ArrayList; import java.util.Arrays; 30 2 Grundelemente von Java 31 public class ArrayList_For { public static void main(String args[]) { ArrayList<String> list = new ArrayList<String>(); list.add("One"); list.add("Two"); list.add("Three"); for(int i=0; i<list.size(); i++) { System.out.println(String.format ("Element at Index #%d: \"%s\"", i, list.get(i))); } } } Auf der Konsole: Element at Index #0: "One" Element at Index #1: "Two" Element at Index #2: "Three" Ersetzen von Werten, set() import java.util.ArrayList; import java.util.Arrays; public class ArrayList_Set { public static void main(String args[]) { String[] data = { "One", "Two", "Three"}; ArrayList<String> list = new ArrayList<String>(Arrays.asList(data)); list.set(0, "Zero"); for(String item : list) { System.out.println(String.format("%s", item)); } } } Auf der Konsole: Zero Two Three 2 Grundelemente von Java 2.3 32 Kontrollstrukturen Schleifen while (boolean expression) { statement(s) } Die Bedingung (boolean expression) wird am Schleifenanfang geprüft. Die Anweisungen in der Schleife werden gegebenenfalls nicht ein einziges Mal ausgeführt. do { statement(s) } while (boolrean expression); Die Bedingung (boolean expression) wird am Schleifenende geprüft. Die Anweisungen in der Schleife werden immer mindestens ein Mal ausgeführt. for (initialization; boolean expression; increment) { statement(s) } Die Bedingung (boolean expression) wird am Schleifenanfang geprüft Die Anweisungen in der Schleife werden gegebenenfalls nicht ein einziges Mal ausgeführt. Bedingte Ausführung und Alternativen if (boolean expression) { statement(s) } Die Anweisungen (statement(s)) werden nur ausgeführt, wenn die Bedingung wahr ist. if (boolean expression) { statement(s) 1 } else { statement(s) 2 } Abhängig von der Bedingung werden alternativ nur die ersten Anweisungen (statement(s) 1) oder die zweiten Anweisungen (statement(s) 2) ausgeführt. if (boolean expression 1) { statement(s) 1 } else if (boolean expression 2) { statement(s) 2 } else if (boolean expression 3) { statement(s) 3 } else { statement(s) 4 } 2 Grundelemente von Java 33 Abhängig von den Bedingungen (logical expression 1 to 3) werden alternativ nur die ersten Anweisungen (statement(s) 1), oder die zweiten Anweisungen (statement(s) 2), usw. ausgeführt. switch (integer expression) { case integer expression 1: statement(s) 1 break; case integer expression 2: statement(s) 2 break; ... ... default: statement(s) break; } Abhängig vom ganzzahligen Ausdruck (integer expression) wird zum zugehörigen Fall (case) verzweigt. Die Anweisungen werden ab dieser Stelle bis zum "break" ausgeführt. Wenn keiner der Fälle zutrifft, wird der default-Zweig gewählt. Weitere Kontrollanweisungen continue Die Anweisungen unterbricht die innerste Schleife und führt dazu, dass deren Schleifenbedingung erneut ausgewertet wird. break Die Anweisung beendet die innerste "switch", "for", "while" oder "do while" Anweisung. 2.4 Operatoren Arithmetische Operatoren Operator + * / % Use op1 op1 op1 op1 op1 Operator ++ Use op++ ++ ++op + * / % op2 op2 op2 op2 op2 Description Adds op1 and op2 Subtracts op2 from op1 Multiplies op1 by op2 Divides op1 by op2 Computes the remainder of dividing op1 by op2 Description Increments op by 1; evaluates the value of op before the increment operation Increments op by 1; evaluates the value of op after 2 Grundelemente von Java -- op-- -- --op the increment operation Decrements op by 1; evaluates the value of op before the increment operation Decrements op by 1; evaluates the value of op after the increment operation Vergleichsoperatoren Operator > >= < <= == != Use op1 op1 op1 op1 op1 op1 > op2 >= op2 < op2 <= op2 == op2 != op2 Returns true if op1 is greater than op2 op1 is greater than or equal to op2 op1 is less than op2 op1 is less than or equal to op2 op1 and op2 are equal op1 and op2 are not equal Logische Operatoren Operator && || ! Use op1 && op2 op1 || op2 !op Returns true if op1 and op2 are both true either op1 or op2 is true op is false Schiebeoperatoren Operator >> << >>> 34 Use op1 >> op2 op1 << op2 op1 >>> op2 Operation shift bits of op1 right by distance op2 shift bits of op1 left by distance op2 shift bits of op1 right by distance op2 (unsigned) Aufgabe 2.4 public class Shift { public static void main(String[] args) { int val = 0xFFFFFFFF; String hexStr1 = "0x" + Integer.toHexString(val >> 3); String hexStr2 = "0x" + Integer.toHexString(val >>> 3); System.out.println("val >> 3: " + hexStr1); System.out.println("val >>> 3: " + hexStr2); } } Was gibt das Programm aus? 2 Grundelemente von Java 35 Bitoperatoren Operator & | ^ ~ Use op1 & op2 op1 | op2 op1 ^ op2 ~op2 Operation bitwise AND bitwise OR bitwise XOR bitwise NOT Zuweisungsoperatoren Operator = += -= *= /= %= &= |= ^= <<= >>= >>>= Use op1 op1 op1 op1 op1 op1 op1 op1 op1 op1 op1 op1 Equivalent to = op2 += op2 -= op2 *= op2 /= op2 %= op2 &= op2 |= op2 ^= op2 <<= op2 >>= op2 >>>= op2 op1 op1 op1 op1 op1 op1 op1 op1 op1 op1 op1 = = = = = = = = = = = op1 op1 op1 op1 op1 op1 op1 op1 op1 op1 op1 + op2 - op2 * op2 / op2 % op2 & op2 | op2 ^ op2 << op2 >> op2 >>> op2 Weitere Operatoren Operator ?: Use op1 ? op2 : op3 [] type [] [] type[op1] [] op1[op2] . () op1.op2 op1(params) (type) (type) op1 new new op1 instanceof op1 instanceof op2 Description If op1 is true, returns op2. Otherwise, returns op3. Declares an array of unknown length, which contains type elements. Creates and array with op1 elements. Must be used with the new operator. Accesses the element at op2 index within the array op1. Indices begin at 0 and extend through the length of the array minus one. Is a reference to the op2 member of op1. Declares or calls the method named op1 with the specified parameters. Casts (converts) op1 to type. An exception will be thrown if the type of op1 is incompatible with type. Creates a new object or array. op1 is either a call to a constructor, or an array specification. Returns true if op1 is an instance of op2. 2 Grundelemente von Java 36 Priorität der Operatoren In der folgenden Tabelle sind die Operatoren in der Reihenfolge ihrer Priorität aufgelistet: je höher in der Tabelle ein Operator aufgeführt ist, desto größer ist seine Priorität. Operatoren in derselben Zeile haben die gleiche Priorität. postfix operators unary operators creation or cast multiplicative additive shift relational equality bitwise AND bitwise exclusive OR bitwise inclusive OR logical AND logical OR conditional assignment [] . (params) expr++ expr-++expr --expr +expr -expr ~ ! new (type)expr * / % + << >> >>> < > <= >= instanceof == != & ^ | && || ? : = += -= *= /= %= &= ^= |= <<= >>= >>>= Für Operatoren mit derselben Priorität gelten die folgenden Zusatzregeln: − Wenn binäre Operatoren derselben Priorität mit Ausnahme von Zuweisungsoperatoren in einem Ausdruck (expression) zu finden sind, wird der Ausdruck von links nach rechts ausgewertet. − Zuweisungsoperatoren werden von rechts nach links ausgewertet. 2.5 Ausnahmebehandlung (Exception-Handling) Im Beispiel "Exception1" wird die Programmausführung mit einer Fehlermeldung beendet, wenn es zu einer Division durch Null kommt. Beispiel, Exception1 public class Exception1 { public static void main(String[] args) { for(int i = -2; i <= 2; i++) System.out.println("100/" + i + " = " + 100/i); } } Auf der Konsole 100/-2 = -50 100/-1 = -100 Exception in thread "main" java.lang.ArithmeticException: / by zero at Exception1.main(Exception1.java:5) 2 Grundelemente von Java 37 In Java gibt es die Möglichkeit, einen Programmabschnitt zu markieren ("try", versuchen), in diesem Programmabschnitt auftretende Laufzeitfehler "abzufangen" ("catch") und auf sie dann geeignet zu reagieren. Beispiel, Exception2 public class Exception2 { public static void main(String[] args) { for(int i = -2; i <= 2; i++) { try { System.out.println("100/" + i + " = " + 100/i); } catch(ArithmeticException e) { System.out.println("100/0 = ???? " + e); } } } } Auf der Konsole 100/-2 = -50 100/-1 = -100 100/0 = ???? 100/1 = 100 100/2 = 50 java.lang.ArithmeticException: / by zero Wenn es im try-Block zu einer Ausnahmesituation kommt, unterbricht die Laufzeitumgebung die normale Programmausführung. Das Programm wird dann mit den Anweisungen in der catch-Klausel fortgesetzt, die zur jeweiligen Ausnahmesituation passt, im Beispiel also in der catch-Klausel für eine arithmetische Ausnahmesituation (ArithmeticException). Gibt es keine passende catch-Klausel, wird die Fehlerbehandlung von der Laufzeitumgebung übernommen. Java-Programme haben grundsätzlich die Pflicht, den Compiler über möglicherweise auftretende Fehler zu informieren. Dabei gilt die sogenannte "catch-or-throw"-Regel. Diese Regel besagt, dass jede Ausnahme entweder mit einer catch-Klausel abgefangen oder mit "throws" weitergegeben werden muss. Das folgende Beispiel zeigt die Vorgehensweise bei der Weitergabe. Beispiel, Exception3 public class Exception3 { public static void main(String[] args) throws ArithmeticException { for(int i = -2; i <= 2; i++) System.out.println("100/" + i + " = " + 100/i); } } Auf der Konsole 100/-2 = -50 100/-1 = -100 Exception in thread "main" java.lang.ArithmeticException: / by zero at Exception3.main(Exception3.java:5) 2 Grundelemente von Java 38 Um den Aufwand durch die Fehlerbehandlung nicht zu groß werden zu lassen, ist es für viele Laufzeitfehler zulässig, auf die explizite Weitergabe mit "throws" zu verzichten. Das Beispiel "Exception1" zeigt, dass arithmetische Ausnahmesituationen zu diesen Laufzeitfehlern gehören. Treten in dem eigenen Programm Fehler auf, die in dem von Java zu Verfügung gestellten Klassenbaum der Exceptions noch nicht vertreten sind, so muß man eine spezielle, eigene Ausnahmeklasse vereinbaren. Da Exceptions nichts anderes als Klassen sind, leitet man sich für seine Bedürfnisse einfach eine Klasse von der Klasse Exception oder einer Subklasse ab. Dadurch können die Exceptions unterschieden und entsprechend angepasste Fehlermeldungen ausgegeben werden. Das folgende Beispiel zeigt, wie man eine selbst definierte Exception generieren, auswerfen und wieder fangen kann. Beispiel, Ausnahme1 class MyException extends Exception { public MyException() { // Aufruf des Konstruktors der Klasse Exception. // Ihm wird ein String mit dem Fehlertext übergeben. super ("Fehler ist aufgetreten!"); } } public class MyClass { public static void main (String[] args) { // Dieser try-Block ist untypisch, da in ihm nur eine // Exception zu Demonstrationszwecken geworfen wird. try { MyException ex = new MyException(); throw ex; // Anweisungen unterhalb einer throw-Anweisung in einem // try-Block werden nie abgearbeitet. } catch (MyException e) { System.out.println (e.getMessage()); } } } Auf der Konsole Fehler ist aufgetreten! Zur Erstellung einer eigenen Exception wird in dem Beispiel die Klasse MyException von der Klasse Exception abgeleitet. Im parameterlosen Konstruktor der Klasse MyException wird mit super() der Konstruktor der Klasse Exception aufgerufen. An den Konstruktor von Exception wird ein String übergeben, der den 2 Grundelemente von Java 39 Fehlertext enthält. Dieser Fehlertext kann dann im Fehlerfall mit der Methode getMessage() ausgelesen werden. In obigem Beispiel wurde die Exception direkt im Hauptprogramm generiert und ausgeworfen mit dem Befehl throw ex; Die Exception kann aber auch von einer Methode erzeugt werden, die an irgendeiner Stelle im Hauptprogramm aufgerufen wird. Eine Exception, die eine Methode auslösen kann, muss dabei zwingend in der Deklaration der Methode mit Hilfe der throws-Klausel angegeben werden. Dadurch wird dem Aufrufer signalisiert, welche Ausnahmen von einer Methode ausgelöst bzw. weitergereicht werden. Der Aufrufer muss die geworfene Exception dann auffangen und sinnvoll behandeln. Im nachfolgenden Beispiel, das nur eine kleine Abwandlung gegennüber dem Beispiel "Ausnahme1" darstellt, wird dies verdeutlicht. Beispiel, Ausnahme2 class MyException extends Exception { public MyException() { // Aufruf des Konstruktors der Klasse Exception. // Ihm wird ein String mit dem Fehlertext übergeben. super ("Fehler ist aufgetreten!"); } } public class MyClass { static void method() throws MyException { MyException ex = new MyException(); throw ex; } public static void main (String[] args) { // Dieser try-Block ist untypisch, da in ihm nur eine // Exception zu Demonstrationszwecken geworfen wird. try { method(); // Anweisungen unterhalb einer throw-Anweisung in einem // try-Block werden nie abgearbeitet. } catch (MyException e) { System.out.println (e.getMessage()); } } } 2 Grundelemente von Java 2.6 40 Einfache Ein- und Ausgabe Beispiel, DisplayNumbers1 public class DisplayNumbers1 { public static void main(String[] args) { int iVar1 = 11, int iVar2 = 1/2; double dVar1 = 2.718, double dVar2 = 1.0/3.0; System.out.println("iVar1: System.out.println("iVar2: System.out.println("dVar1: System.out.println("dVar2: " " " " + + + + iVar1); iVar2); dVar1); dVar2); } } Auf der Konsole iVar1: iVar2: dVar1: dVar2: 11 0 2.718 0.3333333333333333 Im nächsten Beispiel wird die Ausgabe mit Hilfe der statischen Methode "printf()" formatiert: public PrintStream printf(String format, Object... args) Wobei der Formatstring den Aufbau hat: % [Argument-Index$][Flags][Width][.Precision]Conversion "Conversion" gibt den Datentyp an. Die wichtigsten Möglichkeiten sind: b c d o x X f e E g G t s - Boolescher Wert - Einzelnes Zeichen - Ganzzahl in Dezimaldarstellung - Ganzzahl in Oktaldarstellung - Ganzzahl in Hexadezimaldarstellung - Dito, mit großen Buchstaben - Flieskommazahl - Flieskommazahl mit Exponent - Dito, mit großem "E" - Flieskommazahl in gemischter Schreibweise - Dito, ggfs. mit großem "E" - Prefix für Datums-/Zeitangaben - Strings und andere Objekte Anders als in C/C++ müssen die Argumente strikt typkonform übergeben werden, was mitunter etwas lästig sein kann. Das Konvertierungszeichen "f" akzeptiert beispielsweise nur Fliesskommazahlen, nicht aber Ganzzahlen; bei "d" ist es genau umgekehrt. 2 Grundelemente von Java 41 Einen Sonderstatus haben "%" und "n", die für das Prozentzeichen bzw. die Zeilenschaltung stehen und kein zusätzliches Argument benötigen. Mit den "Flags" können weitere Ausgabeoptionen abgerufen werden. + 0 , ( - Linksbündige Ausgabe - Vorzeichen immer ausgegeben Zahlen werden mit Nullen aufgefüllt - Zahlen werden mit Tausenderpunkten ausgegeben - Negative Zahlen werden in Klammern eingeschlossen Sollen mit Hilfe des Konvertierungsprefixes "t" Datums-/Zeitwerte ausgegeben werden, so muß mit einem zweiten Buchstaben direkt dahinter angegeben werden, welcher Teil des Dateoder Calendar-Objektes ausgegeben werden soll. Einige der vielen Möglichkeiten sind: H M S d m Y F c - Stunde, zweistellig, im 24-Stunden-Format - Minute, zweistellig - Sekunde, zweistellig - Tag, zweistellig - Monat, zweistellig, 1..12 - Jahr, vierstellig - Datum, formatiert nach ISO 8601 (YYYY-MM-DD) - Kompletter Datums-/Zeitstring inkl. Zeitzone Beispiel, MyPrintf1 import java.util.*; public class MyPrintf1 { public static void main(String[] args) { //Boolesche Werte System.out.printf("%b %b %2$b %1$b%n", true, false); //Ganzzahlen System.out.printf("[%d]%n", -2517); System.out.printf("[%7d]%n", -2517); System.out.printf("[%-7d]%n", -2517); System.out.printf("[%(7d]%n", -2517); System.out.printf("[%07d]%n", -2517); System.out.printf("[%,7d]%n", -2517); System.out.printf("%1$d %<o %<x %<X%n", 127); //Fliesskommazahlen System.out.printf("%f%n", 0.000314); System.out.printf("%1$6.2f %1$6.2e %1$6.2E %1$6.2G%n", 3.141592); System.out.printf("%,8.2f%n", 31415.92); System.out.printf(Locale.ENGLISH, "%,8.2f%n", 31415.92); //Zeichen und Strings 42 2 Grundelemente von Java System.out.printf("%c%c%c\n", 97, 64, 98); System.out.printf("%s nein\n", "ja"); //Datum/Uhrzeit Calendar now = Calendar.getInstance(); System.out.printf( "%1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS%n", now ); System.out.printf("%tF%n", now); System.out.printf("%tc%n", now); } } Auf der Konsole: true false false true [-2517] [ -2517] [-2517 ] [ (2517)] [-002517] [ -2.517] 127 177 7f 7F 0,000314 3,14 3.14e+00 3.14E+00 3.1 31.415,92 31,415.92 a@b ja nein 26.02.2008 12:28:51 2008-02-26 Di Feb 26 12:28:51 CET 2008 Die Trennzeichen "," und "." entsprechen der deutschen Standardeinstellung (default locale). Aufgabe 2.5 Schreiben Sie ein Programm, das die folgende Tabelle der ASCII-Zeichen 32 bis 127 ausgibt. 32 38 44 50 56 62 68 74 80 86 92 98 104 110 116 122 0x20 0x26 0x2c 0x32 0x38 0x3e 0x44 0x4a 0x50 0x56 0x5c 0x62 0x68 0x6e 0x74 0x7a & , 2 8 > D J P V \ b h n t z 33 39 45 51 57 63 69 75 81 87 93 99 105 111 117 123 0x21 0x27 0x2d 0x33 0x39 0x3f 0x45 0x4b 0x51 0x57 0x5d 0x63 0x69 0x6f 0x75 0x7b ! ' 3 9 ? E K Q W ] c i o u { 34 40 46 52 58 64 70 76 82 88 94 100 106 112 118 124 0x22 0x28 0x2e 0x34 0x3a 0x40 0x46 0x4c 0x52 0x58 0x5e 0x64 0x6a 0x70 0x76 0x7c " ( . 4 : @ F L R X ^ d j p v | 35 41 47 53 59 65 71 77 83 89 95 101 107 113 119 125 0x23 0x29 0x2f 0x35 0x3b 0x41 0x47 0x4d 0x53 0x59 0x5f 0x65 0x6b 0x71 0x77 0x7d # ) / 5 ; A G M S Y _ e k q w } 36 42 48 54 60 66 72 78 84 90 96 102 108 114 120 126 0x24 0x2a 0x30 0x36 0x3c 0x42 0x48 0x4e 0x54 0x5a 0x60 0x66 0x6c 0x72 0x78 0x7e $ * 0 6 < B H N T Z ` f l r x ~ 37 43 49 55 61 67 73 79 85 91 97 103 109 115 121 127 0x25 0x2b 0x31 0x37 0x3d 0x43 0x49 0x4f 0x55 0x5b 0x61 0x67 0x6d 0x73 0x79 0x7f % + 1 7 = C I O U [ a g m s y 2 Grundelemente von Java 43 Beispiel, ReadString import java.io.*; public class ReadString { public static void main(String[] args) throws IOException { // BufferedReader reads text from a character-input stream, // buffering characters so as to provide for the efficient // reading of characters, arrays, and lines // An InputStreamReader is a bridge from byte streams to // character streams: It reads bytes and translates them into // characters according to a specified character encoding // System.in: the "standard" input stream BufferedReader din = new BufferedReader( new InputStreamReader(System.in)); System.out.print("String: "); // Read a line of text String str = din.readLine(); System.out.println(str); } } Auf der Konsole String: Ich bin ein String mit Leerzeichen Ich bin ein String mit Leerzeichen Wenn die Angabe von "throws IOException" fehlt, bricht der Compiler mit einer Fehlermeldung ab (vgl. Abschnitt 2.4). Beispiel, ReadInt import java.io.*; public class ReadInt { public static void main(String[] args) throws IOException { BufferedReader din = new BufferedReader( new InputStreamReader(System.in)); System.out.print("anInt: "); // "parseInt" parses the string argument as a signed // decimal integer int anInt = Integer.parseInt(din.readLine()); System.out.println(anInt); } } 2 Grundelemente von Java 44 Auf der Konsole anInt: -123 -123 Aufgabe 2.6 Schreiben Sie ein Programm, das ein Rechteck auf der Konsole ausgibt. ****** * * * * ****** Breite (width) und Höhe (height) sollen eingelesen werden. Weisen Sie Eingaben zurück, wenn nicht 1 ≤ Breite ≤ 79 und 1 ≤ Höhe ≤ 20 gilt. Beachten Sie auch den Sonderfall Breite = Höhe = 1. Im folgenden Beispiel wird eine Kopie einer Textdatei angelegt. Die Namen der beiden Dateien sind dabei in der Kommandozeile zu übergeben. Beispiel, CopyTextFile import java.io.*; public class CopyTextFile { public static void main(String[] args) throws IOException { // Line separator (Windows "\r\n", Unix "\n", Mac "\r") final String L_SEP = System.getProperty("line.separator"); BufferedReader in; BufferedWriter out; String txt; if(args.length < 2) { System.out.println("Usage: CopyTextFile <inFile> <outFile>"); return; } try { in = new BufferedReader(new FileReader(args[0])); } catch(FileNotFoundException e) { System.out.println("Could not find file " + args[0]); return; } try { out = new BufferedWriter(new FileWriter(args[1])); } 2 Grundelemente von Java 45 catch(FileNotFoundException e) { System.out.println("Could not create file " + args[1]); return; } while((txt = in.readLine()) != null) out.write(txt + L_SEP); // Copy text file in.close(); out.close(); } } 2.7 Parameterübergabe bei Methoden In Java werden Methodenparameter generell per Wert übergeben. Das heißt, ein formaler Parameter erhält beim Aufruf der Methode eine Kopie des entsprechenden aktuellen Parameters (Übergabe per Wert, call by value). Dies hat aber völlig unterschiedliche Auswirkungen, je nach dem ob der Übergabeparameter von einfachem Datentyp ist oder von Referenztyp, also eine Objekt repräsentiert. Im folgenden Beispiel betrachten wir zunächst den Fall, dass der Übergabeparameter von einfachem Datentyp ist. Beispiel, Parameter1 /* * */ Beispiel: Parameter1 public class Parameter1 { public static void method1 (int par) { par = 2; } public static void main(String[] args) { int value = 1; System.out.println("Vor dem Methodenaufruf"); System.out.println("value = " + value); method1(value); System.out.println("Nach dem Methodenaufruf"); System.out.println("value = " + value); } } Auf der Konsole Vor dem Methodenaufruf value = 1 Nach dem Methodenaufruf value = 1 2 Grundelemente von Java 46 Die Änderungen, welche die Methode methode1() an der Variablen par vornimmt, haben keine Auswirkung auf den aktuellen Übergabeparameter value. Dies entspricht genau einem call by value. Im nächsten Beispiel ist der Übergabeparameter vom Referenztyp. Auch hier geschieht die Übergabe des Parameters per call by value, diesmal aber wird eine Referenz kopiert. Damit referenziert der formale Parameter par dasselbe Objekt wie der aktuelle Parameter ref. Beide greifen also auf dasselbe Objekt zu. Damit kann das Originalobjekt aber in der Methode verändert werden. Dieses entspricht vom Verhalten her einem call by reference. Beispiel, Parameter2 /* * */ Beispiel: Parameter2 class RefTyp { int x; } public class Parameter2 { public static void method2 (RefTyp par) { par.x = 2; } public static void main(String[] args) { RefTyp ref = new RefTyp(); ref.x = 1; System.out.println("Vor dem Methodenaufruf"); System.out.println("ref.x = " + ref.x); method2(ref); System.out.println("Nach dem Methodenaufruf"); System.out.println("ref.x = " + ref.x); } } Auf der Konsole Vor dem Methodenaufruf ref.x = 1 Nach dem Methodenaufruf ref.x = 2 2.8 Zuweisung von Objekten Beim Zuweisen von Objekten muss man Vorsicht walten lassen. Im folgenden Beispiel werden mit der Zuweisung m2 = m1 lediglich die Referenzen kopiert. Man erhält dadurch zwei Referenzen, die auf das gleiche Objekt verweisen. Eine Änderung an der Kopie m2 bewirkt dann auch eine Änderung am Original m1, was in der Regel unerwünscht ist. 2 Grundelemente von Java Beispiel, MyCopy1 /* * */ Beispiel: MyCopy1 class Mitarbeiter { private String name; private int gehalt; public Mitarbeiter() { } public Mitarbeiter(String name, int gehalt) { this.name = name; this.gehalt = gehalt; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getGehalt() { return gehalt; } public void setGehalt(int gehalt) { this.gehalt = gehalt; } } public class MyCopy1 { public static void main(String[] args) { Mitarbeiter m1 = new Mitarbeiter("Mueller", 2000); Mitarbeiter m2; m2 = m1; // flache Kopie (shallow copy)!!! m2.setName("Schulze"); m2.setGehalt(1500); System.out.println("m2.name = " + m2.getName() + ", m2.gehalt = " + m2.getGehalt()); System.out.println("m1.name = " + m1.getName() + ", m1.gehalt = " + m1.getGehalt()); } } 47 2 Grundelemente von Java 48 Auf der Konsole m2.name = Schulze, m2.gehalt = 1500 m1.name = Schulze, m1.gehalt = 1500 Um wirklich eine echte Kopie der Originalobjektes zu erhalten, wird im folgenden Beispiel der Klasse Mitarbeiter die Methode "copy()" hinzugefügt, die ein neues Objekt vom Typ Mitarbeiter erzeugt und alle Daten des Originalobjekts kopiert. Hier werden also alle Daten des Originalobjektes kopiert und nicht nur die Objektvariable, die lediglich eine Referenz auf das Objekt enthält. Beispiel, MyCopy2 /* * */ Beispiel: MyCopy2 class Mitarbeiter { private String name; private int gehalt; public Mitarbeiter() { } public Mitarbeiter(String name, int gehalt) { this.name = name; this.gehalt = gehalt; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getGehalt() { return gehalt; } public void setGehalt(int gehalt) { this.gehalt = gehalt; } public Mitarbeiter copy() { Mitarbeiter c = new Mitarbeiter(); c.name = this.name; c.gehalt = this.gehalt; return c; } } 2 Grundelemente von Java 49 public class MyCopy2 { public static void main(String[] args) { Mitarbeiter m1 = new Mitarbeiter("Mueller", 2000); Mitarbeiter m2; m2 = m1.copy(); // tiefe Kopie (deep copy)!!! m2.setName("Schulze"); m2.setGehalt(1500); System.out.println("m2.name = " + m2.getName() + ", m2.gehalt = " + m2.getGehalt()); System.out.println("m1.name = " + m1.getName() + ", m1.gehalt = " + m1.getGehalt()); } } Auf der Konsole m2.name = Schulze, m2.gehalt = 1500 m1.name = Mueller, m1.gehalt = 2000 Wenn in das zu kopierende Objekt wiederum Referenzvariablen eingebettet sind, unterscheidet man erneut zwischen flacher and tiefer Kopie. − Flache Kopie (shallow copy): Man kopiert die eingebetteten Referenzvariablen. Anschließend verweisen dann also die eingebetteten Referenzvariablen von Original und Kopie auf ein- und dieselben Daten. − Tiefe Kopie (deep copy): Man kopiert auch die Daten, auf die die eingebetteten Referenzvariablen verweisen. In einem späteren Kapitel werden wir sehen, dass man für das kopieren von Objekten in der Regel die Methode clone() verwendet, die in der Basisklasse Object, von der alle Klassen abgeleitet sind, vordefiniert ist. Man muss dazu das Interface Cloneable implementieren und die Methode clone() in der eigenen Klasse überschreiben. 2.9 Vergleich von Objekten Auch beim Vergleichen von Objekten, also Referenzvariablen, muss man ähnlich Vorsicht walten lassen wie bei der Zuweisung von Objekten. Bei Referenztypen wird mit dem Vergleich a == b getestet, ob die Referenzen gleich sind, das heißt, es wird getestet, ob die Referenzvariablen auf dieselbe Instanz verweisen. Es wird aber nicht geprüft, ob zwei unterschiedliche Instanzen den gleichen Inhalt haben. Will man diesen Test auf inhaltliche Gleichheit bei unterschiedlichen Instanzen haben, so muss in der eigenen Klasse die Methode equals(), die von der Klasse Object geerbt wird, überschrieben werden. Wir hatten dieses Problem bei Zeichenketten (Strings) schon einmal angesprochen. In der Klasse String gibt es bereits eine geeignete Methode equals(), die zwei verschiedene Strings auf Gleichheit prüft. Im folgenden Beispiel wird das noch einmal an Hand einer eigenen Klasse dargestellt. Dort ist die Klasse Book entworfen, um z.B. Bücher einer Bibliothek zu erfassen. Der Klasse Book wurde eine geeignete Methode equals() hinzugefügt, die zwei verschiedene Objekte der Klasse Book als gleich erkennt, wenn Titel und Autor identisch sind. 2 Grundelemente von Java /* * Beispiel: Vergleich1 */ class Book { private String title; private String author; public Book(String title, String author) { this.title = title; this.author = author; } public boolean equals(Object otherObject) { // true, wenn die Objekte identisch sind if (this == otherObject) return true; // false, wenn otherObject == null if (otherObject == null) return false; // false, wenn die Klassen nicht gleich sind if (getClass() != otherObject.getClass()) return false; Book other = (Book) otherObject; // true, wenn title und author gleich return title.equals(other.title) && author.equals(other.author); } } public class Vergleich1 { public static void main(String[] args) { Book b1 = new Book("Java 6", "Samaschke"); Book b2 = new Book("Java 6", "Samaschke"); System.out.println(b1 == b2); System.out.println(b2.equals(b1)); } } Auf der Konsole false true 50 2 Grundelemente von Java 51 2.10 Übungsaufgaben Übungsaufgabe 2.1 Schreiben Sie ein Programm, das eine Zeichenkette (String) einliest und diese Zeichenkette dann in umgekehrter Reihenfolge wieder ausgibt. Beispiel String: Heute ist Montag gatnoM tsi etueH Hinweis: Im Anhang ist eine Kurzbeschreibung der Methoden der Klasse "java.lang.String" zu finden. Übungsaufgabe 2.2 Schreiben Sie ein Programm zur grafischen Ausgabe der folgenden Funktion: y = a+b ⋅ x+c ⋅ x 2 +d ⋅ x 3 +e ⋅ x 4 Das untenstehende Beispiel zeigt, wie die Parameter eingegeben werden sollen und wie die Grafik auszugeben ist. Die Grafik besteht aus 17 Zeilen und 71 Spalten und soll so beschriftet werden, wie es das Beispiel zeigt. Auf der Konsole Geben Sie die Parameter ein: a: -2 b: -2 c: 0 d: 1 e: .1 4| y | * | * | | * | * | * | * x ----------------------------------------------------------------*------2 0| * 2 *************** | ** *** ***** | * *** ***** ** ** | ***** *** * | ************ ** | * -4| 2 Grundelemente von Java 52 Übungsaufgabe 2.3 Vervollständigen Sie die Klasse "IntStack" für Daten vom Typ "int" so, dass sich "main" ausführen lässt und die gegebene Konsolenausgabe liefert. class IntStack { int stack[], stackSize, index; ... ... } public class Test { public static void main(String[] args) { IntStack stack1 = new IntStack(2); stack1.push(2); stack1.push(5); stack1.push(1); // Stack ist voll while(!stack1.empty()) System.out.println("Stack 1: " + stack1.pop()); stack1.pop(); // Stack ist leer IntStack stack2 = new IntStack(3); stack2.push(-3); stack2.push(44); stack2.push(-9); while(!stack2.empty()) System.out.println("Stack 2: " + stack2.pop()); } } Auf der Konsole Stack Stack Stack Stack Stack Stack Stack ist voll! 1: 5 1: 2 ist leer! 2: -9 2: 44 2: -3 3 OOP II : Vererbung Aus Abschnitt 1.2 sind die folgenden Begriffe schon bekannt: − Klassen und Objekte; − Instanz- und Klassenvariablen (static), sowie Konstanten (static final); − Konstruktor, Standardkonstruktor, Methoden und Überladen von Methoden; − Instanz- und Klassenmethoden (static), − die Referenz "this". 3.1 Das Konzept der Vererbung Eine Klasse kann ihre Daten und Methoden an eine andere Klasse "vererben" (Bild 3.1) Oberklasse (superclass) Transportmittel "ist ein"-Beziehung Landtransportmittel Abgeleitete Klasse(subclass) Bild 3.1 Oberklasse und abgeleitete Klasse Die vererbende Klasse wird Oberklasse (superclass) genannt, die erbende Klasse bezeichnet man als abgeleitete Klasse (subclass). Es ist üblich, dass der Vererbung eine "ist ein"Beziehung zugrunde liegt: ein Landtransportmittel ist ein Transportmittel. Wenn man den Pfeil in diesem Sinn liest, ist seine Richtung sinnvoll. Die Vererbung ist nicht auf zwei Klassen beschränkt. Bild 3.2 zeigt ein umfangreicheres Beispiel. − Die Klasse "Transportmittel" enthält Daten und Methoden, die allen Transportmitteln gemeinsam sind. − Von dieser Klasse wird "Landtransportmittel" abgeleitet, und es werden die für diese Teilmenge der Transportmittel notwendigen Daten und Methoden hinzugefügt. − Ein "Auto" ist ein "Landtransportmittel". Die Klasse "Landtransportmittel" stellt also aus Sicht der Klasse "Auto" die Oberklasse dar. − Die Klasse "Amphibienfahrzeug" erbt Daten und Methoden von "Auto" und von "Wassertransportmittel". Ein Amphibienfahrzeug ist ein Auto, und ein Amphibienfahrzeug ist ein Wassertransportmittel. In Java ist Mehrfachvererbung nicht zulässig. Eine Oberklasse stellt eine Generalisierung der von ihr abgeleiteten Klassen dar. "Landtransportmittel" enthält z.B. für "Rikscha", "Fahrrad" und "Auto" gemeinsame Daten und Methoden. Jede abgeleitete Klasse fügt dann für sie typische Dinge hinzu oder legt das geerbte Verhalten neu fest. Eine abgeleitete Klasse ist eine Spezialisierung ihrer Oberklasse. "Rikscha", "Fahrrad" und "Auto" sind z.B. spezielle "Landfahrzeuge". In der jeweiligen Klasse brauchen nur noch die Abweichungen beschrieben zu werden, alles andere kann man wiederverwenden. 54 3 OOP II : Vererbung Transportmittel Klasse breite hoehe Daten Methoden bewegen() Rikscha ... ... Landtransportmittel Wassertransportmittel radzahl fahren() schieben() bruttoregistertonnen anlegen() ablegen() Fahrrad ... ... Auto spritverbrauch starten() fahren() tanken() Amphibienfahrzeug hersteller schottenDichtmachen() Bild 3.2 Beispiel zur Vererbung von Daten und Methoden Im Programmcode kennzeichnet man die Vererbung in der Kopfzeile der abgeleiteten Klasse. class Transportmittel { double breite, hoehe; void bewegen() {...} } class Landtransportmittel extends Transportmittel { int radzahl; void fahren() {...} void schieben() {...} } class Auto extends Landtransportmittel { double spritverbrauch; void starten() {...} void fahren() {...} void schieben() {...} } ... ... ... 3 OOP II : Vererbung 55 Im folgenden Programmausschnitt wird als Beispiel ein Objekt "auto" vom Typ "Auto" vereinbart. public static void main(String[] args) { Auto auto = new Auto(); ... } Bild 3.3 veranschaulicht, dass "auto" je ein Objekt der Klassen "Landtransportmittel" und "Transportmittel" enthält. In ein Objekt einer abgeleiteten Klasse ist also je ein anonymes Objekt seiner Oberklasse(n) eingebettet. Zu "auto" gehören folglich die Daten "breite", "hoehe", "radzahl" und "spritverbrauch", sowie die Methoden "bewegen()", "fahren()", "schieben()", "starten()", "fahren()" und "tanken()". Auto Landtransportmittel Transportmittel breite hoehe bewegen() radzahl fahren() schieben() spritverbrauch starten() fahren() tanken() Bild 3.3 Ein Objekt der Klasse "Auto" enthält je ein Objekt der Klassen "Landtransportmittel" und "Transportmittel" Aufgabe 3.1 Warum lässt der Compiler die Zuweisung 1 zu, meldet dann aber für die Zuweisung 2 einen Fehler? ... ... ... (Programmcode zu Bild 3.3) public class Test { public static void main(String[] args) { Landtransportmittel ltm = new Landtransportmittel(); Auto auto = new Auto(); ltm = auto; auto = ltm; } } // Zuweisung 1 // Zuweisung 2 3 OOP II : Vererbung 56 Ein Beispiel Ein Unternehmen hat unterschiedliche Mitarbeiter: Arbeiter, Vertreter, Manager, usw. Die Mitarbeiter unterscheiden sich z.B. in der Art, in der ihr Monatsgehalt berechnet wird. • Arbeiter: Stundenlohn * Stunden, • Vertreter: Stundenlohn * Stunden + Provision * Anzahl, • Manager: Festes Gehalt, • usw. Für die Mitarbeiter sind Namen und gehaltsrelevante Daten zu speichern und die Namen und das zugehörige Gehalt sind auszugeben. In einem ersten Programm wird für jeden Mitarbeitertyp eine eigene Klasse mit allen benötigten Daten vereinbart. Wir nutzen hier nicht das Konzept der Vererbung. Beispiel, Employee1 class Worker //Arbeiter { String name; // Name double hourlyWage; // Stundenlohn double hours; // Stunden Worker(String n, double hW, double h) { name = new String(n); hourlyWage = hW; hours = h; } void display() { System.out.println(name + " " + hourlyWage * hours + " EUR"); } } class SalesAgent //Vertreter { String name; // Name double hourlyWage; // Stundenlohn double hours; // Stunden double commission; // Provision double count; // Anzahl SalesAgent(String n, double hW, double h, double c, double cn) { name = new String(n); hourlyWage = hW; hours = h; commission = c; count = cn; } void display() { System.out.println(name + " " + (hourlyWage * hours + commission * count) 3 OOP II : Vererbung 57 + " EUR"); } } class Manager // Manager { String name; // Name double salary; // Gehalt; Manager(String n, double s) { name = new String(n); salary = s; } void display() { System.out.println(name + " + salary + " EUR"); } " } public class Employee1 { public static void main(String[] args) { Worker w = new Worker("Meyer, Klaus", 15.82, 151.00); SalesAgent s = new SalesAgent("Hamer, Peter", 8.80, 150.0, 60.28, 22.0); Manager m = new Manager("Kramer, Hans", 3501.27); w.display(); s.display(); m.display(); } } Auf der Konsole Meyer, Klaus Hamer, Peter Kramer, Hans 2388.82 EUR 2646.16 EUR 3501.27 EUR Es liegt hier nah, zur Lösung eine Klassenhierarchie einzusetzen. Ein entscheidender Vorteil liegt darin, dass bestehender Code wieder verwendet werden kann. Bereits bestehende Konstruktoren und Methoden der Superklassen können wieder verwendet werden. Dort durchgeführte Sicherheitsprüfungen und Plausibilitätschecks werden übernommen, und so können robuste Programme geschrieben werden. Das Beispiel "Employee2" zeigt die Vorgehensweise. Es wir dieselbe Aufgabenstellung programmiert wie in Beispiel "Employee1" aber diesmal unter Verwendung von Vererbung. Von der Basisklasse Employee wird die Klasse Worker abgeleitet und von dieser wiederum die Klasse SalesAgent. Von der Klasse Employee wird außerdem die Klasse Manager abgeleitet (Bild 3.4). Mit "super(..)" kann in einer Klasse jeweils der Konstruktor der Oberklasse, der "Vatersklasse", aufgerufen werden. Dadurch ergibt sich in Klassenhierarchien eine Verkettung der Konstruktoren. Methoden einer Superklasse, wie im nachfolgenden Beispiel die Methode display() der Klasse Worker, können in einer Subklasse überschrieben werden. Beim Überschreiben einer 3 OOP II : Vererbung 58 Methode müssen aber die Signatur und der Rückgabewert der überschreibenden Methode identische sein mit der Signatur und dem Rückgabewert der überschriebenen Methode. Employee Worker Manager SalesAgent Bild 3.4 Klassenhierarchie Beispiel, Employee2 class Employee { String name; // Name Employee(String n) { name = new String(n); } void displayName() { System.out.print(name + " " ); } } class Worker extends Employee { double hourlyWage; // Stundenlohn double hours; // Stunden Worker(String n, double hW, double h) { super(n); hourlyWage = hW; hours = h; } double workerSalary() { return hourlyWage * hours; } void display() { displayName(); System.out.println(workerSalary() + " EUR"); } } 3 OOP II : Vererbung class SalesAgent extends Worker { double commission; // Provision double count; // Anzahl SalesAgent(String n, double hW, double h, double c, double cn) { super(n, hW, h); commission = c; count = cn; } void display() { displayName(); System.out.println((workerSalary() + commission*count) + " EUR"); } } class Manager extends Employee { double salary; // Gehalt Manager(String n, double s) { super(n); salary = s; } void display() { displayName(); System.out.println(salary + " EUR"); } } public class Employee2 { public static void main(String[] args) { Worker w = new Worker("Meyer, Klaus", 15.82, 151.00); SalesAgent s = new SalesAgent("Hamer, Peter", 8.80, 150.0, 60.28, 22.0); Manager m = new Manager("Kramer, Hans", 3501.27); w.display(); s.display(); m.display(); } } Auf der Konsole Meyer, Klaus Hamer, Peter Kramer, Hans 2388.82 EUR 2646.16 EUR 3501.27 EUR 59 3 OOP II : Vererbung 60 Mit "super(..)" wird jeweils der Konstruktor der Oberklasse, des "Vatersklasse", aufgerufen. Unmittelbare Zugriffe auf den Konstruktor der "Großvaterklasse" sind in Java nicht möglich In "SalesAgent" ist also z.B. kein Zugriff auf den Konstruktor von "Employee" durchführbar. (Anweisungen wie "super.super(..)" sind nicht zulässig.) Hinweis: Wenn ein Konstruktor die Anweisung "super(...)" enthält, muss sie die erste Anweisung dieses Konstruktors sein! Aufgabe 3.2 Im Beispiel "Empoyee2" wird in der Klasse "Employee" die Methode "displayName()" in "display()" umbenannt. class Employee { String name; Employee(String n) { name = new String(n); } void display() { System.out.print(name + " " ); } } Erläutern Sie dadurch verursachte Programmänderungen und die dadurch entstehenden Probleme. Im nachfolgenden Programmbeispiel "Employee3" wird der Konstruktor der Oberklasse "Employee" nicht mehr explizit mit "super(...)" aufgerufen. Die Konsolenausgabe zeigt, dass der Compiler dann selbständig einen Aufruf des Standardkonstruktors der Oberklasse generiert. Beispiel, Employee3 class Employee { String name; Employee() { System.out.println("--- Employee-Konstruktor ---" ); } void displayName() { System.out.print(name + " " ); } } class Worker extends Employee { double hourlyWage; double hours; Worker(String n, double hW, double h) { name = new String(n); hourlyWage = hW; hours = h; } 3 OOP II : Vererbung 61 double workerSalary() { return hourlyWage * hours; } void display() { displayName(); System.out.println(workerSalary() + " EUR"); } } public class Employee3 { public static void main(String[] args) { Worker w = new Worker("Meyer, Klaus", 15.82, 151.00); w.display(); } } Auf der Konsole --- Employee-Konstruktor --Meyer, Klaus 2388.82 EUR Hinweis: Wenn der Compiler im Konstruktor einer abgeleiteten Klasse als erste Anweisung kein "super(..)" findet, erzeugt er selbständig einen Aufruf des Standardkonstruktors der Oberklasse2. Aufgabe 3.3 Im Beispiel "Empoyee3" wird in der Klasse "Employee" der Standardkonstruktor auskommentiert. class Employee { ... // Employee() { System.out.println("Employee-Konstruktor" ); } ... Kommt es dann zu einer Fehlermeldung des Compilers? Im nachfolgenden Beispiel werden noch einmal das Überschreiben von Methoden und das Verdecken von Membervariablen in einer Vererbungshierarchie dargestellt. Die überschriebene Instanzmethode methode() einer Vaterklasse kann mit super.methode() innerhalb von Instanzmethoden der abgeleiteten Sohnklasse angesprochen werden. Der Aufruf super.methode() wird dabei vom Compiler in ((Vaterklasse) this).methode() umgesetzt. Vom Verdecken einer Membervariable spricht man, wenn es in der Sohnklasse eine Membervariable mit gleichem Namen gibt, wie in der Vaterklasse. Der Zugriff auf eine von der Vaterklasse ererbte, verdeckte Instanzvariable x erfolgt mit super.x. Der Zugriff super.x wird dabei vom Compiler in den Zugriff ((Vaterklasse) this).x umgesetzt. 2 Es gibt eine Ausnahme zu dieser Regel, wenn ein Konstruktor einer Klasse einen anderen Konstruktor derselben Klasse aufruft ([2], [3]). 3 OOP II : Vererbung 62 Beispiel, Ueberschreiben //------------------------------------------------------------class A { char c = 'A'; void f1() {System.out.println("f1 aus " + c);} void f2() {System.out.println("f2 aus " + c);} void f3() {System.out.println("f3 aus " + c);} } //------------------------------------------------------------class B extends A { char c = 'B'; void f1() {System.out.println("f1 aus " void f2() {System.out.println("f2 aus " " + c + extends " + super.c);} " + c + extends " + super.c);} } //------------------------------------------------------------class C extends B { char c = 'C'; void f1() { System.out.println("f1 aus " + c + " extends " + super.c); super.f1(); super.f2(); super.f3(); } } //------------------------------------------------------------public class SuperDemo { public static void main(String[] args) { A a = new A(); a.f1(); a.f2(); a.f3(); B b = new B(); b.f1(); b.f2(); b.f3(); C c = new C(); c.f1(); c.f2(); c.f3(); } } 3 OOP II : Vererbung 63 Auf der Konsole f1 f2 f3 f1 f2 f3 f1 f1 f2 f3 f2 f3 3.2 aus aus aus aus aus aus aus aus aus aus aus aus A A A B B A C B B A B A extends A extends A extends B extends A extends A extends A Die Basisklasse Object In Java besitzt jede selbstdefinierte Klasse eine Superklasse. Wenn man diese Superklasse nicht mit "extends" angibt, dann wird automatisch die Klasse "Object" zur Superklasse (Bild 3.5). Object Employee Worker Manager SalesAgent Bild 3.5 Klassenhierarchie zum Beispiel "Employee2" mit Superklasse Object Die Klasse "Object" − ist die einzige Klasse, die keine Superklasse besitzt und − ihre Methoden können in jedem Javaobjekt aufgerufen werden. Im folgenden Beispiel werden die drei Methoden "toString", "hashCode" und "equals" eingesetzt, die von "Object" geerbt worden sind. Eine weitere Methode ("clone") folgt in einem späteren Abschnitt. Beispiel, Object1 package consolepack; class Employee { // extends Object String name; Employee(String n) { name = new String(n); } } 3 OOP II : Vererbung 64 public class Object1 { public static void main(String[] args) { Employee e1 = new Employee("Meyer, Klaus"); Employee e2 = new Employee("Meyer, Klaus"); System.out.println(e1.toString()); System.out.println(e2); System.out.println(Integer.toHexString(e1.hashCode())); System.out.println(Integer.toHexString(e2.hashCode())); System.out.println(e1.equals(e2)); System.out.println(e1 == e2); } } Auf der Konsole consolepack.Employee@1cde100 consolepack.Employee@16f0472 1cde100 16f0472 false false Die Methode "hashCode" berechnet einen numerischen Wert, der als Schlüssel zur Speicherung eines Objekts in einer Hash Tabelle eingesetzt werden kann. Weitergehende Informationen sind z.B. in [2] zu finden. Im folgenden Beispiel werden die geerbten Methoden "toString" und "equals" durch eigene Methoden ersetzt ("überschrieben"). Beispiel, Object2 class Employee { // extends Object String name; Employee(String n) { name = new String(n); } public String toString() { return name; } public boolean equals(Object obj) { return name.equals(((Employee) obj).name); } } public class Object2 { public static void main(String[] args) { Employee e1 = new Employee("Meyer, Klaus"); Employee e2 = new Employee("Meyer, Klaus"); System.out.println(e1.toString()); System.out.println(e2); System.out.println(e1.equals(e2)); System.out.println(e1 == e2); } } 3 OOP II : Vererbung 65 Auf der Konsole Meyer, Klaus Meyer, Klaus true false Ein anderes Beispiel für das Überschreiben der equals()-Methode hatten wir bereits in dem Kapitel 2.9 gesehen. Dort war die equals()-Methode in geeigneter Weise für die Klasse Book überschrieben worden. In der Klasse String ist bereits eine geeignete equals()-Methode implementiert, die zwei Zeichenketten Buchstaben für Buchstaben auf inhaltliche Gleichheit prüft. 3.3 Typkonvertierung in Vererbungshierarchien Wir betrachten die folgende Klassenhierarchie bestehend aus der Vaterklasse Vater und der davon abgeleiteten Sohnklasse Sohn. class Vater { int valueVater; public void methodVater() {...} } class Sohn extends Vater { int valueSohn; public void methodSohn() {...} } Wir betrachten eine Zuweisung, bei der eine Referenzvariable vom Typ der Sohnklasse einer Referenzvariablen vom Typ der Vaterklasse zugewiesen wird. Sohn refSohn = new Sohn(); Vater refVater = refSohn; Die Referenzvariable refVater vom Typ Vater zeigt damit auf ein Objekt der Klasse Sohn. Dies ist zulässig, weil ein Sohnobjekt durch den Vererbungsmechanismus auch alle Eigenschaften eines Vaterobjektes besitzt. Bei der Zuweisung findet eine implizite Typkonvertierung auf den Typ Vater statt mittels des cast-Operators (Vater) angewandt auf die Referenz refSohn. Allerdings sieht die Referenz refVater vom Typ Vater nur die Vateranteile des Sohnobjekts, und die Anweisung refVater.methodeSohn() führt natürlich zu einem Compilerfehler, da zur Compile-Zeit unter Umständen ja noch gar nicht feststeht, ob die Referenz refVater auf ein Sohn- oder ein Vaterobjekt zeigt. Eine solche implizite Typkonvertierung in eine Superklasse wird auch als Up-Cast bezeichnet. Die umgekehrte Zuweisung, Vater refVater = new Vater(); Sohn refSohn = refVater; 3 OOP II : Vererbung 66 bei der eine Referenzvariable vom Typ der Vaterklasse einer Referenzvariablen vom Typ der Sohnklasse zugewiesen wird, ist unzulässig und führt zu einem Compilerfehler. Dies ist einleuchtend, weil ein Vaterobjekt nicht alle Eigenschaften eines Sohnobjektes besitzt. Man kann diese Zuweisung erzwingen durch eine explizite Typkonvertierung der Form Sohn refSohn = (Sohn) refVater; Eine solche explizite Typkonvertierung in eine Subklasse wird auch als Down-Cast bezeichnet. Man muss aber beachten, dass die obige Zuweisung nur sinnvoll ist, wenn man vorher sichergestellt hat, dass die Referenzvariable refVater tatsächlich auf ein Sohnobjekt zeigt. Dies kann mit Hilfe des Operators instanceof geschehen, der nachfolgend noch genauer beschrieben wird. Zeigt die Referenz refVater auf ein Objekt der Vaterklasse, so führt obige Typkonvertierung zum Programmabsturz, und zwar wegen einer ClassCastException. Mit dem Operator instanceof kann man ermitteln, zu welcher Klasse ein Objekt gehört. Der Ausdruck "x instanceof y" liefert genau dann "true", wenn "x" eine Instanz der Klasse "y" oder einer von ihr abgeleiteten Klasse ist. Dies kann man nutzen, um obige Zuweisung sicher zu programmieren. Mit dem instanceof-Operator lässt sich prüfen, ob das referenzierte Objekt tatsächlich von dem angenommenen Typ ist. Mit diesem Ergebnis kann die Referenz dann in den entsprechenden Typ down-gecastet werden. if (refVater instanceof Sohn) Sohn refSohn = (Sohn) refVater; 3.4 Verwendung des Operators instanceof Das Up-casten und das sichere Down-casten mit dem Operator instanceof wird in der Praxis häufig genutzt, wenn in einer Collection oder auch einem Array Objekte aus einer Vererbungshierarchie abgespeichert werden sollen. Die Elemente des Arrays sind dann Referenzen vom Typ der Basisklasse. Diesen Referenzen können auch Instanzen vom Typ der abgeleiteten Klassen zugewiesen werden. Auf diese Weise können Objekte der gesamten Vererbungshierarchie in dem Array abgelegt werden. Mit dem instanceof-Operator kann man nun feststellen, von welchem Klassentyp ein bestimmtes Array Element zur Laufzeit ist, und dann die entsprechenden Methoden aufrufen. Dazu das folgende Beispiel. Beispiel, OpInstanceof class Shape { int x, y; Shape(int x, int y) {this.x = x; this.y = y;} } class Circle extends Shape { int r; Circle(int x, int y, int r) { super(x, y); this.r = r; } } class Text extends Shape { String s; Text(int x, int y, String s) { super(x, y); this.s = s; } } 3 OOP II : Vererbung 67 class BoldText extends Text { BoldText(int x, int y, String s) { super(x, y, s); } } //--------------------------------------------------------public class OpInstanceof { public static void main(String[] args) { Shape shape[] = new Shape[4]; shape[0] shape[1] shape[2] shape[3] = = = = new new new new Shape(10, 20); Circle(10, 20, 200); Text(10, 20, "Monday to Friday"); BoldText(10, 20, "Sunday"); for(int i = 0; i < shape.length; i++) System.out.println(shape[i] instanceof Text); for(int i = 0; i < shape.length; i++) { if(shape[i] instanceof BoldText) System.out.println("Bold: " + ((BoldText) shape[i]).s); else if(shape[i] instanceof Text) System.out.println(((Text) shape[i]).s); } } } Auf der Konsole false false true true Monday to Friday Bold: Sunday 3.5 Pakete und Sichtbarkeit Pakete Pakete (Packages) dienen dazu, die Software eines größeren Projektes in inhaltlich zusammenhängende Bereiche einzuteilen. Jedes Paket entspricht dabei einer KlassenBibliothek. Java Klassen, die zu einem Paket gehören, lassen sich dann mit der schon bekannten "import"-Anweisung in ein Programm einbinden. Pakete können aus verschiedenen Quellcode-Dateien bestehen. Jede Quellcode-Datei, die zu einem Paket gehört, muss mit derselben Paketdeklaration beginnen. Alle Programmeinheiten einer QuellcodeDatei gehören auf jeden Fall zum gleichen Paket. Im folgenden Beispiel "PackageDemo" wird die folgende Verzeichnisstruktur benutzt: <Pfad> <Pfad>\demo <Pfad>\demo\tools // Pfad für PackageDemo // Subdirectory mit Klassen A und B // Subdirectory mit Klasse C 3 OOP II : Vererbung Beispiel, PackageDemo (nach [1]) In Datei "<Pfad>\PackageDemo.java" import demo.*; import demo.tools.*; public class PackageDemo { public static void main (String args[]) { A a = new A(); B b = new B(); C c = new C(); a.Hello(); b.Hello(); c.Hello(); } } In Datei "<Pfad>\demo\A.java" package demo; public class A { public void Hello(){ System.out.println("--- A ---"); } } In Datei "<Pfad>\demo\B.java" package demo; public class B { public void Hello(){ System.out.println("--- B ---"); } } In Datei "<Pfad>\demo\tools\C.java" package demo.tools; public class C { public void Hello(){ System.out.println("--- C ---"); } } Auf der Konsole --- A ----- B ----- C --- 68 69 3 OOP II : Vererbung Beim Übersetzen von "PackageDemo" prüft der Compiler selbsttätig, ob die ".class"-Dateien in den Packages "demo" und "demo.tools" noch aktuell sind. Andernfalls übersetzt er die zugehörigen Quelldateien erneut. Es wird übersetzt, wenn zu einer ".java"-Datei noch keine ".class"-Datei existiert oder eine ".java"-Datei neuer als die zugehörige ".class"-Datei ist. − − Wenn eine "package"-Anweisung verwendet wird, muss sie die erste Anweisung in der Quelldatei sein. Klassen ohne "package"-Anweisung gehören zu einem namenlosen Default-Paket (default package). Default-Pakete sind für kleinere Programmierprojekte gedacht, für die es sich nicht lohnt, eigene Packages anzulegen. In aller Regel gehören alle in einem Verzeichnis oder in einem Unterverzeichnis gespeicherte Klassen zu ein- und demselben Default-Paket. Sichtbarkeit Tabelle 3.1 gibt einen Überblick über die Sichtbarkeit von Daten und Methoden. Die Sichtbarkeit lässt sich mit Modifikatoren verändern (private, <default>, protected, public). Tabelle 3.1 Sichtbarkeit von Daten und Methoden In der Klasse In einer abgeleiteten Klasse Im Paket (package) Überall private X <default> X X protected X (X) X public X X X X (X) = Mit Einschränkungen. − − − − Mit "private" vereinbarte Daten und Methoden sind nur in der eigenen Klasse sichtbar. Ohne Angabe eines Modifikators (<default>) vereinbarte Daten und Methoden sind nur im eigenen Paket sichtbar. Mit "protected" vereinbarte Daten und Methoden sind im eigenen Paket und mit Einschränkungen in abgeleiteten Klassen in anderen Paketen sichtbar. Mit "public" vereinbarte Daten und Methoden sind überall sichtbar. Im Folgenden wollen wir den Zugriffsschutz von Membervariablen und Methoden mittels Modifikatoren an Hand eines Beispiels noch deutlicher machen. Für die weitere Diskussion gehen wir davon aus, dass sich die Klassen A, B und C im Paket x befinden und die Klassen D und E im Paket y. Die Klasse C im Paket x ist von der Klasse A abgeleitet. Ebenso ist die Klasse E im Paket y von der Klasse A abgeleitet. Die Klasse A im Paket x hat die Membervariable valueInA und die Methode methodInA(). Als Diskussionsgrundlage sollen diese zunächst den Zugriffsschutz "private", dann "<default>", dann "protected" und schließlich "public" haben. Beispiel: ProtectedDemo //--- package x ------------------package x; 3 OOP II : Vererbung public class A { private int valueInA = 0; private void methodInA() { System.out.println("Method in A"); } } package x; public class B { public void methodInB() { A refA = new A(); refA.valueInA = 5; // z1 refA.methodInA(); // z2 } } package x; public class C extends A { public void methodInC() { valueInA = 2; // z1 methodInA(); // z2 A refA = new A(); refA.valueInA = 5; // z3 refA.methodInA(); // z4 } } // --- package y ------------------package y; import x.A; public class D { public void methodInD() { A refA = new A(); refA.valueInA = 5; // z1 refA.methodInA(); // z2 } } package y; import x.A; public class E extends A { public void methodInE() { valueInA = 2; // z1 methodInA(); // z2 A refA = new A(); refA.valueInA = 5; // z3 refA.methodInA(); // z4 } } 70 71 3 OOP II : Vererbung Zugriffsmodifikator "private": Haben die Membervariablen und Methoden der Klasse A das Zugriffsrecht "private", so kann aus keiner der Klassen B, C, D und E auf die privaten Membervariablen und Methoden der Klasse A zugegriffen werden. Zugriffsmodifikator "<default>": Haben die Membervariablen und Methoden der Klasse A das Zugriffsrecht "<default>", so kann aus Klassen heraus, die im gleichen Paket liegen, zugegriffen werden. Die Zugriffe auf die Variable valueInA und die Methode methodInA() in den Klassen B und C sind also zulässig. Die Klassen D und E haben keinen Zugriff auf die Variable valueInA und die Methode methodInA() der Klasse A. Zugriffsmodifikator "protected": Haben die Membervariablen und Methoden der Klasse A das Zugriffsrecht "protected", so besteht gegenüber dem Zugriffsrecht "<default>" ein etwas erweiterter Zugriff. Auf Membervariablen und Methoden mit dem Zugriffsrecht "protected" kann aus Klassen im gleichen Paket zugegriffen werden, und zusätzlich können auch Subklassen in anderen Paketen auf die von der Vaterklasse geerbten Membervariablen und Methoden zugreifen. Bedingung ist allerdings, dass nur auf die eigenen ererbten Membervariablen und Methoden zugegriffen wird. Wird jedoch in der Klasse E ein neues Objekt der Klasse A angelegt, so darf auf die protected geschützten Membervariablen und Methoden dieses Objekts nicht zugegriffen werden. Die Zugriffe z1 und z2 in der Klasse E sind also zulässig, die Zugriffe z3 und z4 sind jedoch nicht zulässig. Zugriffsmodifikator "public": Haben die Membervariablen und Methoden der Klasse A das Zugriffsrecht "public", so kann aus allen Klassen B, C, D und E auf die Membervariablen und Methoden der Klasse A zugegriffen werden. 3.6 Übungsaufgaben Übungsaufgabe 3.1 Programmieren und testen Sie die in Bild 3.6 dargestellten Klassen. Mit den Klassen soll "main()" zu der gegebenen Textausgabe auf der Konsole führen. class Konto { ... ... } class PrivatGirokonto extends Konto { ... ... } PrivatGirokonto Konto kontonummer bankleitzahl display() kundenname kundenadresse zinssatz kontostand display() Bild 3.6 Klassen "Konto" und "PrivatGirokonto" 3 OOP II : Vererbung public class Test { public static void main(String[] args) { PrivatGirokonto pGiro = new PrivatGirokonto( 32082455L, 20040020L,"Meyer, Boris", "Alphaeck 12, 22624 Hamburg", 3.20, 6123.25); pGiro.display(); } } Auf der Konsole Meyer, Boris Alphaeck 12, 22624 Hamburg Kontonr. 32082455 BLZ 20040020 Zinsen 3.2% Kontostand 6123.25 DM 72 4 Grafikprogramme, Maus und Tastatur Ab diesem Abschnitt werden Klassen und Methoden aus dem Swing Toolset eingesetzt. Swing war der Name eines Projekts bei Sun Microsystems, in dem neue Komponenten und das zugehörige API (Application Programming Interface) entwickelt wurden. Im Gegensatz zum Vorläufer AWT (Abstract Windows Toolkit) benutzt Swing nur noch selten plattformspezifische GUI Ressourcen (Graphical User Interface). Wenn z.B. ein Button gezeichnet werden soll, übernimmt Swing diese Aufgabe selbst und gibt den Auftrag nicht etwa an den GUI Manager weiter. − Als Vorteile fallen plattformspezifische Besonderheiten weg, und man ist nicht mehr auf den kleinsten gemeinsamen Nenner der unterstützten Betriebssysteme angewiesen. − Dem stehen als Nachteile langsamere Ausführung und höherer Ressourcenbedarf gegenüber. Außerdem werden Swing Applets von Browsern nur selten unterstützt. Jedes Programm mit einem Swing GUI muss mindestens eine der folgenden Hauptkomponenten enthalten: JFrame, JWindow, JDialog oder JApplet. Die Hauptkomponenten stellen anderen Swing Komponenten Methoden zum Zeichnen und zur Ereignisbehandlung zur Verfügung. − JFrame: Ein Hauptfenster mit Rahmen, Kopfleiste und Standardschaltflächen. − JWindow: Ein rahmenloses Fenster. − JDialog: Ein sekundäres Fenster, das von einem anderen Fenster abhängig ist. − JApplet: Ein Fenster für einen Ausgabebereich in einem Browser. Im folgenden ersten Swing Beispiel wird die Klasse "HelloWorld1" von JFrame abgeleitet. Die Programmumgebung ruft "paint" auf, wenn das Fenster neu gezeichnet werden soll. Das ist z.B. nach dem Programmstart der Fall oder wenn das Fenster verdeckt worden war und dann wieder sichtbar wird. Beispiel, HelloWorld1 import java.awt.*; import javax.swing.*; // For "Graphics" // For "JFrame" public class HelloWorld1 extends JFrame { // Draw whenever necessary. Do some fancy graphics public void paint(Graphics g) { setBackground(Color.GREEN); // The pink oval g.setColor(Color.PINK); g.fillOval(10, 10, 330, 100); // The red outline. Java doesn't support wide lines, so a // 4-pixel wide line is simulated by drawing four ovals g.setColor(Color.RED); g.drawOval(10,10, 330, 100); g.drawOval( 9, 9, 332, 102); g.drawOval( 8, 8, 334, 104); g.drawOval( 7, 7, 336, 106); // The text g.setColor(Color.BLACK); g.setFont(new Font("Arial", Font.BOLD, 46)); 4 Grafikprogramme, Maus und Tastatur 74 g.drawString("Hello World!", 40, 75); } public static void main(String[] args) { JFrame window = new HelloWorld1(); window.setTitle("Hello World 1"); window.setSize(360, 150); window.setLocation(100, 100); // Exit the application using the System exit method. // Use this only in applications window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 4.1 zeigt das Ausgabefenster. Der Koordinatenursprung liegt in der oberen linken Ecke des Gesamtfensters. Die Kopfleiste überdeckt deshalb hier noch einen Teil des Ovals. Der Fehler wird in den beiden folgenden Programmen korrigiert werden. Bild 4.1 Hello World 1 Das Swing-Hauptfenster JFrame und seine Komponenten JFrame enthält eine Hauptkomponente Root Pane, die ihrerseits die in Bild 4.2 zu sehenden weiteren Komponenten besitzt. Bild 4.2 Frame, Root Pane und Komponenten (aus [4]) Layered Pane (Geschichtete Scheibe) Die Layered Pane wird eingesetzt, um die zugehörigen Komponenten Content Pane und Menu Bar (Menüleiste) zu positionieren. Sie kann zusätzliche übereinander geschichtete Komponenten mit in z-Richtung frei wählbarer Reihenfolge enthalten. Content Pane Dieser Container nimmt in der Regel die sichtbaren Komponenten eines Programms mit Ausnahme der Menüleiste auf. Menu Bar (optional) Die Menüleiste enthält die Menüs einer Anwendung Glass Pane (Glasscheibe) Die Glass Pane ist normalerweise durchsichtig und wird in der Regel nicht zur Ausgabe benutzt. Sie kann eingesetzt werden, wenn man das gesamte Fenster ansprechen will, um z.B. Mauseingaben abzufangen. Im Programm "HelloWorld2" ist eine erste Vorgehensweise zur Grafikausgabe in der Content Pane zu finden. 4 Grafikprogramme, Maus und Tastatur 75 Beispiel, HelloWorld2 import java.awt.*; import javax.swing.*; public class HelloWorld2 extends JFrame { public static void main(String[] args) { JFrame window = new HelloWorld2(); window.setTitle("Hello World 2"); window.setSize(360, 150); window.setLocation(100, 100); window.setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel mp = new MyPanel(); window.getContentPane().add(mp); window.setVisible(true); } } class MyPanel extends JPanel { MyPanel() { setBackground(Color.GREEN); } // Draw whenever necessary. Do some fancy graphics public void paintComponent(Graphics g) { super.paintComponent(g); // The pink oval // ... Same code as in HelloWorld1 } } Bild 4.3 zeigt das Ausgabefenster. Der Fehler des Programms "HelloWorld1" ist also behoben. JPanel mp = new MyPanel(); window.getContentPane().add(mp); Die Methode "getContentPane" gibt eine Referenz auf die Content Pane zurück. In die Content Pane wird dann eine Instanz von "MyPanel" eingefügt. Bild 4.3 Hello World 2 class MyPanel extends JPanel { Die Klasse "MyPanel" wird von "JPanel" (Tafel, Platte) abgeleitet. public void paintComponent(Graphics g) { super.paintComponent(g); Die Programmumgebung ruft "paintComponent" auf, wenn das Fenster neu gezeichnet werden soll. Der Aufruf von "super.paintComponent" führt hier dazu, daas das Fenster mit der Hintergrundfarbe gefüllt wird. Das Programm "HelloWorld3" zeigt eine zweite Vorgehensweise zur Grafikausgabe in der Content Pane. Man leitet das Programm von "JPanel" ab und instanziiert "JFrame" in "main". 4 Grafikprogramme, Maus und Tastatur 76 Beispiel, HelloWorld3 import java.awt.*; import javax.swing.*; public class HelloWorld3 extends JPanel { HelloWorld3() { setBackground(Color.GREEN); } // Draw whenever necessary. Do some fancy graphics public void paintComponent(Graphics g) { super.paintComponent(g); // The pink oval // ... Same code as in HelloWorld1 } public static void main(String[] args) { JFrame window = new JFrame("Hello World 3"); window.setSize(360, 150); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new HelloWorld3()); window.setVisible(true); } } Das Ausgabefenster entspricht Bild 4.3, in der Kopfleiste wird aber "Hello World 3" ausgegeben. In den weiteren Beispielen dieses Abschnitts wird in der Regel die in "HelloWorld3" verwendete Programmstruktur eingesetzt. Das Programm "RandomStrings" gibt 25 Zeichenketten aus. Farbe und Position der einzelnen Kopien werden dabei zufällig bestimmt. Die Schriftart wird ebenfalls zufällig aus fünf Vorgaben gewählt Beispiel, RandomStrings import java.awt.*; import javax.swing.*; public class RandomSrrings extends JPanel { private Font font[] = new Font[5]; RandomSrrings () { // The font name can be a logical font name or a font face // name. A logical name must be either: Dialog, DialogInput, // Monospaced, Serif, SansSerif, or Symbol. If name is null, // the name of the new Font is set to Default font[0] = new Font("Serif", Font.BOLD, 14); font[1] = new Font("SansSerif", Font.BOLD + Font.ITALIC, 24); font[2] = new Font("Monospaced", Font.PLAIN, 20); font[3] = new Font("Dialog", Font.PLAIN, 30); font[4] = new Font("Serif", Font.ITALIC, 36); setBackground(Color.black); } 4 Grafikprogramme, Maus und Tastatur 77 public void paintComponent(Graphics g) { super.paintComponent(g); int width = getSize().width; int height = getSize().height; // Get width // and height for (int i = 0; i < 25; i++) { int fontNum = (int) (5 * Math.random()); g.setFont(font[fontNum]); float hue = (float) Math.random(); // hue = Farbton // getHSBColor(float hue, float saturation, // float brightness) g.setColor(Color.getHSBColor(hue, 1.0F, 1.0F)); // Select the position of the string, at random int x = -50 + (int) (Math.random() * (width + 40)); int y = (int) (Math.random() * (height + 20)); // Draw the message g.drawString("Hello", x, y); } } public static void main(String[] args) { JFrame window = new JFrame("Random Srrings"); window.setSize(360, 150); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new RandomSrrings()); window.setVisible(true); } } Vom Programm wird das Fenster in Bild 4.4 ausgegeben. Das Programm hat aber noch Nachteile. − Wenn der Benutzer die Größe des Fensters ändert, wird erneut "paintComponent" aufgerufen. Es werden dann neue Kopien von "Hello" ausgegeben und folglich ein verändertes Bild gezeichnet. − Wenn ein ehemals verdeckter Teil des Fensters wieder sichtbar wird, wird nur dieser Bereich des Fensters erneuert. In Bild 4.5 ist zum Beispiel die rechte Bildhälfte neu gezeichnet worden. Bild 4.4 Random Strings Bild 4.5 Nur die rechte Bildhälfte wurde neu gezeichnet Aufgabe 4.1 Schreiben Sie ein Programm "RandomStrings1". Ändern Sie dazu "RamdomStrings" so, dass das Bild beim Neuzeichnen unverändert bleibt. 4 Grafikprogramme, Maus und Tastatur 4.1 78 Maus- und Tastaturereignisse Grafische Programme sind ereignisgesteuert. Sie warten und reagieren auf äußere Ereignisse (Events), wie z.B. das Verändern eines Fensters, das Bewegen der Maus oder das Betätigen einer Taste. Es gibt Ereignisquellen (Event sources) und Ereignisempfänger (Event listener). In Tabelle 4.1 sind Listener Interfaces (Empfängerschnittstellen) für Maus und Tastatur und die zugehörigen Methoden zu finden. Der Java Begriff "Interface" wird in einem späteren Abschnitt genauer erläutert. Tabelle 4.1 Listener Interface für Maus und Tastatur und zugehörige Methoden Listener Interface MouseListener MouseMotionListener KeyListener Methoden mouseClicked mouseEntered mouseExited mousePressed mouseReleased mouseDragged mouseMoved keyPressed keyReleased keyTyped Auslösende Aktion(en) Eine Maustaste wurde gedrückt und wieder losgelassen Der Mauszeiger "betritt" die Komponente Der Mauszeiger verlässt die Komponente Eine Maustaste wurde gedrückt Eine Maustaste wurde losgelassen Die Maus wurde bei gedrückter Taste bewegt Die Maus wurde bewegt, ohne dass eine Taste gedrückt war Eine Taste wurde gedrückt Eine Taste wurde losgelassen Eine Taste wurde gedrückt und wieder losgelassen Für Mausereignisse gibt es zwei Event Interfaces. "MouseListener" ist für Mausklicks zuständig, und "MouseMotionListener" reagiert auf Mausbewegungen. Diese Trennung ist aus Performancegründen vorgenommen worden. Wenn der Benutzer die Maustaste 1 gefolgt von der Maustaste 2 drückt und sie in derselben Reihenfolge wieder loslässt, werden zum Beispiel die folgenden Methoden in der gegebenen Reihenfolge aufgerufen: 1) 2) 3) 4) 5) 6) mousePressed mousePressed mouseReleased mouseClicked mouseReleased mouseClicked für für für für für für Maustaste Maustaste Maustaste Maustaste Maustaste Maustaste 1 2 1 1 2 2 4.1.1 Maus Vom folgenden Programm wird das Fenster in Bild 4.6 ausgegeben. Der Kreis lässt sich bei gedrückter linker oder rechter Maustaste über das Fenster ziehen. Beispiel, CircleAndMouse import java.awt.*; import java.awt.event.*; import javax.swing.*; Bild 4.6 CircleAndMouse // For MouseEvent, MouseMotionAdapter public class CircleAndMouse extends JPanel { private static final int R = 40; private static final Color BG_COLOR = Color.WHITE; 79 4 Grafikprogramme, Maus und Tastatur private static final Color FG_COLOR = Color.BLACK; private int x = 40, y = 40; // Center of circle CircleAndMouse() { // Add MyMouseMotionListener to receive mouse motion events addMouseMotionListener(new MyMouseMotionListener()); setBackground(BG_COLOR); } public void paintComponent(Graphics g) { super.paintComponent(g); draw(g); } void draw(Graphics g) { display(g, FG_COLOR); } void erase(Graphics g) { display(g, BG_COLOR); } void display(Graphics g, Color c) { g.setColor(c); g.drawOval(x-R, y-R, R+R, R+R); } void moveTo(Graphics g, int xx, int yy) { erase(g); x = xx; y = yy; draw(g); } public static void main(String[] args) { JFrame window = new JFrame("Circle and Mouse"); window.setSize(360, 200); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new CircleAndMouse()); window.setVisible(true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - class MyMouseMotionListener extends MouseMotionAdapter { public void mouseDragged(MouseEvent me) { moveTo(getGraphics(), me.getX(), me.getY()); } } } addMouseMotionListener(new MyMouseMotionListener()); Mit dieser Anweisung wird die Klasse "MyMouseMotionListener" als Ereignisempfänger angemeldet. Danach wird ihre Methode "mouseDragged" (und gegebenenfalls auch "mouseMoved") bei den zugehörigen Mausereignissen aufgerufen. class MyMouseMotionListener extends MouseMotionAdapter { public void mouseDragged(MouseEvent me) { moveTo(getGraphics(), me.getX(), me.getY()); } } Die Klasse "MyMouseMotionListener" ist vollständig in die Klasse "CirleAndMouse" eingebettet. "MyMouseMotionListener" ist damit eine innere Klasse (inner class). Eine innere Klasse hat Zugriff auf alle Daten und Methoden der umgebenden Klasse. Das gilt auch dann, wenn diese Daten und Methoden als "private" vereinbart worden sind. Adapterklassen, wie hier z.B. "MouseMotionAdapter" werden in einem späteren Abschnitt erläutert. 4 Grafikprogramme, Maus und Tastatur 80 Das folgende Programm MouseDemo1 enthält sowohl einen "MouseListener" als auch einen "MouseMotionListener". Darüber hinaus zeigt es, wie man Doppelklicks feststellen kann, die jeweilige Maustaste bestimmt und beim Mausklick zusätzlich gedrückte Tasten ermittelt. Beispiel, MouseDemo1 import java.awt.event.*; import javax.swing.*; public class MouseDemo1 extends JPanel { private long previousTime = 0; MouseDemo1() { addMouseMotionListener(new MyMouseMotionListener()); addMouseListener(new MyMouseListener()); } void printMessageMousePressed(String s, MouseEvent me) { long time = me.getWhen(); // Timestamp in ms if(time - previousTime < 300) s += "Double "; previousTime = time; if(me.isShiftDown()) // Test Shift s += "Shift "; if(me.isControlDown()) // Test Control s += "Control "; int mods = me.getModifiers(); if((mods & InputEvent.BUTTON1_MASK) != 0) System.out.println(s + "Button 1"); // Left mouse button if((mods & InputEvent.BUTTON2_MASK) != 0) System.out.println(s + "Button 2"); // Middle or wheel if((mods & InputEvent.BUTTON3_MASK) != 0) System.out.println(s + "Button 3"); // Right } void printMessageMouseDragged(String s, MouseEvent me) { if(me.isShiftDown()) // Test Shift s += "Shift "; if(me.isControlDown()) // Test Control s += "Control "; int mods = me.getModifiers(); if((mods & InputEvent.BUTTON1_MASK) != 0) System.out.println(s + "Button 1"); // Left mouse button if((mods & InputEvent.BUTTON2_MASK) != 0) System.out.println(s + "Button 2"); // Middle or wheel if((mods & InputEvent.BUTTON3_MASK) != 0) System.out.println(s + "Button 3"); // Right } 4 Grafikprogramme, Maus und Tastatur 81 public static void main(String[] args) { JFrame window = new JFrame("Mouse Demo 1"); window.setSize(360, 200); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new MouseDemo1()); window.setVisible(true); } // --------------------------------------------------------class MyMouseMotionListener extends MouseMotionAdapter { public void mouseDragged(MouseEvent me) { printMessageMouseDragged("mouseDragged ", me); } } class MyMouseListener extends MouseAdapter { public void mousePressed(MouseEvent me) { printMessageMousePressed("mousePressed ", me); } } } Auf der Konsole mousePressed mousePressed mousePressed mousePressed mousePressed mousePressed mouseDragged mouseDragged ... Button 1 Button 1 Double Button 1 Shift Button 3 Shift Control Button 3 Shift Button 1 Shift Button 1 Shift Button 1 // Beginn eines Doppelklicks // Rechte Maustatse // Shift + Maustaste gedrückt und Maus bewegt Aufgabe 4.2 Schreiben Sie das Programm "Duke", mit dem ein in der Datei "Duke.gif" gespeichertes Bild geladen und dargestellt werden kann (Bild 4.7). "Duke" soll an die Position eines Mausklicks springen und sich bei gedrückter Maustaste bewegen lassen. Der folgende Programmausschnitt zeigt, wie man eine Bilddatei lesen und ein Bild zeichnen kann. Bild 4.7 Duke ... public class Duke extends JPanel { private Image duke; private int hDuke, wDuke; // Height and width of Duke ... Duke() { addMouseListener(new MyMouseListener()); 4 Grafikprogramme, Maus und Tastatur 82 addMouseMotionListener(new MyMouseMotionListener()); setBackground(BG_COLOR); duke = getToolkit().getImage("Duke.gif"); MediaTracker mt = new MediaTracker(this); mt.addImage(duke, 0); // Identifier id = 0 // Wait until image with identifier 0 has been loaded try { mt.waitForID(0); } catch (InterruptedException e) {} wDuke = duke.getWidth(this); hDuke = duke.getHeight(this); } ... // x, y is the upper left corner of the picture void draw(Graphics g) { g.drawImage(duke,x-wDuke,y-hDuke,this); } ... Die Methode "getToolkit" gibt eine Referenz auf einen "Werkzeugkasten" für die aktuelle Umgebung zurück. Zu diesem Werkzeugkasten gehört die Methode "getImage", die den Lesevorgang für die als Parameter übergebene Bilddatei anstößt. (Wenn nicht der vollständige Pfad, sondern nur ein Dateiname angegeben wird, erwartet Eclipse die Datei im Projektverzeichnis, also im Verzeichnis, in dem auch die Datei ".project" gespeichert ist.) Die Klasse "MediaTracker" ermöglicht es, auf den Abschluss des Lesevorgangs zu warten. Mit "addImage" wird "duke" in die Liste der zu verfolgenden Lesevorgänge aufgenommen, und es wird der Identifier 0 zugewiesen. Die Methode "waitForID" kehrt erst dann in das aufrufende Programm zurück, wenn die Datei mit dem angegebenen Identifier vollständig gelesen worden ist. 4.1.2 Tastatur Im folgenden Programm "CircleAndKeys" lässt sich ein Kreis mit den Pfeiltasten der Tastatur bewegen. Dazu enthält "CircleAndKeys" einen "KeyListener". Das vom Programm ausgegebene Fenster stimmt Bild 4.6 überein. Beispiel, CircleAndKeys import java.awt.*; import java.awt.event.*; import javax.swing.*; public class CircleAndKeys extends JPanel { private static final int R = 40; private static final Color BG_COLOR = Color.WHITE; private static final Color FG_COLOR = Color.BLACK; private int x = 40, y = 40; // Center of circle CircleAndKeys() { setBackground(BG_COLOR); addKeyListener(new MyKeyListener()); // Request that this component gets the input focus 83 4 Grafikprogramme, Maus und Tastatur setFocusable(true); requestFocus(); } public void paintComponent(Graphics g) { super.paintComponent(g); draw(g); } void draw(Graphics g) { display(g, FG_COLOR); } void erase(Graphics g) { display(g, BG_COLOR); } void display(Graphics g, Color c) { g.setColor(c); g.drawOval(x-R, y-R, R+R, R+R); } void moveTo(Graphics g, int xx, int yy) { erase(g); x = xx; y = yy; draw(g); } public static void main(String[] args) { JFrame window = new JFrame("Circle and Keys"); window.setSize(360, 200); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new CircleAndKeys()); window.setVisible(true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - class MyKeyListener extends KeyAdapter { public void keyPressed(KeyEvent ke) { switch(ke.getKeyCode()) { case KeyEvent.VK_DOWN: // Valid key arrow down moveTo(getGraphics(), x, y+5); break; case KeyEvent.VK_UP: // Arrow up moveTo(getGraphics(), x, y-5); break; case KeyEvent.VK_LEFT: // Arrow left moveTo(getGraphics(), x-5, y); break; case KeyEvent.VK_RIGHT: // Arrow right moveTo(getGraphics(), x+5, y); break; } } } } Das folgende Programm "KeyDemo" zeigt, wie man auf wieder losgelassene Tasten reagieren kann. Im Gegensatz zu gedrückten Tasten gibt es in diesem Fall keine Selbstwiederholung (auto repeat). Zusätzlich werden die Funktionstasten "Shift" und "Ctrl" abgefragt. Beispiel, KeyDemo import java.awt.event.*; import javax.swing.*; public class KeyDemo extends JPanel { KeyDemo() { addKeyListener(new MyKeyListener()); setFocusable(true); requestFocus(); } 4 Grafikprogramme, Maus und Tastatur public static void main(String[] args) { JFrame window = new JFrame("Key Demo"); window.setSize(360, 200); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new KeyDemo()); window.setVisible(true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - class MyKeyListener extends KeyAdapter { public void keyReleased(KeyEvent ke) { String s = "KeyReleased "; if(ke.isShiftDown()) s += "Umschalt "; if(ke.isControlDown()) s += "Strg "; switch(ke.getKeyCode()) { // Two examples for valid key constants case KeyEvent.VK_F1: s += "Key F1"; break; case KeyEvent.VK_F2: s += "Key F2"; break; default: // Methods in class KeyEvent s += KeyEvent.getKeyText(ke.getKeyCode()); } System.out.println(s); } } } Auf der Konsole KeyReleased KeyReleased KeyReleased KeyReleased KeyReleased KeyReleased KeyReleased ... Rechts Links Key F1 Umschalt Key F1 Umschalt Strg Key F2 Strg // // // // // // // Pfeiltaste -> Pfeiltaste <Funktionstaste F1 Shift + F1 Shift Strg + F2 Strg 84 4 Grafikprogramme, Maus und Tastatur 85 Aufgabe 4.3 Schreiben Sie ein Programm "Ellipse" zur grafischen Ausgabe der Funktion, wie es Bild 4.8 zeigt. Die folgende Gleichung beschreibt eine Ellipse mit den beiden Halbachsen a und b. x2 y2 + =1 a 2 b2 − Mit der Taste F1 lässt sich die Farbe von Achsenkreuz, Beschriftung und Ellipse ändern. − Wenn der Benutzer die Fenstergröße ändert, soll sich das Diagramm der neuen Größe anpassen. 4.2 Bild 4.8 Ellipse Übungsaufgaben Übungsaufgabe 4.1 Schreiben Sie ein Programm "RandomStrings2". Ändern Sie dazu das in Aufgabe 4.1 geschriebene Programm "RandomString1" so, dass die Anzahl der Strings proportional zur Fenstergröße anwächst, die "Stringdichte" also näherungsweise gleich bleibt. Übungsaufgabe 4.2 Schreiben Sie ein Programm "StringsAndMouse". Ändern Sie dazu das in Aufgabe 4.1 geschriebene Programm "RandomString1" so, dass neue Strings erzeugt und ausgegeben werden, wenn der Benutzer eine Maustaste drückt. Übungsaufgabe 4.3 Schreiben Sie ein Programm "MouseAndKeys". Ändern Sie dazu das Programm "CircleAndMouse" aus dem vorigen Abschnitt so, dass − mit den Funktionstasten F1 bis F4 unterschiedliche Farben für den Kreis ausgewählt werden können und − sich der Kreis sowohl mit der Maus als auch mit den Pfeiltasten der Tastatur bewegen lässt. 5 Applets Ein erstes Applet ist schon im Abschnitt 1.1 zu finden. Dort ist das Applet von der AWT Klasse "Applet" abgeleitet worden. In den folgenden Beispielen wird stattdessen die Swing Klasse "JApplet" eingesetzt. Vorteile und Nachteile sind schon im vorausgehenden Abschnitt erläutert worden. Beispiel, HelloWorldApplet import java.awt.*; import javax.swing.*; public class HelloWorldApplet extends JApplet { public void init () { getContentPane().add(new MyPanel()); } } class MyPanel extends JPanel { private Font font; MyPanel() { font = new Font("Arial", Font.BOLD, 46); setBackground (Color.GREEN); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.PINK); g.fillOval(10, 10, 330, 100); g.setColor(Color.RED); g.drawOval(10,10, 330, g.drawOval( 9, 9, 332, g.drawOval( 8, 8, 334, g.drawOval( 7, 7, 336, 100); 102); 104); 106); g.setColor(Color.BLACK); g.setFont(font); g.drawString("Hello World!", 40, 75); } } Datei "HelloWorldApplet.html" <HTML> <APPLET CODE="HelloWorldApplet.class" WIDTH=352 HEIGHT=120> </APPLET> </HTML> Vom Appletviewer wird das Fenster in Bild 5.1 ausgegeben. Bild 5.1 HelloWorldApplet 5 Applets 87 Bevor ein Applet aktiv wird, instanziiert der Browser (oder der Appletviewer) zunächst ein Objekt der von "JApplet" abgeleiteten Klasse, im vorausgehenden Beispiel also von "HelloWorldApplet". Einige Methoden, die vom Browser aufgerufen werden − public void init() Nach der Instanziierung ruft der Browser "init" auf, um dem Applet die Möglichkeit zu geben, Initialisierungen vorzunehmen. Die Methode "init" wird während der Lebensdauer eines Applets nur einmal aufgerufen. − public void start() Der Browser ruft "start" immer dann auf, wenn das Applet sichtbar wird. Im Gegensatz zur Initialisierung kann das Starten eines Applets also mehrfach erfolgen. Die Methode kann z.B. eine Animation starten. − public void paintComponent(Graphics) Durch Aufruf von "paintComponent" fordert der Browser das Applet auf, sich auf dem Bildschirm darzustellen. Die Methode wird z.B. aufgerufen, wenn das Browserfenster verdeckt war und dann wieder neu gezeichnet werden muss. − public void stop() Der Browser ruft "stop" auf, wenn ein Applet vorübergehend unsichtbar wird, weil der Benutzer es beispielsweise vom Bildschirm gescrollt hat. Die Methode kann z.B. eine Animation anhalten. − public void destroy() Durch Aufruf von "destroy" teilt der Browser dem Applet mit, dass es nicht mehr benötigt wird. Das ist z.B. der Fall, wenn der Benutzer den Browser beendet. Die Methode kann z.B. einen Thread (Programmfaden) "zerstören", der vom Applet angelegt worden ist. Darüber hinaus gibt es weitere Methoden. Einige werden verwendet, um auf äußere Ereignisse (events) zu reagieren, wie z.B. auf Tastatur- oder Mausereignisse. Unterschiede zwischen Applikationen (Programmen) und Applets − − − Eine Applikation wird vom Java-Interpreter durch Aufruf von "main" gestartet. Zum Starten eines Applets instanziieren Web-Browser oder Appletviewer die von Applet abgeleitete Klasse und rufen danach die Methoden "init" usw. auf. Im Gegensatz zu Applikationen darf ein Applet in der Regel aus Sicherheitsgründen nicht auf Dateien des Rechners zugreifen. Ein Applet arbeitet immer im Grafikmodus, eine Applikation kann sich dagegen auf Konsolenausgabe beschränken. Das folgende Applet "OneLine" zeigt, dass sich die schon bekannte Vorgehensweise zur Ereignisbehandlung unverändert auf Applets übertragen lässt. Zum Zeichnen einer Linie drückt man eine Mautaste und zieht den Mauscursor bei gedrückt gehaltener Maustaste. Die Linie folgt dem Cursor, bis die Maustaste wieder losgelassen wird. Mit einer beliebigen Taste der Tastatur kann man die Linienfarbe verändern. Beispiel, OneLine import java.awt.*; import java.awt.event.*; 5 Applets 88 import javax.swing.*; public class OneLine extends JApplet { public void init() { getContentPane().add(new OneLinePanel()); } } class OneLinePanel extends JPanel { private final static Color COLOR[] = { Color.BLACK, Color.WHITE }; private Point from = new Point(-1, -1), to = new Point(-1, -1); private int colInd = 0; OneLinePanel() { addMouseListener(new MyMouseListener()); addMouseMotionListener(new MyMouseMotionListener()); addKeyListener(new MyKeyListener()); setFocusable(true); requestFocus(); setBackground (Color.LIGHT_GRAY); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(COLOR[colInd]); g.drawLine(from.x, from.y, to.x, to.y); g.setColor(Color.BLACK); g.drawString("Line from (" + from.x + "," + from.y + ") to (" + to.x + "," + to.y + ")", 0, 10); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class MyMouseListener extends MouseAdapter { public void mousePressed(MouseEvent me){ from = to = me.getPoint(); repaint(); } } class MyMouseMotionListener extends MouseMotionAdapter { public void mouseDragged(MouseEvent me) { to = me.getPoint(); repaint(); } } class MyKeyListener extends KeyAdapter { public void keyPressed(KeyEvent ke) { colInd = ++colInd % COLOR.length; repaint(); } } } Bild 5.2 zeigt ein Beispiel zum Fenster, das vom Appletviewer ausgegeben wird. Aufgabe 5.1 Schreiben Sie das Applet "OneLIne" so um, dass man es wahlweise als Applet oder als Programm (Application) starten kann. Verwenden Sie dabei eine Vorgehensweise, die sich ohne größere Änderungen auf andere Applets übertragen lässt. Bild 5.2 Applet OneLine 5 Applets 5.1 89 Das Einbinden eines Applets Ein Applet wird mit einem Applet-Tag (Kennung, Markierung) in ein HTML Dokument eingebunden3. Die "Minimalform" eines Applet-Tags ist schon bekannt: <APPLET CODE="HelloWorld.class" WIDTH=350 HEIGHT=120> </APPLET> Die Datei "HelloWorld.class" wird in diesem Fall im selben Verzeichnis wie das HTMLDokument erwartet. In Tabelle 5.1 sind einige weitere Angaben zu finden, die in einem Applet-Tag zulässig sind. Tabelle 5.1 Einige im Applet-Tag zulässige Angaben Angabe CODEBASE ALIGN HSPACE VSPACE Bedeutung Verzeichnis, in dem die ".class"-Dateien gesucht werden (beliebige URL) Ausrichtung des Fensters (z.B. left oder right) Rand rechts und links vom Applet Rand über und unter dem Applet In der folgenden HTML Datei "AppletAndText" wird das Applet "OneLIne" in Text eingebettet. Die Angaben im Applet Tag führen dazu, dass der Browser − das Applet im Unterverzeichnis "classes" zum Verzeichnis der HTML Datei sucht, − das Applet-Fenster rechtsbündig ausrichtet und − die vorgegebenen Ränder einhält. Datei "AppletAndText.html" <HTML> <p>An array is an important data structure in any programming language. An array is a fixed-length structure that stores <APPLET CODEBASE="classes" CODE="OneLine.class" WIDTH=200 HEIGHT=160 ALIGN=right HSPACE=25 VSPACE=15> </APPLET> multiple values of the same type. You can group values of the same type within arrays. Arrays are supported directly by the Java programming language; there is no array class. Arrays are implicit extensions of the Object class, so you can assign an array to a variable whose type is declared as Object. The Java platform groups its classes into functional packages. Instead of writing your own classes, you can use one provided by the platform. Most of the classes discussed in this chapter are members of the java.lang package. All the classes in the java.lang package are available to your programs automatically.</p> </HTML> 3 Die Einbindung eines Applets mit Embed- und Object-Tags wird hier nicht erläutert. 5 Applets 90 Vom Browser wird Bild 5.3 ausgegeben. Bei gedrückter Maustaste lässt sich eine Linie im Appletfenster zeichnen. Bild 5.3 Das Appletfenster wird in den Text eingebettet Center Tag Mit dem HTML Zentrierungs-Tag <CENTER> ... </CENTER> lässt sich ein Applet-Fenster zentriert ausgeben. ... An array is a fixed-length structure that stores <CENTER><APPLET CODEBASE="classes" CODE="CircleAndMouse.class" WIDTH=200 HEIGHT=160> </APPLET></CENTER> multiple values of the same type. You can group values of the same ... Param Tag Man kann in einem Applet Parameter aus der zugehörigen HTML-Datei lesen. Dazu werden Param-Tags verwendet, die in das Applet-Tag eingebettet sind. In der Datei "TestParamTag.html" sind als Beispiel zwei Parameter mit den Namen (name) "initialX" bzw. "initialY" und ihren Werten (value) "10" bzw. "12" zu finden. Datei "TestParamTag.html" <APPLET CODE="TestParamTag.class" WIDTH=350 HEIGHT=120> <PARAM name="initialX" value=10> <PARAM name="initialY" value=12> </APPLET> ImApplet können die Paramter mit der Methode public String getParameter(String name) 5 Applets 91 als String gelesen werden. Das folgenden Programm "TestParamTag.java" zeigt die Vorgehensweise. Datei "TestParamTag.java" import java.awt.*; import javax.swing.*; public class TestAppletTag extends JApplet { private int startX, startY; public void init() { try { startX = Integer.parseInt(getParameter("initialX")); startY = Integer.parseInt(getParameter("initialY")); } catch (NumberFormatException e) { startX = 0; startY = 0; } } public void paint(Graphics g) { System.out.println("startX = " + startX); System.out.println("startY = " + startY); } } Auf der Konsole startX = 10 startY = 12 Aufgabe 5.2 Schreiben Sie ein Applet "Funktion" zur grafischen Ausgabe der folgenden Funktion: y = a0 + a1 ⋅ x + a2 ⋅ sin( π ⋅ x ) + a3 ⋅ sin( 3 ⋅ π ⋅ x ) + a4 ⋅ sin(5 ⋅ π ⋅ x ) Bild 5.4 zeigt, wie die Funktion ausgegeben werden sollen. − Lesen Sie die benötigen Koeffizienten a0 bis a4 aus der HTML-Datei ein. − Wenn das Applet mit einem Appletviewer ausgeführt wird und der Benutzer die Fenstergröße ändert, soll sich die Größe des Diagramms der neuen Fenstergröße anpassen. Bild 5.4 a0 = a1 = 0, a2 = 5, a3 = 1.667, a4 = 1 5 Applets 5.2 92 Übungsaufgaben Übungsaufgabe 5.1 a) Schreiben Sie ein Applet "Rechteck" zum Zeichnen eines Rechtecks. Man drückt eine Mautaste und legt damit den ersten Eckpunkt fest. Bei gedrückt gehaltener Maustaste zieht man den Mauscursor auf den zweiten Eckpunkt. Während der Mauscursor bewegt wird, gibt das Applet jeweils das derzeit aktuelle Rechteck aus. Erster und zweiter Eckpunkt sollen beliebig zueinander positioniert werden können. b) Sinngemäß wie a), es soll aber ein Kreis ausgegeben werden. Übungsaufgabe 5.2 Das gegebene Programm "Klausur" gibt auf der Konsole eine einfache Übersicht über das Ergebnis einer Klausur aus (Bild 5.5). Wandeln Sie das Programm in ein Applet um, das eine ähnliche Übersicht im Appletfenster ausgibt. 0..4 5..6 7..9 10..12 13..15 -----------------------------------------* * * * * * * * * * * * * * * * * * * Participants: 19 Bild 5.5 Übersicht über ein Klasusurergebnis public class Klausur { private final String RANGE[] = {" 0..4 ", " 5..6 ", " 7..9 ", "10..12","13..15"}; private final String STAR = " * ", BLANKS = " "; private int numStud[] = new int[RANGE.length], sum = 0, max = 0; Klausur() { numStud[0] numStud[1] numStud[2] numStud[3] numStud[4] } = = = = = 3; 3; 6; 5; 2; public void diagram() { for(int i = 0; i < RANGE.length; i++) System.out.print(" " + RANGE[i]); System.out.println(); for(int i = 0; i < 2 + 8*RANGE.length; i++) System.out.print('-'); System.out.println(); 5 Applets for(int i = 0; i < numStud.length; i++) { sum += numStud[i]; max = Math.max(max, numStud[i]); } for(int i = 0; i < max; i++) { String s = ""; for(int j = 0; j < RANGE.length; j++) s += (i < numStud[j])? STAR: BLANKS; System.out.println(s); } System.out.println("\nParticipants: " + sum); } public static void main(String[] args) { Klausur klausur = new Klausur(); klausur.diagram(); } } 93 6 OOP III : Polymorphie, Abstrakte Klassen und Interfaces 6.1 Polymorphie und spätes Binden Frühes Binden (Statisches Binden, Early Binding) bedeutet, dass Compiler, Linker und Loader schon vor der Ausführung eines Programms die tatsächliche physikalische Adresse einer Methode (eines Unterprogramms) in den Maschinencode einfügen. Frühes Binden wird z.B. in Assembler- und in C-Programmen verwendet. In dem nachfolgenden Beispiel "EmployeeApp" werden einer Referenz auf die Basisklasse "Employee" nacheinander Instanzen der abgeleiteten Klassen "Worker", "SalesAgent" und "Manager" zugewiesen. Die Konsolenausgabe zeigt, dass immer die displaySalary()-Methode der Klasse aufgerufen wird, auf die die Basisklassenreferenz aktuell zeigt. Dies ist eine Ausprägung der Polymorphie, welche ein wichtiges Konzept des objektorientierten Programmierens ist. Die Polymorphie von Objekten erlaubt es, wieder verwendbaren Code zu schreiben, der nur Referenzen auf Objekte der Basisklasse enthält, wie im nachfolgenden Beispiel der array Employee[] empl. Trotzdem können Objekte der gesamten Vererbungshierarchie in ihm abgelegt werden, und es wird stets die "passende" Methode displaySalary() der entsprechenden abgeleiteten Klasse aufgerufen. Beispiel, EmployeeApp class Employee { private String name; Employee(String n) { name = new String(n); } void displayName() { System.out.print(name + " " ); } void displaySalary() {} // For "empl[i].displaySalary();" } //---------------------------------------------------------------class Worker extends Employee { protected double hourlyWage, hours; Worker(String n, double hW, double h) { super(n); hourlyWage = hW; hours = h; } void displaySalary() { System.out.println(hourlyWage * hours + " EUR"); } } //---------------------------------------------------------------class SalesAgent extends Worker { protected double commission, count; SalesAgent(String n, double hW, double h, double c, double cn) { super(n, hW, h); commission = c; count = cn; 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 95 } void displaySalary() { System.out.println(hourlyWage * hours + commission*count + " EUR"); } } //---------------------------------------------------------------class Manager extends Employee { private double salary; Manager(String n, double s) {super(n); salary = s; } void displaySalary() { System.out.println(salary + " EUR"); } } //---------------------------------------------------------------public class EmployeeApp { public static void main(String[] args) throws IOException { int sel; Employee[] empl = new Employee[3]; BufferedReader din = new BufferedReader( new InputStreamReader(System.in)); for (int i= 0; i < empl.length; i++) { do { System.out.println("Was soll erzeugt werden?"); System.out.println("- Worker (1)\n" + "- SalesAgent (2)\n" + "- Manager (3)"); System.out.print("Eingabe: "); sel = Integer.parseInt(din.readLine()); switch(sel) { case 1: empl[i] = new Worker("Meyer, Klaus", 15.82, 151.00); break; case 2: empl[i] = new SalesAgent("Hamer, Peter", 8.80, 150.0, 60.28, 22.0); break; case 3: empl[i] = new Manager("Kramer, Hans", 3501.27); break; default: System.out.println("Ungültige Eingabe"); } } while (sel < 1 || sel > 3); } for (int i= 0; i < empl.length; i++) { empl[i].displayName(); 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 96 empl[i].displaySalary(); } } } Auf der Konsole: Was soll erzeugt werden? - Worker (1) - SalesAgent (2) - Manager (3) Eingabe: 1 Was soll erzeugt werden? - Worker (1) - SalesAgent (2) - Manager (3) Eingabe: 2 Was soll erzeugt werden? - Worker (1) - SalesAgent (2) - Manager (3) Eingabe: 3 Meyer, Klaus 2388.82 EUR Hamer, Peter 2646.16 EUR Kramer, Hans 3501.27 EUR Wenn in einer abgeleitete Klasse eine Methode definiert wird, die den gleichen Namen, den gleichen Rückgabetyp und die gleichen Parameter wie eine Methode der Oberklasse besitzt, dann überschreibt diese Methode die gleichnamige Methode der Oberklasse (Overriding). Im vorausgehenden Programm ist das für displaySalary() der Fall. Für überschriebene Methoden gilt, dass das zur Programmlaufzeit zugewiesene Objekt die Methode bestimmen. Die Entscheidung, welche Version von displaySalary() jeweils angesprochen wird, findet also erst zur Ausführungszeit des Programms statt und wird als Spätes Binden (Dynamisches Binden, Late binding) bezeichnet. Beachten Sie, dass die Methode displaySalary() in der Basisklasse Employee noch einen leeren Methodenrumpf hat, da man für einen allgemeinen Angestellten noch kein konkretes Gehalt berechnen kann. Das Auswählen der "richtigen" Methode zur Laufzeit des Programms ist in Java zwar effizient implementiert, der Aufwand ist aber dennoch höher als beim Frühen Binden. Wenn das Späte Binden aus Laufzeitgründen verhindert werden soll, lässt sich das folgendermaßen erreichen: − Private Methoden ("private") sind in einer abgeleiteten Klasse nicht sichtbar und können folglich nicht überschrieben werden. − Mit dem zusätzlichen Schlüsselwort "final" lässt sich festlegen, dass eine Methode in abgeleiteten Klassen nicht überschrieben werden kann. − Klassenmethoden ("static") können nicht überschrieben werden. Das nachfolgende Applet ist ebenfalls ein Beispiel zur Polymorphie. Wenn das Applet "ShapeAndMouse" ausgeführt wird, kann man bei gedrückter linker Maustaste wahlweise einen Kreis oder ein Rechteck über das Appletfenster ziehen. Mit der rechten Maustaste lässt sich dabei zwischen Kreis zum Rechteck umschalten. Hier wird die Methode display() der Basisklsse Shape in den abgeleiteten Klassen Circle und Rect überschrieben. Die Aufgabe der Methode display() ist es, das jeweilige Objekt auf der Zeichenebene zu zeichen. 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 97 Beispiel, ShapeAndMouse import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ShapeAndMouse extends JApplet { public void init() { getContentPane().add(new SMPanel()); } } class SMPanel extends JPanel { private Circle circle = new Circle(100, 100, 40); private Rect rect = new Rect(100, 100, 70, 70); private Shape shape = circle; SMPanel() { addMouseMotionListener(new MyMouseMotionListener()); addMouseListener(new MyMouseListener()); setBackground(Shape.BG_COLOR); setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); } public void paintComponent(Graphics g) { super.paintComponent(g); shape.draw(g); } class MyMouseMotionListener extends MouseMotionAdapter { public void mouseDragged(MouseEvent me) { if(!me.isMetaDown()) // Left button pressed? shape.moveTo(getGraphics(), me.getX(), me.getY()); } } class MyMouseListener extends MouseAdapter { public void mousePressed(MouseEvent me) { if(me.isMetaDown()) { // Right button pressed? shape.erase(getGraphics()); if(shape == circle) shape = rect; else shape = circle; shape.moveTo(getGraphics(), me.getX(), me.getY()); } } } } //--------------------------------------------------------------class Shape { static final Color BG_COLOR = Color.LIGHT_GRAY; static final Color FG_COLOR = Color.BLACK; private int x, y; // Reference point Shape(int x, int y) {this.x = x; this.y = y;} 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 98 int getX() { return x; } int getY() { return y; } void display(Graphics g, Color c) {} void draw(Graphics g) {display(g, FG_COLOR);} void erase(Graphics g) {display(g, BG_COLOR);} void moveTo(Graphics g, int x, int y) { erase(g); this.x = x; this.y = y; draw(g); } } //--------------------------------------------------------------class Circle extends Shape { int r; Circle(int x, int y, int r) { super(x, y); this.r = r; } void display(Graphics g, Color c) { g.setColor(c); g.drawOval(getX(), getY(), r+r, r+r); } } //--------------------------------------------------------------class Rect extends Shape { int width, height; Rect(int x, int y, int w, int h) { super(x, y); width = w; height = h; } void display(Graphics g, Color c) { g.setColor(c); g.drawRect(getX(), getY(), width, height); } } Vom Appletviewer wird Bild 6.1 ausgegeben. Aufgabe 6.1 a) Erweitern Sie das Programm so, dass sich zusätzlich zu Kreis und Rechteck auch ein Kreisbogen über das Appletfenster ziehen lässt. Mit der rechten Maustaste soll zyklisch zwischen Kreis, Rechteck und Kreisbogen umgeschaltet werden können. b) Was geschieht, wenn man das Programm um eine weitere Klasse, wie z.B. "Arc", erweitert und dabei die zugehörige Funktion "display" vergisst? Bild 6.1 Applet ShapeAndMouse 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 6.2 99 Abstrakte Klassen und Interfaces Abstrakte Klassen und abstrakte Methoden Eine Klasse wird durch das Schlüsselwort "abstract" zur abstrakten Klasse. Abstrakte Klassen können nur als Oberklassen für andere Klassen verwendet werden. Es ist nicht möglich, ein Objekt einer abstrakten Klasse zu vereinbaren, d.h. die Klasse zu instanziieren. abstract class Shape { ... // Abstakte Klasse Abstrakte Klassen enthalten häufig abstrakte Methoden. Eine abstrakte Methode beginnt ebenfalls mit "abstract". Zusätzlich wird der "Methodenkörper" einschließlich des geschweiften Klammernpaars durch ein Semikolon ersetzt. Wenn nur eine einzige Methode einer Klasse abstrakt ist, so ist die Klasse selber zwangsläufig abstrakt, und ist damit mit dem Schlüsselwort "abstract" zu deklarieren. abstract class Shape { // Abstarkte Klasse ... abstract void display(Graphics g, Color c); // Abstrakte Methode ... Eine von einer abstrakten Klasse abgeleitete Klasse erbt natürlich auch die abstrakten Methoden. − Nur wenn die abgeleitete Klasse für alle geerbten abstrakten Methoden konkrete Implementierungen enthält, ist sie nicht mehr abstrakt, und es lassen sich Objekte vereinbaren. − Wenn die abgeleitete Klasse auch nur für eine der abstrakten Methoden keine konkrete Implementierungen bereitstellt, ist sie ebenfalls wieder abstrakt. Sie muss dann mit dem Schlüsselwort "abstract" beginnen. Beispiel, "Shape" als abstrakte Klasse abstract class Shape { // Abstarkte Klasse static final Color BG_COLOR = Color.LIGHT_GRAY; static final Color FG_COLOR = Color.BLACK; private int x, y; Shape(int x, int y) {this.x = x; this.y = y;} int getX() { return x; } int getY() { return y; } abstract void display(Graphics g, Color c); // Abstrakte Methode void draw(Graphics g) {display(g, FG_COLOR);} void erase(Graphics g) {display(g, BG_COLOR);} void moveTo(Graphics g, int x, int y) { erase(g); this.x = x; this.y = y; draw(g); } } 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 100 Das Beispiel zeigt eine sinnvolle Anwendung. Die Klasse "Shape" aus dem Programm "ShapeAndMouse" ist nun abstrakt. Als Folge kann man von Klassen, die von ihr abgeleitet werden, nur dann Objekte vereinbaren, wenn sie eine konkrete Implementierung der Methode "display()" enthalten. Daher kann nach dieser Änderung der Fehler aus Unterpunkt c) der Aufgabe 6.1 nicht mehr auftreten! Interfaces Ein Interface (Schnittstelle) ist eine spezielle Klasse die ausschließlich abstrakte Methoden und Konstanten enthält. Statt "class" wird das Schlüsselwort "interface" verwendet. Beispiel, Interface "Drawable" Datei "Drawable.java" import java.awt.*; public interface Drawable { public static final Color BG_COLOR = Color.lightGray; public static final Color FG_COLOR = Color.black; public abstract void display(Graphics g, Color c); } Alle abstrakten Methoden eines Interface sind durch Voreinstellung "public" und "abstract". Man sollte das Schlüsselwort "public" aber hinzufügen, man muss es aber nicht. Andere Modifizierer, wie z.B. "private", sind nicht zulässig. Bei der Definition von Konstanten besteht bezüglich der Angabe der Modifikatoren "public", "static" und "final" völlige Freiheit. Sie können angegeben werden, sie können aber auch weggelassen werden. Man kann bekanntlich eine Klasse von einer Oberklasse mit "extends" ableiten, ein Interface wird dagegen mit dem Schlüsselwort "implements" implementiert. Beispiel, "implements" Datei "Shape.java" import java.awt.*; //-------------------------------------------------------------abstract class Shape implements Drawable { private int x, y; int getX() { return x; } int getY() { return y; } Shape(int x, int y) {this.x = x; this.y = y;} // public void display(Graphics g, Color c) void draw(Graphics g) {display(g, FG_COLOR);} void erase(Graphics g) {display(g, BG_COLOR);} ... } 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 101 //-------------------------------------------------------------class Circle extends Shape { ... public void display(Graphics g, Color c) { ... } } //-------------------------------------------------------------class Rect extends Shape { ... public void display(Graphics g, Color c) { ... } } Eine Klasse, die ein Interface implementiert, muss alle abstrakten Methoden des Interface überschreiben. Andernfalls wird sie wie "Shape" zur abstrakten Klasse ("abstract") oder der Compiler meldet einen Fehler. Eine Klasse kann nur von einer Oberklasse abgeleitet werden. Aber eine Klasse kann nicht nur eine, sondern beliebig viele Interfaces implementieren. Auf der Ebene von Interfaces ist also Mehrfachvererbung möglich. Interfaces bieten ein elegantes Mittel zur Prüfung, ob der Anwender bei Parameterübergaben den richtigen Typ übergeben hat. − − − In der Java-Klassenbibliothek werden eine Reihe von Interfaces definiert und implementiert. Im folgenden Abschnitt wird z.B. das Interface "Cloneable" zum "Klonen" von Objekten eingesetzt. Weitergehende Erläuterungen zu Schnittstellen sind z.B. in [1] zu finden. 6.3 Das Interface Cloneable Klonen bedeutet nichts anderes, als eine exakte Kopie von etwas schon Existierendem zu erstellen. Wenn ein Objekt geklont wird, erwartet man, dass man eine Referenz auf ein neues Objekt bekommt, dessen Datenfelder die gleichen Werte haben, wie das Originalobjekt. Die bloße Zuweisung von Referenzen c2 = c1 leistet dies nicht. Um eine echte Kopie von Objekten einer Klasse zu bekommen muss man zwei Dinge tun: − − Das Interface Cloneable implementiert. Die Methode clone() der Klasse Object überschreiben. Diese Teile sind im nachfolgenden Beispiel "Clone 1" durch Fettdruck hervorgehoben. Die Klasse Circle implementiert das Interface Cloneable und enthält eine public-Methode clone(). In clone() wird der Aufruf an die Oberklasse, also an die Klasse Object weitergeleitet. Da die Klasse Circle das Interface Cloneable implementiert, sollte die CloneNotSupportedException nie auftreten. Die Aufgabe der Methode clone() der Klasse Object besteht darin, eine Eins-zuEins-Kopie des Objektes zu erstellen, für das sie aufgerufen wird. Beispiel, Clone 1 class { int int int Circle implements Cloneable x = 0; y = 0; r = 0; Circle(int x, int y, int r) { 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 102 this.x = x; this.y = y; this.r = r; } void display() { System.out.println("x=" + x + ", y=" + y + ", r=" + r); } public Object clone() throws CloneNotSupportedException { // mit super.clone() wird die clone()-Methode // der Klasse Object aufgerufen return super.clone(); } } public class Clone1 { public static void main(String[] args) throws CloneNotSupportedException { Circle c1 = new Circle(10,10,10); Circle c2 = c1; c2.x = 20; c2.y = 20; c2.r = 20; c1.display(); c2.display(); c1 = new Circle(10,10,10); Circle c3 = (Circle) c1.clone(); c3.x = 30; c3.y = 30; c3.r = 30; c1.display(); c3.display(); } } Auf der Konsole: x=20, x=20, x=10, x=30, y=20, y=20, y=10, y=30, r=20 r=20 r=10 r=30 Man erkennt an der Ausgabe, dass nach der bloßen Zuweisung c2 = c1 Änderungen an der Kopie c2 auch das Original c1 verändern, während nach c3 = (Circle) c1.clone() Änderungen an der Kopie c3 das Original c1 unverändert lassen. Es ist noch Folgendes zu bemerken. Man könnte vermuten, dass die Deklaration der clone()-Methode in dem Interface Cloneable enthalten ist. Das ist aber nicht der Fall, das Interface Cloneable hat einen leeren Rumpf: public interface Cloneable { } 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 103 Was gewinnt man dann durch das Implementieren des Interface Cloneable? Das Kompilieren der Klasse Circle wäre auch möglich, wenn die clone()-Methode der Klasse Object in der Klasse Circle nicht überschrieben würde. Aber die clone()-Methode der Klasse Object hat nur den Zugriffsmodifikator protected, so dass der Aufruf c1.clone() in main() nicht möglich ist. Durch das Überschreiben in der Klasse Circle wird die Sichtbarkeit der clone()-Methode auf public angehoben. Das Implementieren des Interface Cloneable ist aber ebenfalls notwendig. Lässt man es weg, so lässt sich das Programm zwar compilieren, der Aufruf super.clone() wirft dann aber zur Laufzeit eine CloneNotSupportedException aus. Das Interface Cloneable ist ein so genanntes Marker-Interface. Die Klasse gibt damit an, dass ihre Objekte kopierbar sind. Man kann auf diese Weise verhindern, dass Objekte von Klassen kopiert werden, für die das gar nicht vorgesehen ist. Im vorausgegangenen Programm "Clone 1" war es ausreichend, in der Methode clone() der Klasse Circle einfach die Methode clone() der Klasse Object aufzurufen. Die Methode clone() der Klasse Object erzeugt dann eine Eins-zu-Eins-Kopie von allen Datenfeldern. Sobald die Datenfelder des Objekts nicht mehr nur aus einfachen Datentypen bestehen, sondern auch Referenzen umfassen, reicht obige Vorgehensweise aber nicht mehr aus. Denn die Methode clone() der Klasse Objekt erzeugt dann nur eine Kopie der jeweiligen Referenz, also lediglich eine flache Kopie. Im nachfolgenden Beispielprogramm "Clone 2" ist das deutlich zu sehen. Eine Membervariable der Klasse Circle ist jetzt von dem Referenztyp Point, welcher in dem Paket java.awt deklariert ist, und Punkte in der Ebene beschreibt. Beispiel, Clone 2 import java.awt.Point; class Circle implements Cloneable { Point p; int r; Circle(int x, int y, int r) { this.p = new Point(x,y); this.r = r; } void display() { System.out.println("x=" + p.x + ", y=" + p.y + ", r=" + r); } public Object clone () throws CloneNotSupportedException { // mit super.clone() wird die clone()-Methode // der Klasse Object aufgerufen. Das erzeugt hier // zunächst lediglich eine flache Kopie return super.clone(); } } public class Clone2 { public static void main(String[] args) 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 104 throws CloneNotSupportedException { Circle c1 = new Circle(10,10,10); Circle c2 = (Circle) c1.clone(); c2.p.x = 30; c2.p.y = 30; c2.r = 30; c1.display(); c2.display(); } } Auf der Konsole: x=30, y=30, r=10 x=30, y=30, r=30 Änderungen an der Instanzvariable p des Objektes c2 verändern auch das Original c1 an der entsprechenden Stelle. Man muss die clone()-Methode der Klasse Circle so erweitern, dass das in der Classe Cirlcle enthaltenen Teilobjekt des Typs Point echt mit kopiert (geklont) wird. Auf diese Weise erhält man dann eine tiefe Kopie des Circle-Objekts. public Object clone () throws CloneNotSupportedException { // mit super.clone() wird die clone()-Methode // der Klasse Object aufgerufen. Das erzeugt // zunächst eine flache Kopie Circle temp = (Circle) super.clone(); // dann Erweiterung zur tiefe Kopie temp.p = (Point) p.clone(); return temp; } Mit der so geänderten clone()-Methode in der Klasse Circle liefert das Programm "Clone 2" auf der Konsole: x=10, y=10, r=10 x=30, y=30, r=30 Das nachfolgende Beispiel "Clone 3" zeigt Beispiel schließlich noch, wie Objekte einer Vererbungshierarchie zu klonen sind. Man hat lediglich in jeder Stufe der Hierarchie zu entscheiden, ob eine tiefe Kopie notwendig ist. Dies ist immer dann erforderlich, wenn die entsprechende Unterklasse Membervariablen hat, die von Referenztyp sind. In diesem Fall müssen die entsprechenden Membervariablen tief kopiert werden. Beispiel, Clone 3 import java.awt.Point; class OnePoint implements Cloneable { Point p1; OnePoint(int x, int y) { 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces this.p1 = new Point(x,y); } void display() { System.out.println("x=" + p1.x + ", y=" + p1.y); } public Object clone () throws CloneNotSupportedException { OnePoint temp = (OnePoint) super.clone(); temp.p1 = (Point) p1.clone(); return temp; } } class TwoPoints extends OnePoint { Point p2; TwoPoints(int x1, int y1, int x2, int y2) { super(x1,y1); p2 = new Point(x2,y2); } void display() { System.out.println("x1=" + p1.x + ", y1=" + p1.y + ", x2=" + p2.x + ", y2=" + p2.y); } public Object clone () throws CloneNotSupportedException { TwoPoints temp = (TwoPoints) super.clone(); temp.p2 = (Point) p2.clone(); return temp; } } public class Clone3 { public static void main(String[] args) throws CloneNotSupportedException { TwoPoints t1 = new TwoPoints(10,10,10,10); TwoPoints t2 = (TwoPoints) t1.clone(); t2.p1.x = 30; t2.p1.y = 30; t2.p2.x = 30; t2.p2.y = 30; t1.display(); t2.display(); } } 105 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 106 Auf der Konsole: x1=10, y1=10, x2=10, y2=10 x1=30, y1=30, x2=30, y2=30 6.4 Das generische Interface Comparable<T> Als Beispiel für ein generisches Interface betrachten wir das Interface Comparable<T>. Dieses Interface kann von jeder Klasse implementiert werden und ermöglicht dann, dass Arrays, bestehend aus Referenzen auf Objekte dieser Klasse durch die Klassenmethode public static void sort (Object[] a) der Klasse Arrays aus dem Packet java.util sortiert werden können. Die Implementierung des generischen Interface Comparable<T> stellt dabei sicher, dass die Objekte eine Vergleichsoperation unterstützen. Das generische Interface Comparable<T> hat den Aufbau public interface Comparable<T> { public int compareTo (T o); } Dabei werden bei dem Aufruf a.compareTo(b) folgende Rückgabewerte erwartet: − − − kleiner Null (z.B. -1), wenn a < b, 0, wenn a == b, größer Null (z.B. +1), wenn a > b ist. Nachfolgend ein Beispiel hierzu, bei dem mechanische Artikel erfasst und sortiert werden. Die Klasse Artikle hat zwei Membervariablen artNr und name und implementiert das Interface Comparable<T>. Beispiel, Vergleich import java.util.*; class Artikel implements Comparable<Artikel> { private Integer artNr; private String name; public Artikel (Integer artNr, String name) { this.artNr = artNr; this.name = name; } public int compareTo (Artikel artikel) { // Die Methode compareToIgnoreCase() ist in der Klasse // String implementiert und liefert einen lexikographischen // Vergleich beider Strings ohne Brücksichtigung von 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 107 // Groß- und Kleinschreibung return name.compareToIgnoreCase (artikel.name); } // Überschreiben der toString()-Methode, um die String// repräsentation des Objektes ausgeben zu können. public String toString() { return (artNr + " " + name); } } public class Vergleich { public static void main (String[] args) { Artikel[] arr = new Artikel [4]; arr [0] = new Artikel (1000, "Mutter"); arr [1] = new Artikel (1001, "Dichtungsring"); arr [2] = new Artikel (1002, "Abstreifring"); arr [3] = new Artikel (1003, "montierbarer Zackenring"); Arrays.sort (arr); for (Artikel artikel : arr) System.out.println (artikel); } } Auf der Konsole 1002 1001 1003 1000 6.5 Abstreifring Dichtungsring montierbarer Zackenring Mutter Ereignisbehandlung Die Ereignisbehandlung basiert auf dem Konzept der Ereignisempfänger (Event Listener). Jedes Objekt, das über Ereignisse benachrichtigt werden kann, ist ein Ereignisempfänger. Eine Ereignisquelle (Event Source) verwaltet eine Liste von Empfängern, die über Ereignisse zu benachrichtigen sind und stellt Methoden zur Verfügung, mit denen sich Empfänger selbst in diese Liste aufnehmen oder sich wieder aus ihr löschen können. Beispiele: addMouseListener(MouseListener); removeMouseListener(MouseListener); Wenn dann ein Ereignis eintritt, benachrichtigt die Quelle alle Empfänger, die sich in ihre Liste eingetragen haben. Ein Empfänger wird durch Aufruf einer dem jeweiligen Ereignis zugeordneten Methode benachrichtigt. Als Folge dieser Vorgehensweise müssen Ereignisempfänger alle zugehörigen Methoden implementieren, ein "MouseListener" zum Beispiel alle fünf in Tabelle 6.1 gegebenen Methoden. Das wird dadurch erzwungen, dass im Interface "MouseListener" alle fünf Methoden als abstrakte Methoden aufgeführt sind. 108 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces Tabelle 6.1 MouseListener und zugehörige Methoden Listener-Interface MouseListener Methoden mouseClicked mouseEntered mouseExited mousePressed mouseReleased Auslösende Aktion Eine Maustaste wurde gedrückt und wieder losgelassen Der Mauszeiger "betritt" die Komponente Der Mauszeiger verlässt die Komponente Eine Maustaste wurde gedrückt Eine Maustaste wurde losgelassen Lösung 1: Implementieren des Interface Im Beispiel "Event1" wird das Interface "MosueListener" implementiert. Das Programm gibt einen Zählerstand aus (Bild 6.2), der inkrementiert wird, wenn der Mauszeiger das Fenster "betritt". Beispiel, Event1 import java.awt.*; import java.awt.event.*; import javax.swing.*; Bild 6.2 Event1 public class Event1 extends JPanel implements MouseListener{ private int count = 0; Event1() { addMouseListener(this); } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("Counter: " + count, 20, 20); } public static void main(String[] args) { JFrame window = new JFrame("Event1"); window.setSize(160, 100); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new Event1()); window.setVisible(true); } public void mouseEntered(MouseEvent me) { count++; repaint(); } public public public public void void void void mouseClicked(MouseEvent me) {} mouseExited(MouseEvent me) {} mousePressed(MouseEvent me) {} mouseReleased(MouseEvent me) {} } addMouseListener(this); Die Klasse "Event1" meldet sich selbst als Ereignisempfänger an. public void mouseClicked(MouseEvent me) {} ... public void mouseReleased(MouseEvent me) {} 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 109 Die leeren Methoden werden eigentlich nicht benötigt, es müssen aber alle fünf Methoden des Interface überschrieben werden. Lösung 2: Adapterklasse und Innere Klasse Mit Adapterklassen (Tabelle 6.2) lässt sich verhindern, dass Ereignisempfänger leere Methoden enthalten, die für die Anwendung eigentlich unnötig sind. In Java gibt es Adapterklassen für alle Listener-Interfaces mit mehr als einer Methode. Tabelle 6.2 Einige Event-Listener und die zugehörigen Adapterklassen Listener-Interface MouseListener Adapterklasse MouseAdapter MouseMotionListener KeyListener MouseMotionAdapter KeyAdapter Methoden mouseClicked, mouseEntered, mouseExit, mousePressed, mouseReleased mouseDragged, mouseMoved keyPressed, keyReleased, keyTyped Eine Adapterklasse ist eine abstrakte Klasse, die das zugehörige Listener-Interface mit leeren Methoden implementiert. Die Methoden sind zwar leer, aber nicht abstrakt. Klassen, die von einer Adapterklasse abgeleitet sind, brauchen deshalb nur noch die in der Anwendung wirklich benötigten Methoden zu implementieren, sie überschreiben für diese Fälle also die leeren Methoden der Adapterklasse. Da in Java Mehrfachvererbung nicht zulässig ist, wird im folgenden Beispiel eine innere Klasse eingesetzt. Die Vorgehensweise ist aus vielen früheren Beispielen schon bekannt. Beispiel, Event2 import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Event2 extends JPanel { private int count = 0; Event2() { addMouseListener(new MyMouseListener()); } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("Counter: " + count, 20, 20); } public static void main(String[] args) { JFrame window = new JFrame("Event2"); window.setSize(160, 100); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new Event2()); window.setVisible(true); } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class MyMouseListener extends MouseAdapter { public void mouseEntered(MouseEvent me) { count++; repaint(); } } } 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces 110 addMouseListener(new MyMouseListener()); Eine Instanz von "MyMouseListener" wird angelegt und als Ereignisempfänger angemeldet. class MyMouseListener extends MouseAdapter { public void mouseEntered(MouseEvent me) { count++; repaint(); } } Die innere Klasse überschreibt die Methode "mouseEntered" der Adapterklasse. Lösung 3: Adapterklasse und anonyme lokale Klasse Man kann eine lokale Klasse deklarieren, ohne ihr einen Namen zu geben. Im Programm "Event3" wird eine derartige anonyme lokale Klasse eingesetzt. Da anonyme Klassen keinen Namen haben, müssen sie gleich bei der Definition instanziiert werden. Beispiel, Event3 import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Event3 extends JPanel { private int count = 0; Event3() { addMouseListener( new MouseAdapter() { public void mouseEntered(MouseEvent me) { count++; repaint(); } } ); } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("Counter: " + count, 20, 20); } public static void main(String[] args) { JFrame window = new JFrame("Event3"); window.setSize(160, 100); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new Event3()); window.setVisible(true); } } Im Konstruktor wird eine Kurzschreibweise eingesetzt, die Bild 6.3 erläutert. Der Compiler erzeugt hier also ein Objekt einer anonymen Klasse, die von "MouseAdapter" abgeleitet worden ist. Zu beachten ist, dass bei anonymen Klassen die Definition der Klasse und ihre Instanziierung in einer Anweisung erfolgen. 6 OOP II : Polymorphie, Abstrakte Klassen und Interfaces addMouseListener(new MyMouseListener()); zusammen mit der Definition class MyMouseListener extends MouseAdapter { public void mouseEntered(MouseEvent me) { count++; repaint(); } } wird ersetzt durch: addMouseListener( new MouseAdapter() { public void mouseEntered(MouseEvent me) { count++; repaint(); } } ); Bild 6.3 Verwendung einer anonymen lokalen Klasse 111 7 Timer und Threads Für Animationen muss eine Folge von einzelnen Bildern ausgegeben werden. Dazu kann man Timer (Zeitgeber) oder Threads (Programmfäden) einsetzen. 7.1 Timer Im folgenden Programm wird der aktuelle Stand eines Zählers fortlaufend ausgegeben. Mit den Maustasten lässt sich der Zähler anhalten und wieder starten. Ein Timer spricht hier alle 500 ms seinen Ereignisempfänger an. Es wird dann die Methode "actionPerformed" des Interface "ActionListener" ausgeführt (Tabelle 7.1). Tabelle 7.1 Action Listener und zugehörige Methode Listener-Interface ActionListener Methode actionPerfomed Auslösende Aktion Ein Ereignis hat stattgefunden, eine Timerverzögerung ist z.B. abgelaufen Beispiel, Counter import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Counter extends JPanel implements ActionListener { private Timer timer; private int number = 0; Counter() { addMouseListener(new MyMouseListener()); // Timer(int delay, ActionListener a), delay in ms timer = new Timer(500, this); timer.setInitialDelay(0); timer.start(); } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("Counter: " + number, 20, 40); } public static void main(String[] args) { JFrame window = new JFrame("Counter"); window.setSize(260, 160); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new Counter()); window.setVisible(true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - public void actionPerformed(ActionEvent e){ number++; repaint(); } 113 7 Timer und Threads class MyMouseListener extends MouseAdapter { public void mousePressed(MouseEvent me) { if(timer.isRunning()) timer.stop(); else timer.start(); } } } Das Programm gibt das Fenster in Bild 7.1 aus. Bild 7.1 Counter timer = new Timer(500, this); Die Anweisung instanziiert einen Timer mit einer Verzögerung von 500 ms und legt das aktuelle Objekt (this) als ActionListener fest. timer.setInitialDelay(0); Mit der Methode lässt sich die Verzögerung für die erste Ausführung von "actionPerformed" angeben. Ohne den Aufruf würde "actionPerformed" nach der an den Konstruktor übergebenen Verzögerung zum ersten Mal ausgeführt, hier also nach 500 ms. if(timer.isRunning()) timer.stop(); else timer.start(); Die Anweisungen ändern den gegenwärtigen Zustand des Timers. Ein laufender Timer (running) wird gestoppt und umgekehrt. Aufgabe 7.1 Schreiben Sie ein Programm "Clock", das die laufende Uhrzeit auf der Konsole und zusätzlich das aktuelle Datum und die laufende Uhrzeit in einem Fenster ausgibt (Bild 7.2). Auf der Konsole ... 5:41:19 5:41:20 5:41:21 ... Bild 7.2 Ausgabe von "Clock" Der folgende Programmausschnitt zeigt, wie man Instanzen der Klassen "GregorianCalendar" und "Date" zur Ausgabe von Zeit und Datum einsetzen kann. ... import java.util.*; ... // For GregorianCalendar and Date // Get a calendar using the default time zone GregorianCalendar cal = new GregorianCalendar(); // Print the current time, GregorianCalendar is a // subclass of Calendar 114 7 Timer und Threads System.out.println(cal.get(Calendar.HOUR) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND)); // Copy date and time to a Date object and display it Date date = cal.getTime(); g.drawString("Current date and time: " + date, 10, 25); ... Im folgenden Programm werden zwei Timer verwendet. − Timer "t1" führt zum Aufruf der Methode "addString", die die Zeichenkette "Two timers" mit zufälliger Position und Farbe ausgibt (Bild 7.3). − Timer "t2" führt zum Aufruf von "repaint" und hat den Aufruf von "paintComponent" zur Folge. Beispiel, TwoTimers import java.awt.*; import java.awt.event.*; import javax.swing.*; Bild 7.3 Zwei Timer public class TwoTimers extends JPanel { private Font font = new Font("Serif", Font.BOLD, 20); TwoTimers() { setBackground(Color.WHITE); Timer t1 = new Timer( 100,new Timer1()); Timer t2 = new Timer(2000,new Timer2()); t1.start(); t2.start(); } public void paintComponent(Graphics g) { super.paintComponent(g); } void addString(Graphics g) { int x = -50 + (int) (Math.random() * (getSize().width + 40)); int y = (int) (Math.random() * (getSize().height + 20)); float hue = (float) Math.random(); // hue = Farbton g.setFont(font); g.setColor(Color.getHSBColor(hue, 1.0F, 1.0F)); g.drawString("Two timers", x, y); } public static void main(String[] args) { JFrame window = new JFrame("Two timers"); window.setSize(240, 200); window.setLocation(100, 100); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.getContentPane().add(new TwoTimers()); window.setVisible(true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class Timer1 implements ActionListener { 115 7 Timer und Threads public void actionPerformed(ActionEvent e) { addString(getGraphics()); } } class Timer2 implements ActionListener { public void actionPerformed(ActionEvent e){ repaint(); } } } 7.2 Threads und das Interface Runnable Ein Thread ist ein "Programmfaden". Multithreading ermöglicht es, mehrere Programmfäden gleichzeitig oder quasi gleichzeitig auszuführen. − In Einprozessorsystemen ist nur quasi gleichzeitige Abarbeitung möglich. Das Betriebssystem teilt dazu den Threads nacheinander den Prozessor in Zeitscheiben zu. − In Mehrprozessorsystemen können dagegen mehrere Threads tatsächlich gleichzeitig ablaufen. Threads werden durch Erweitern der Klasse "Thread" oder durch Implementieren des Interface "Runnable" realisiert. Beispiel, Thread1 public class Thread1 public static void MyThread alpha = MyThread beta = { main(String[] args) { new MyThread(" Thread Alpha"); new MyThread(" Thread Beta"); alpha.start(); beta.start(); } } class MyThread extends Thread { private String s; MyThread(String s) { this.s = s; } public void run() { for(int i = 0; i < 5; i++) { System.out.println(i + s); try { // Sleep for 1 ms sleep(1); } catch (InterruptedException e) {} } } Auf der Konsole 0 0 1 1 2 2 3 3 4 4 Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Alpha Beta Alpha Beta Beta Alpha Alpha Beta Alpha Beta } alpha.start(); beta.start(); Der Programmfäden "alpha" und "beta" werden gestartet und laufen nun quasi gleichzeitig ab. 7 Timer und Threads 116 class MyThread extends Thread { ... public void run() { ... Die Methode "run" wird beim Start eines Thread aufgerufen. Sie enthält den Programmcode des Thread. Ein Thread beendet sich selbst, wenn "run" abgearbeitet worden ist. try { sleep(1); } catch (InterruptedException e) {} Mit der Methode "sleep" kann man einen Programmfaden für eine vorgebbare Anzahl von Millisekunden pausieren lassen. Die Konsolenausgabe zeigt, dass die beiden Programmfäden tatsächlich quasi gleichzeitig ablaufen. Man erkennt außerdem, dass das Ende von "main" nicht zum Stopp der beiden Threads führt. Eine Java-Applikation ist erst dann beendet, wenn der letzte Thread abgeschlossen worden ist4. Aufgabe 7.2 Schreiben Sie ein Programm "Thread2". Das Programm soll sich von "Thread1" dadurch unterscheiden, dass nur ein zusätzlicher Thread gestartet wird und die Konsolenausgaben in "main" und in der Methode "run" des Threads dann quasi gleichzeitig erfolgen. Da Java Mehrfachvererbung nicht unterstützt, kann in dem nachfolgenden Applet die von JPanel abgeleitete Klasse ACPanel nicht zusätzlich noch von "Thread" abgeleitet werden. Stattdessen muss das Interface "Runnable" implementiert werden, das als einzige Deklaration "public abstract void run()" enthält. Die abstrakte Methode "run" ist dann zu überschreiben. Beispiel, AppletCounter2 import java.awt.*; import javax.swing.*; public class AppletCounter2 extends JApplet { private Thread countThread = null; private ACPanel acPanel; public void init() { acPanel = new ACPanel(); getContentPane().add(acPanel); } public void start() { countThread = new Thread(acPanel); countThread.start(); } 4 Sogenannte Hintergrund-Threads oder Dämonen werden beim Beenden von "main" gestoppt, vgl. z.B. [1]. 7 Timer und Threads 117 public void stop() { acPanel.beenden(); } public void destroy() { acPanel.beenden(); } } class ACPanel extends JPanel implements Runnable { private int num = 0; private boolean running = false; public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("Value: " + num++, 60, 40); } public void run() { num = 0; running = true; while(running == true) { repaint(); try { Bild 7.4 AppletCounter1 Thread.sleep(500); } catch(InterruptedException e) { } } System.out.println("Thread Terminated!"); } public void beenden() { running = false; } } Zum Abschluss noch ein typisches Beispiel für die Verwendung von Threads. In dem nachfolgenden Applet läuft im Hintergrund eine Schrift durchs Bild, während man parallel dazu im Vordergrund mit der Maus einen Kreis immer wieder neu positionieren kann. Wenn das Applet unsichtbar wird oder geschlossen wird, wird der Thread mit der Methode interrupt() gestoppt. Beispiel, MouseAndThread02 import java.awt.*; import java.awt.event.*; // For MouseEvent import javax.swing.*; public class MouseAndThread02 extends JApplet { Thread laufThread; MouseAndThreadPanel mtPanel; 7 Timer und Threads public void init() { mtPanel = new MouseAndThreadPanel(); getContentPane().add(mtPanel); } public void start() { laufThread = new Thread(mtPanel); laufThread.start(); } public void stop() { laufThread.interrupt(); } public void destroy() { laufThread.interrupt(); } } //---------------------------------------------------class MouseAndThreadPanel extends JPanel implements Runnable { private static final int R = 40; private static final Color BG_COLOR = Color.WHITE; private static final Color FG_COLOR = Color.GREEN; private static final Color TXT_COLOR = Color.RED; private int x = 40, y = 40; // Center of circle // Variablen für Laufschrift String text; String hello; int lengthText, xpos, ypos, xmax; Font fontText; Font fontHello; int fs; // // // // Laenge des x-Position y-Position Breite des Textes des Textes des Textes Fensters MouseAndThreadPanel() { // Add MyMouseListener and MouseMotionListener // to receive mouse motion events addMouseListener(new MyMouseListener()); addMouseMotionListener(new MyMouseMotionListener()); setBackground(BG_COLOR); text = " ... Java ... "; fs = 28; fontText = new Font("SansSerif", Font.BOLD, fs); FontMetrics fm = getFontMetrics(fontText); lengthText = fm.stringWidth(text); ypos = (int) (fs*1.2) ; } 118 7 Timer und Threads public void paintComponent(Graphics g) { super.paintComponent(g); // Update if window size has been changed if (xmax != getSize().width) { xmax = getSize().width; } g.setColor(TXT_COLOR); g.setFont(fontText); g.drawString(text, xpos , ypos); draw(g); } void display(Graphics g, Color c) { g.setColor(c); g.drawOval(x-R, y-R, R+R, R+R); } void draw(Graphics g) { display(g, FG_COLOR); } public void run() { Graphics g = getGraphics(); g.setFont(fontText); xmax = getSize().width; xpos = xmax; while(true) { xpos--; if(xpos < -lengthText) { xpos = xmax ; } repaint(); try { Thread.sleep(20); } catch(InterruptedException e) { System.out.println("Thread Interrupted!"); return; } } } //---------------------------------------------------class MyMouseListener extends MouseAdapter { public void mousePressed(MouseEvent me) { x = me.getX(); y = me.getY(); repaint(); } } //---------------------------------------------------class MyMouseMotionListener extends MouseMotionAdapter { public void mouseDragged(MouseEvent me) { x = me.getX(); y = me.getY(); repaint(); } } } 119 7 Timer und Threads 120 Die Ausgabe dieses Applets ist in der nachfolgenden Abbildung 7.5 zu sehen. Weitergehende Erläuterungen zu Threads, insbesondere zu Threadgruppen, Threadprioritäten und Synchronisationsproblemen, sind z.B. in [1] zu finden. Bild 7.5 Applet MouseAndThread02 Aufgabe 7.3 Schreiben Sie ein Applet "Lines", das wie ein Bildschirmschoner ein farbiges Linienbündel ausgibt (Bild 7.5). Das Linienbündel läuft im Uhrzeigersinn auf einer Spiralbahn um ein Zentrum in der Fenstermitte. Der Umlaufradius schrumpft bis zu einer Untergrenze und wächst dann wieder bis zu einer Obergrenze. Bild 7.6 Applet Lines 7.3 Übungsaufgaben Übungsaufgabe 7.1 Schreiben Sie ein Applet "Laufschrift", das eine Textnachricht als Laufschrift auf dem Bildschirm darstellt. Lesen Sie den auszugebenden Text aus der HTML Datei ein. a) Verwenden Sie in Ihrer Lösung einen Timer. b) Verwenden Sie in Ihrer Lösung das Interface Runnable. 8 Swing-Komponenten In diesem Abschnitt wird eine Auswahl von Swing-Komponenten vorgestellt. Mit diesen Komponenten lassen sich übersichtliche und intuitiv einsetzbare Schnittstellen zum Anwender programmieren. 8.1 Übersicht über die Swing-Komponenten Die folgende Übersicht stammt aus dem Java Tutorial [4]. Dort sind auch weitergehende Erläuterungen zu allen Swing-Komponenten zu finden. Top-Level Containers Applet Frame Dialog General-Purpose Containers Panel Scroll pane Tabbed pane Tool bar Special-Purpose Containers Internal frame Layered pane Split pane 122 8 Swing-Komponenten Root pane Basic Controls Buttons Combo box Text fields Menu Slider List Uneditable Information Displays Label Progress bar Editable Displays of Formatted Information Color chooser File chooser Tool tip 123 8 Swing-Komponenten Table Text Tree In der Regel wird man die genaue Platzierung der Komponenten im jeweiligen Fenster einem "Layout-Manger" überlassen. Ein Layout-Manager erhält Vorgaben und passt dann Größe und genaue Platzierung der Dialogelemente der jeweiligen Fenstergröße an. Man kann für die Dialogelemente aber auch feste Positionen und Größen angeben. 8.2 JButton und JLabel Ein Button (JButton) ist eine beschriftete Schaltfläche. Wenn man einen Button anklickt, wird die aus dem vorausgehenden Abschnitt schon bekannte Methode "ActionPerformed" des Interface "ActionListener" ausgeführt. Ein Label (JLabel) dient zur Ausgabe eines Textes und reagiert nicht auf Benutzereingaben. Der ausgegebene Text lässt sich während der Programmausführung ändern. Im folgenden Programm wird kein Layout-Manager eingesetzt, die Komponenten werden also fest platziert. Beispiel, Layout1 import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.border.*; public class Layout1 extends JFrame implements ActionListener { final private String[] BTXT = {"Pass", "Back", "Back to OS"}; final private JLabel lbl = new JLabel("Start", JLabel.CENTER); Layout1() { Container con = getContentPane(); // public void setLayout(LayoutManager mgr) // mgr -> layout manager or null con.setLayout(null); // public void setBounds(int topLeftX, int topLeftY, // int width, int height) lbl.setBounds(12, 8, 248, 40); // public void setBorder(Border border) // border -> border to be drawn for this component lbl.setBorder(new LineBorder(Color.BLACK)); con.add(lbl); for(int i = 0; i < BTXT.length; i++) { JButton btn = new JButton(BTXT[i]); btn.setBounds(10+85*i, 55, 80, 25); 8 Swing-Komponenten 124 con.add(btn); btn.addActionListener(this); } } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd == BTXT[2]) System.exit(0); lbl.setText(cmd); } public static void main(String[] args) { JFrame window = new Layout1(); window.setTitle("Layout 1"); window.setSize(280, 110); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.1 zeigt das vom Programm ausgegebene Fenster. Der Button "Back to OS" beendet das Programm. Die beiden anderen Schaltflächen führen hier nur dazu, dass die jeweilige Beschriftung im Label ausgegeben wird. Da die Komponenten vorgegebene Koordinaten haben, passen sich ihre Positionen Änderungen der Fenstergröße nicht an. Bild 8.1 Feste Koordinaten Die Vorgabe fester Koordinaten ist in der Regel keine gute Idee. Man sollte die Platzierung vielmehr einem Layout-Manager überlassen. 8.3 Layout-Manager Es stehen sechs Layout-Manager mit unterschiedlichen Platzierungsstrategien zur Verfügung. Im Folgenden werden vier der Layout-Manager erläutert, eine vollständigere Beschreibung ist z. B. in [1] zu finden. − FlowLayout ordnet Komponenten nebeneinander in einer Zeile an. − BorderLayout verteilt die Komponenten auf die vier Randbereiche und den Mittelbereich eines Fensters. − GridLayout positioniert die Komponenten in einem rechteckigen Gitter, dessen Zeilenund Spaltenzahl frei wählbar ist. − BoxLayout ordnet die Komponenten in einer horizontalen oder in einer vertikalen Leiste an. FlowLayout FlowLayout platziert die Dialogelemente ähnlich zu Textzeilen. Wenn keine weiteren Komponenten in die Zeile passen, wird mit der nächsten Zeile fortgefahren. In der Voreinstellung werden die Komponenten in den Zeilen zentriert ausgegeben. 125 8 Swing-Komponenten Beispiel, Layout2 import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.border.*; public class Layout2 extends JFrame implements ActionListener { final private String[] BTXT = {"Pass", "Back", "Back to OS"}; final private JLabel lbl = new JLabel("Start", JLabel.CENTER); Layout2() { Container con = getContentPane(); // FlowLayout() -> centered alignment and a default // 5-unit horizontal and vertical gap // FlowLayout(int align) -> align = FlowLayout.LEFT, RIGHT // or CENTER, 5-unit horizontal and vertical gap // FlowLayout(int align, int hgap, int vgap) con.setLayout(new FlowLayout()); lbl.setBorder(new LineBorder(Color.BLACK)); con.add(lbl); for(int i = 0; i < BTXT.length; i++) { JButton btn = new JButton(BTXT[i]); con.add(btn); btn.addActionListener(this); } } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd == BTXT[2]) System.exit(0); lbl.setText(cmd); } public static void main(String[] args) { JFrame window = new Layout2(); window.setTitle("Layout 2"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.2 zeigt das vom Programm ausgegebene Fenster. Man erkennt, dass sich die Größe der Komponenten nach dem Platzbedarf der Beschriftung richtet. Bild 8.2 FlowLayout Man kann die bevorzugte Komponentengröße vorgeben. Der folgende Programmausschnitt zeigt die Vorgehensweise. ... Layout2() { Container con = getContentPane(); 8 Swing-Komponenten 126 con.setLayout(new FlowLayout()); lbl.setPreferredSize(new Dimension(248, 40)); lbl.setBorder(new LineBorder(Color.BLACK)); con.add(lbl); for(int i = 0; i < BTXT.length; i++) { JButton btn = new JButton(BTXT[i]); btn.setPreferredSize(new Dimension(80, 25)); con.add(btn); btn.addActionListener(this); } } ... public static void main(String[] args) { JFrame window = new Layout2(); window.setTitle("Layout 2"); window.setSize(280, 110); window.setResizable(false); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.3 zeigt das Fenster, das nach diesen Änderungen ausgegeben wird. Der Aufruf von "setResizable(false)" führt dazu, dass sich die Fenstergröße nicht mehr ändern lässt. Bild 8.3 Vorgegebene Größe BorderLayout Mit BorderLayout lassen sich Dialogelemente an den vier Rändern ("BorderLayout. EAST", "BorderLayout.NORTH", "BorderLayout.WEST", "BorderLayout.SOUTH") und in der Mitte ("BorderLayout.CENTER") platzieren. Beispiel, Layout3 import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Layout3 extends JFrame implements ActionListener { final private String[] BTXT = {"Pass", "Back", "Back to OS"}; final private JLabel lbl = new JLabel(" Start ", JLabel.CENTER); final private String [] BLOC = {BorderLayout.WEST, BorderLayout.EAST, BorderLayout.SOUTH}; Layout3() { Container con = getContentPane(); // BorderLayout() -> no gaps between components 127 8 Swing-Komponenten // BorderLayout(int hgap, int vgap) -> layout with // the specified gaps between components con.setLayout(new BorderLayout()); con.add(lbl, BorderLayout.CENTER); for(int i = 0; i < BTXT.length; i++) { JButton btn = new JButton(BTXT[i]); con.add(btn, BLOC[i]); btn.addActionListener(this); } } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd == BTXT[2]) System.exit(0); lbl.setText(cmd); } public static void main(String[] args) { JFrame window = new Layout3(); window.setTitle("Layout 3"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.4 zeigt das vom Programm ausgegebene Fenster. Wenn das Fenster vergrößert oder verkleinert wird, passen sich die Komponenten dieser Änderung an. Komponenten an den vertikalen Rändern ändern dazu ihre Höhe, Komponenten an den horizontalen Rändern ihre Breite. Ist eine Komponente in der Mitte platziert, ändert sie sowohl Höhe als auch Breite. Bild 8.4 BorderLayout GridLayout Mit GridLayout lassen sich Komponenten in einem Raster mit wählbarer Spalten- und Zeilenzahl anordnen. Alle Rasterplätze haben dabei ein- und dieselbe Größe. Die Komponenten werden in der obersten Zeile von links beginnend der Reihe nach platziert, dann in der zweiten Zeile, usw. Beispiel, Layout4 import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Layout4 extends JFrame implements ActionListener { final private String[] BTXT = {"Pass", "Back", "Back to OS"}; final private JLabel lbl = new JLabel("Start", JLabel.CENTER); Layout4() { Container con = getContentPane(); 128 8 Swing-Komponenten // GridLayout() -> grid layout with a default of one // column per component, in a single row // GridLayout(int rows, int cols) -> grid layout with the // specified number of rows and columns // GridLayout(int rows, int cols, int hgap, int vgap) -> // Grid layout with the specified // number of rows and columns and gaps con.setLayout(new GridLayout(2,2)); con.add(lbl); for(int i = 0; i < BTXT.length; i++) { JButton btn = new JButton(BTXT[i]); con.add(btn); btn.addActionListener(this); } } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd == BTXT[2]) System.exit(0); lbl.setText(cmd); } public static void main(String[] args) { JFrame window = new Layout4(); window.setTitle("Layout 4"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.5 zeigt das vom Programm ausgegebene Fenster. Wenn das Fenster vergrößert oder verkleinert wird, passen sich die Komponenten diesen Änderungen an. Bild 8.5 GridLayout Schachteln von Layout-Managern Zum Schachteln von Layout-Managern setzt man Objekte der Klasse "JPanel" ein. In diesen "Tafeln" platziert man Sublayouts, die dann zum Gesamtlayout zusammengefügt werden. Das folgende Beispiel zeigt die Vorgehensweise. Beispiel, Layout5 import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Layout5 extends JFrame implements ActionListener { final private JLabel lbl = new JLabel(" Start ", JLabel.CENTER); 129 8 Swing-Komponenten final private int BNUM = 3; Layout5() { JPanel pnl1 = new JPanel(new GridLayout(BNUM, 1)); for(int i = 0; i < BNUM; i++) { JButton btn = new JButton("BV" + i); pnl1.add(btn); btn.addActionListener(this); } JPanel pnl2 = new JPanel(new GridLayout(1, BNUM)); for(int i = 0; i < BNUM; i++) { JButton btn = new JButton("BH" + (i+BNUM)); pnl2.add(btn); btn.addActionListener(this); } Container con = getContentPane(); // con.setLayout(new BorderLayout()); con.add(BorderLayout.CENTER, lbl); con.add(BorderLayout.WEST, pnl1); con.add(BorderLayout.EAST, pnl2); // Default layout } public void actionPerformed(ActionEvent e) { lbl.setText(e.getActionCommand()); } public static void main(String[] args) { JFrame window = new Layout5(); window.setTitle("Layout 5"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.6 zeigt das vom Programm ausgegebene Fenster. Auch hier passen sich die Komponenten Änderungen der Fenstergröße an. Bild 8.6 Geschachtelte Layout-Manager Aufgabe 8.1 Schreiben Sie ein Programm "Layout6", das das in Bild 8.7 gegebene Fenster ausgibt. Die Komponenten sollen sich Änderungen der Fenstergröße anpassen. Bild 8.7 Fenster zur Aufgabe BoxLayout BoxLayout ordnet die Komponenten in einer horizontalen oder in einer vertikalen Leiste an. Dir Breite von Schaltflächen richtet sich dabei nach ihrer Beschriftung. 8 Swing-Komponenten Beispiel, Layout7 import java.awt.*; import javax.swing.*; public class Layout7 extends JFrame { Layout7() { JPanel pnlv = new JPanel(); // BoxLayout(Container target, int axis) // target - the container that needs to be laid out // axis - the axis to lay out components along. Can // be BoxLayout.X_AXIS or BoxLayout.Y_AXIS pnlv.setLayout(new BoxLayout(pnlv, BoxLayout.Y_AXIS)); for(int i = 0; i < 4; i++) pnlv.add(new JButton("BV " + i)); JPanel pnlh = new JPanel(); pnlh.setLayout(new BoxLayout(pnlh, BoxLayout.X_AXIS)); pnlh.add(new JButton("--- BH 0 ---")); for(int i = 1; i < 4; i++) pnlh.add(new JButton("BH " + i)); Container cp = getContentPane(); cp.add(BorderLayout.EAST, pnlv); cp.add(BorderLayout.SOUTH, pnlh); } public static void main(String[] args) { JFrame window = new Layout7(); window.setTitle("Layout 7"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.8 zeigt das vom Programm ausgegebene Fenster. Swing enthält eine Container-Klasse "Box", die "BoxLayout" verwendet. Mit statischen Methoden dieser Klasse lässt sich die Anordnung der Komponenten beeinflussen. ... Layout7() { Box bv = Box.createVerticalBox(); for(int i = 0; i < 4; i++) { bv.add(new JButton("BV " + i)); bv.add(Box.createVerticalStrut(4)); } Box bh = Box.createHorizontalBox(); bh.add(new JButton("--- BH 0 ---")); for(int i = 1; i < 4; i++) { Bild 8.8 BoxLayout 130 8 Swing-Komponenten 131 bh.add(Box.createHorizontalStrut(6)); bh.add(new JButton("BH " + i)); } Container cp = getContentPane(); cp.add(BorderLayout.EAST, bv); cp.add(BorderLayout.SOUTH, bh); ... Bild 8.9 zeigt das nach dieser Änderung ausgegebene Fenster. Die statischen Methoden "create...Strut" (Strebe, Stütze) erzeugen unsichtbare Komponenten mit vorgebbaren Abmessungen, mit denen sich der Abstand zwischen den Komponenten beeinflussen lässt. Eine vollständigere Beschreibung der Methoden aus "Box" ist z.B. in [2] zu finden. 8.4 Bild 8.9 BoxLayout mit Abständen JDialog und JOptionPane Mit "JDialog" und "JOptionPane" lassen sich Fenster ausgeben, die von einem anderen Fenster abhängig sind. JDialog Im folgenden Programm wird ein modales Dialogfenster eingesetzt. Man nennt ein Dialogfenster "modal", wenn es alle anderen Eingaben einer Anwendung blockiert, solange es nicht geschlossen worden ist. Beispiel, Dialog1 import java.awt.*; import java.awt.event.*; import javax.swing.*; class MyDialog extends JDialog implements ActionListener { MyDialog(JFrame parent) { //JDialog(Dialog owner, String title, boolean modal) super(parent, "My dialog", true); Container cnt = getContentPane(); cnt.setLayout(new FlowLayout()); cnt.add(new JLabel("Please press OK")); JButton btn = new JButton("OK"); btn.addActionListener(this); cnt.add(btn); setDefaultCloseOperation(DISPOSE_ON_CLOSE); Rectangle rct = new Rectangle(parent.getBounds()); setBounds(rct.x+40, rct.y+30, 160, 110); } 132 8 Swing-Komponenten public void actionPerformed(ActionEvent e) { // Disposes the Dialog and then causes setVisible(true) in // the calling program to return dispose(); } } public class Dialog1 extends JFrame implements ActionListener { Dialog1() { getContentPane().setLayout(new BorderLayout()); JButton btn = new JButton("Show Dialog"); getContentPane().add(BorderLayout.SOUTH, btn); btn.addActionListener(this); } public void actionPerformed(ActionEvent e) { MyDialog dlg = new MyDialog(this); // Makes the Dialog visible dlg.setVisible(true); } public static void main(String[] args) { JFrame window = new Dialog1(); window.setTitle("Dialog 1"); window.setSize(300, 200); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.10 zeigt die vom Programm ausgegebenen Fenster. MyDialog dlg = new MyDialog(this); dlg.setVisible(true); Der Aufruf von "setVisible(true)" aktiviert das Dialogfenster und führt dazu, dass es auf dem Bildschirm sichtbar wird. dispose(); Das Dialogfenster wird "beseitigt". Bei modalen Dialogfenstern kehrt "setVisible(true)" erst dann in das aufrufende Programm zurück. Bild 8.10 Modales Dialogfenster Aufgabe 8.2 Schreiben Sie ein Programm "TicTacToe", das die in Bild 8.11 gegebenen Fenster ausgibt. Das Programm soll es zwei Personen ermöglichen, "TicTacToe" zu spielen. Bild 8.11 TicTacToe 8 Swing-Komponenten 133 JOptionPane Mit "JOptionPane" kann man mit geringem Aufwand kurze standardisierte Meldungen oder Eingaben programmieren. Dabei lassen sich folgende Aufgabenbereiche abdecken: − Mitteilung mit der Schaltfläche "Ok". − Eingabe mit den Schaltflächen "Ok" und "Cancel". − Bestätigung (Mitteilung oder Frage) mit unterschiedlichen Schaltflächen. − Auswahl für beliebige Daten mit Schaltflächen. Hier wird nur auf die ersten beiden Aufgabenbereiche eingegangen. Vollständigere Erläuterungen sind z.B. in [1] zu finden. Mitteilung (message) Mit der statischen Methode "showMessageDialog" kann man eine Mitteilung ausgeben. Das Mitteilungsfenster ist modal. Solange es nicht beendet wird, blockiert es also alle anderen Eingaben einer Anwendung. Das Fenster enthält eine Schaltfläche "Ok". public static void showMessageDialog(Component parent, Object message, String title, int messageType) parent: message: title: messageType: "Elternfenster", in dem das Mitteilungsfenster zentriert ausgegeben wird. Häufig ein String, der im Fenster als Mitteilung ausgegeben wird. Andere Objekte sind ebenfalls möglich, wie z.B. ein Icon. Der String wird in der Kopfleiste ausgegeben. Typ der Mitteilung, zulässig sind PLAIN_MESSAGE, ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE oder QUESTION_MESSAGE. Beispiel, ShowMessageBox import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ShowMessageBox extends JFrame implements ActionListener { ShowMessageBox() { JButton btn = new JButton("Show error window"); btn.addActionListener(this); getContentPane().add(BorderLayout.SOUTH, btn); } public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(this, "')' expected in line 12, column 34", "Fatal error", JOptionPane.ERROR_MESSAGE); } public static void main(String[] args) { ShowMessageBox window = new ShowMessageBox(); 8 Swing-Komponenten 134 window.setTitle("MessageBox Demo"); window.setSize(360, 200); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.12 zeigt das vom Programm ausgegebene Mitteilungsfenster. Mit der Ausnahme der "PLAIN_MESSAGE" enthalten alle Fenster Icons, die auf die Art der jeweiligen Meldung hinweisen. Eingabe (input) Bild 8.12 Mitteilungsfenster Die statische Methode "showInputDialog" gibt ein Fenster mit Eingabefeld und den Schaltflächen "Ok" und "Cancel" aus. Das Fenster ist modal. public static String showInputDialog(Component parent, Object message, String title, int messageType) Die Bedeutung der vier Parameter unterscheidet sich nicht von "showMessageDialog". Rückgabewert: Eingegebener Text oder "null", wenn die Eingabe abgebrochen worden ist. Beispiel, ShowInputBox import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ShowInputBox extends JFrame implements ActionListener { ShowInputBox() { JButton btn = new JButton("Show input box"); btn.addActionListener(this); getContentPane().add(BorderLayout.SOUTH, btn); } public void actionPerformed(ActionEvent e) { String inp = JOptionPane.showInputDialog(this, "Please enter your name", "Input box", JOptionPane.QUESTION_MESSAGE); String mss = (inp != null)? "Your name is " + inp: "Name not entered"; JOptionPane.showMessageDialog(this, mss, "Message box", JOptionPane.INFORMATION_MESSAGE); } public static void main(String[] args) { ShowInputBox window = new ShowInputBox(); window.setTitle("InputBox Demo"); window.setSize(360, 200); 8 Swing-Komponenten 135 window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.13 zeigt das vom Programm ausgegebene Eingabefenster. 8.5 JCheckBox und JRadioButton Bild 8.13 Eingabefenster Die Klassen "JRadioButton" und "JCheckBox" stellen "Schalter" zur Verfügung, die vom Anwender ein- und ausgeschaltet werden können. Instanzen von "JRadioButton" werden in aller Regel zu Gruppen zusammengefasst, in denen immer genau einer der Schalter aktiviert sein kann. Beispiel, RButtonCBox import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.border.*; public class RButtonCBox extends JFrame implements ActionListener{ final private String TXT[] = {"zero", "one", "two"}; private JRadioButton rb[] = new JRadioButton[TXT.length]; private JCheckBox cb[] = new JCheckBox[TXT.length]; RButtonCBox() { Box brb = Box.createVerticalBox(); brb.setBorder(new LineBorder(Color.GRAY)); ButtonGroup group = new ButtonGroup(); for(int i = 0; i < TXT.length; i++) { rb[i] = new JRadioButton("Button " + TXT[i], false); rb[i].addActionListener(this); brb.add(rb[i]); group.add(rb[i]); } rb[0].setSelected(true); Box bcb = Box.createVerticalBox(); bcb.setBorder(new LineBorder(Color.GRAY)); for(int i = 0; i < TXT.length; i++) { cb[i] = new JCheckBox("Box " + TXT[i], false); cb[i].addActionListener(this); bcb.add(cb[i]); } Container con = getContentPane(); con.setLayout(new GridLayout(1, 2, 2, 2)); con.add(brb); 136 8 Swing-Komponenten con.add(bcb); } public void actionPerformed(ActionEvent e) { String srb = "", scb = ""; for(int i = 0; i < TXT.length; i++) { if(rb[i].isSelected()) srb += i; if(cb[i].isSelected()) scb += i + " "; } System.out.println("RadioButton: " + srb + " } CheckBox: " + scb); public static void main(String[] args) { JFrame window = new RButtonCBox(); window.setTitle("RButton CBox"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.14 zeigt das Programmfenster. Auf der Konsole wird der jeweils aktuelle Zustand der sechs Schalter ausgegeben. Bild 8.14 JRadioButton und JCheckBox Aufgabe 8.3 Schreiben Sie ein Programm, das das in Bild 8.15 gegebene Fenster ausgibt. Wenn der Benutzer die Schaltfläche "Ok" betätigt, soll der aktuelle Zustand der elf Schalter auf der Konsole ausgegeben werden. Bild 8.15 JRadioButton und JCheckBox 8.6 JTextField, JTextArea und JScrollPane "JTextField" stellt ein einzeiliges und "JTextArea" ein mehrzeiliges Feld zur Anzeige und Eingabe von Texten zur Verfügung. "JScrollPane" erzeugt bei Bedarf horizontale und vertikale "Schieber". JTextField Das folgende Programm zeigt die Vorgehensweise bei der Eingabe mit "JTextField". Wenn der Benutzer den Textcursor (Caret) im Eingabefeld bewegt, wird die Methode "CaretUpdate" 137 8 Swing-Komponenten aus "CaretListener" aufgerufen. Die Return-Taste führt zur Ausführung von "ActionPerformed" aus "ActionListener". Beispiel, TextFieldDemo import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; // For "CaretEvent" public class TextFieldDemo extends JFrame implements ActionListener, CaretListener { // JTextField(int columns) or // JTextField(String text) or // JTextField(String text, int columns) private JTextField tf = new JTextField(16); TextFieldDemo() { Box bh = Box.createHorizontalBox(); bh.add(new JLabel(" Name: ", JLabel.LEFT)); tf.addActionListener(this); tf.addCaretListener(this); bh.add(tf); Container con = getContentPane(); con.setLayout(new GridLayout(2, 1, 4, 4)); con.add(new JLabel(" Type and press Return ")); con.add(bh); } public void actionPerformed(ActionEvent e) { System.out.println("Command: " + e.getActionCommand()); System.out.println("Text: " + tf.getText()); } public void caretUpdate(CaretEvent e) { System.out.println("Pos: " + e.getDot() + " } " + tf.getText()); public static void main(String[] args) { JFrame window = new TextFieldDemo(); window.setTitle("Text Field Demo"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Auf der Konsole Bild 8.16 zeigt das einzeilige Eingabefeld und die zugehörige Konsolenausgabe. Bild 8.16 JTextField und Konsolenausgabe Pos: 1 T Pos: 2 Te Pos: 3 Tes Pos: 4 Test Command: Test Text: Test 138 8 Swing-Komponenten Aufgabe 8.4 Schreiben Sie ein Programm, das das in Bild 8.17 gegebene Fenster ausgibt. Wenn der Benutzer die Schaltfläche "Ok" betätigt, sollen die eingetragenen Daten zur Kontrolle auf der Konsole ausgegeben werden. Bild 8.17 Name und Adresse Aufgabe 8.5 Schreiben Sie ein Programm, das den einfachen Rechner aus Bild 8.18 ausgibt. Wenn man eine Taste betätigt, soll das Programm hier nur die zugehörige Beschriftung im Rechnerdisplay ausgeben. (Der Rechner wird in einer Übungsaufgabe zu diesem Abschnitt vervollständigt.) JTextArea und JScrollPane Bild 8.18 Einfacher Rechner "JTextArea" stellt ein mehrzeiliges Feld zur Anzeige und Eingabe von Texten zur Verfügung. Die Klasse ist aber nicht in der Lage, den Text zu scrollen, wenn er nicht vollständig in das Ausgabefeld passt. Für diesen Zweck ist "JScrollPane" vorgesehen. Im folgenden Beispiel wird ein einfacher Editor programmiert. Das Programm zeigt die Vorgehensweise beim Einsatz von "JTextArea" und "JScrollPane". Beispiel, BasicEditor import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BasicEditor extends JFrame implements ActionListener { final static String S[] = {"Duplicate Text", "Clear Text"}; private JButton b[] = {new JButton(S[0]), new JButton(S[1])}; // JTextArea(String text, int rows, int columns) private JTextArea ta = new JTextArea( "Das ist ein Test des einfachen Editors. ", 8, 26); BasicEditor () { ta.setFont(new Font("Arial", Font.ITALIC, 12)); // Long lines will be wrapped, default: false I ta.setLineWrap(true) ; // Lines will be wrapped at word boundaries, default: false 8 Swing-Komponenten 139 ta.setWrapStyleWord(true); JPanel panel = new JPanel(new GridLayout(1, 2)); for(int i = 0; i < b.length; i++) { panel.add(b[i]); b[i].addActionListener(this); } getContentPane().add(BorderLayout.CENTER, new JScrollPane(ta)); getContentPane().add(BorderLayout.SOUTH, panel); } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd.equals(S[1])) ta.setText(""); else { String txt = ta.getText(); if(txt.length() < 16384) ta.setText(txt + txt); else JOptionPane.showMessageDialog(this, "Text too long, not duplicated", "Simple Editor", JOptionPane.ERROR_MESSAGE); } } public static void main(String[] args) { JFrame window = new BasicEditor(); window.setTitle("Basic Editor"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } In Bild 8.19 ist das Programmfenster zu finden. Da der Text am Zeilenende automatisch umgebrochen wird, ist hier nur ein vertikaler Schieber erforderlich. 8.7 Bild 8.19 Einfacher Editor JSlider und JProgressbar Mit der Klasse "JSlider" lässt sich ein Schieberegler ausgeben, der mit Markierungen und einer Bemaßung versehen werden kann. Wenn der Benutzer den Regler betätigt, wird die Methode "stateChanged" des Interface "ChangeListener" ausgeführt (Tabelle 8.1). Tabelle 8.1 ChangeListener und zugehörige Methode Listener-Interface ChangeListener Methode stateChanged Auslösende Aktion Der Zustand hat sich geändert, der Anwender hat den Schieberegler betätigt 8 Swing-Komponenten 140 Die Klasse "JProgressBar" kann man zur Darstellung anloger Werte einsetzen. Eine typische Anwendung ist zum Beispiel eine Fortschrittsanzeige bei länger dauernden Vorgängen. Beispiel, CelsiusConverter import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; // For ChangeListener public class CelsiusConverter extends JFrame implements ChangeListener { // JSlider(int orientation, int min, int max, int initialValue) JSlider sl = new JSlider(JSlider.HORIZONTAL, -20, 100, 0); // JProgressBar(int orient, int min, int max) JProgressBar pb = new JProgressBar( JProgressBar.HORIZONTAL, -4, 212); JLabel cfLabel = new JLabel("0 °C = 32 °F", JLabel.CENTER); CelsiusConverter () { JLabel slLabel = new JLabel("Temperatur in °C", JLabel.CENTER); slLabel.setAlignmentX(Component.CENTER_ALIGNMENT); JLabel pbLabel = new JLabel("Temperatur in °F", JLabel.CENTER); pbLabel.setAlignmentX(Component.CENTER_ALIGNMENT); cfLabel.setAlignmentX(Component.CENTER_ALIGNMENT); sl.setMajorTickSpacing(40); sl.setMinorTickSpacing(10); sl.setPaintTicks(true); sl.setPaintLabels(true); sl.addChangeListener(this); // public static Border createEmptyBorder(int spaceTop, // int spaceLeft, int spaceBottom, int spaceRight) sl.setBorder(BorderFactory.createEmptyBorder(2, 2, 10, 6)); pb.setValue(32); pb.setBorder(BorderFactory.createEmptyBorder(2, 2, 10, 6)); pb.setStringPainted(true); Container con = getContentPane(); con.setLayout(new BoxLayout(con, BoxLayout.Y_AXIS)); con.add(slLabel); con.add(sl); con.add(pbLabel); con.add(pb); con.add(cfLabel); } public void stateChanged(ChangeEvent e) { int tempF = (int) (1.8*sl.getValue() + 32); pb.setValue(tempF); cfLabel.setText(sl.getValue() + " °C = " + tempF +" °F"); } public static void main(String[] args) { 141 8 Swing-Komponenten JFrame window = new CelsiusConverter(); window.setTitle("Celsius Converter"); window.pack(); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.20 zeigt das Programmfenster. Die Temperatur in Grad Celsius wird am Schieberegler mit der Maus oder mit der Tastatur eingestellt. 8.8 JMenu und JPopupmenu Bild 8.20 Celsius Converter Ein Menü ("JMenu") ist Bestandteil einer Menüleiste ("JMenuBar"). Wenn man einen Menüeintrag ("JMenuItem") anklickt, wird die Methode "ActionPerformed" des Interface "ActionListener" ausgeführt. Das folgende Programm zeigt, wie man eine einfache Menüstruktur implementiert. Beispiel, Menu1 import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Menu1 extends JFrame implements ActionListener { Menu1() { JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu1 = new JMenu("File"); menuBar.add(menu1); addMenuItem(menu1, new JMenuItem("Load")); addMenuItem(menu1, new JMenuItem("Save")); menu1.addSeparator(); addMenuItem(menu1, new JMenuItem("Exit")); JMenu menu2 = new JMenu("Options"); menuBar.add(menu2); addMenuItem(menu2, new JMenuItem("Settings")); } private void addMenuItem(JMenu m, JMenuItem mi) { m.add(mi); mi.addActionListener(this); } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd.equals("Exit")) System.exit(0); JOptionPane.showMessageDialog(this, cmd, "Menu 1", JOptionPane.INFORMATION_MESSAGE); } 142 8 Swing-Komponenten public static void main(String[] args) { JFrame window = new Menu1(); window.setTitle("Menu 1"); window.setSize(300, 180); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8.21 zeigt das vom Programm ausgegebene Fenster. Der Menüeintrag "Exit" hat die zu erwartende Wirkung: das Programm wird beendet. Die anderen Menüeintragungen führen hier nur dazu, dass der jeweilige Menütext in einem Meldungsfenster zur Bestätigung ausgegeben wird. Bild 8.21 Menü Mit der Methode "setActionCommand" kann man einem Menüeintrag einen Text zuweisen, der von der Beschriftung dieses Menüeintrags abweicht. Auf diese Weise lassen sich Menüeintragungen mit demselben Text unterscheiden. Im folgenden Programmausschnitt ist die Vorgehensweise zu finden. ... // wie im vorausgehenden Beispiel Menu1() { JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu1 = new JMenu("File"); menuBar.add(menu1); addMenuItem(menu1, new JMenuItem("Exit"), "StopProg"); JMenu menu2 = new JMenu("Rectangle"); menuBar.add(menu2); addMenuItem(menu2, new JMenuItem("Red"), "RectRed"); JMenu menu3 = new JMenu("Circle"); menuBar.add(menu3); addMenuItem(menu3, new JMenuItem("Red"), "CircleRed"); } private void addMenuItem(JMenu m, JMenuItem mi, String s) { m.add(mi); mi.addActionListener(this); mi.setActionCommand(s); } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd.equals("StopProg")) System.exit(0); JOptionPane.showMessageDialog(this, cmd, "Menu 1", JOptionPane.INFORMATION_MESSAGE); 8 Swing-Komponenten 143 } ... Bild 8.22 zeigt das vom Programm ausgegebene Fenster. Eine vollständigere Beschreibung der Möglichkeiten ist z.B. in [1] zu finden. Bild 8.22 "Red" in "Rectangle" und in "Circle" Kontextmenü Kontextmenüs (Popup-Menüs) werden mit einem rechten Mausklick auf die jeweilige Komponente angefordert und enthalten auf diese Komponente bezogene Menüepunkte. Beispiel, Menu2 import java.awt.*; import java.awt.event.*; import javax.swing.*; class MyPanel extends JPanel { private Color cl; MyPanel(Color c) {cl = c; } void setColor(Color c) { cl = c; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(cl); g.drawRect(getSize().width/2-50, getSize().height/2-50, 100, 100); } } public class Menu2 extends JFrame implements ActionListener { private final String[] TXT={"Black","White","Red" }; private final Color[] COL={Color.BLACK,Color.WHITE, Color.RED}; final private MyPanel mypnl = new MyPanel(COL[0]); final private JPopupMenu popup = new JPopupMenu(); Menu2() { addMenuItem(popup, new JMenuItem(TXT[0])); addMenuItem(popup, new JMenuItem(TXT[1])); popup.addSeparator(); addMenuItem(popup, new JMenuItem(TXT[2])); addMouseListener(new MyMouseListener()); getContentPane().add(mypnl); } private void addMenuItem(JPopupMenu pm, JMenuItem mi) { pm.add(mi); mi.addActionListener(this); } 144 8 Swing-Komponenten public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); for(int i = 0; i < TXT.length; i++) if(cmd.equals(TXT[i])) mypnl.setColor(COL[i]); repaint(); } class MyMouseListener extends MouseAdapter { public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) popup.show(e.getComponent(), e.getX(), e.getY()); } } public static void main(String[] args) { Menu2 window = new Menu2(); window.setTitle("Popup"); window.setSize(260, 180); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Bild 8,.23 zeigt das vom Programm ausgegebene Fenster. Mit Hilfe des Kontextmenüs lässt sich die Farbe der Rechteckumrandung ändern. 8.9 JFileChooser Bild 8.23 Kontextmenü Die Klasse "JFileChooser" stellt die üblichen Dialogfenster zur Verfügung, die man zum Laden oder Speichern einer Datei benötigt. Mit dem folgenden Programm lassen sich Dateien zum Laden oder Speichern auswählen. Die Dateien werden dann aber nicht wirklich geladen oder gespeichert, die jeweilige Auswahl wird vielmehr nur in einem Textfeld protokolliert. Beispiel, FileChooserDemo import import import import java.awt.*; java.awt.event.*; javax.swing.*; java.io.*; public class FileChooserDemo extends JFrame implements ActionListener { final String[] TXT = {"Open", "Save"}; final JTextArea log = new JTextArea(10, 40); final JFileChooser fc = new JFileChooser(); public FileChooserDemo() { log.setEditable(false); log.setMargin(new Insets(5, 5, 5, 5)); 8 Swing-Komponenten 145 JPanel pnl = new JPanel(); for(int i = 0; i < TXT.length; i++) { JButton btn = new JButton(TXT[i]); btn.addActionListener(this); pnl.add(btn); } Container contentPane = getContentPane(); contentPane.add(new JScrollPane(log), BorderLayout.CENTER); contentPane.add(pnl, BorderLayout.SOUTH); } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); String mss = cmd + ": Cancelled"; int ret = (cmd.equals(TXT[0]))? fc.showOpenDialog(this): fc.showSaveDialog(this); if(ret == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); mss = cmd + ": " + file.getPath(); } log.append(mss + '\n'); } public static void main(String[] args) { JFrame window = new FileChooserDemo(); window.setTitle("File Chooser Demo"); window.pack(); window.setResizable(false); window.setDefaultCloseOperation(EXIT_ON_CLOSE); window.setVisible(true); } } Vom Programm ausgegebene Fenster sind in Bild 8.24 zu finden. Es ist gerade der Dialog zum Öffnen einer Datei zu sehen. Bild 8.24 File Chooser 8 Swing-Komponenten 146 8.10 Übungsaufgaben Übungsaufgabe 8.1 In einer der Aufgaben zu diesem Abschnitts ist das Programm eines einfachen Rechners entwickelt worden (Bild 8.25). Wenn man eine Taste betätigt, gibt das Programm bisher aber nur die zugehörige Beschriftung im Rechnerdisplay aus. Erweitern Sie das Programm nun zu einem funktionsfähigen einfachen Rechner. Bild 8.25 Einfacher Rechner Übungsaufgabe 8.2 Bild 8.26 MyEditor Schreiben Sie einen einfachen Editor (Bild 8.26) für Textdateien. Es soll möglich sein, eine Datei über "File | Open" zu öffnen, den Text zu editieren und den geänderten Text dann mit "File | Save As" in der ursprünglichen oder in einer anderen Datei zu speichern. Verwenden Sie zum Öffnen und zum Speichern der Textdatei jeweils eine Instanz von "JFileChooser". 9 Anhang 9.1 Schlüsselwörter (keywords) in Java Liste der Schlüsselwörter (reserviert) abstract boolean break byte case catch char class const * continue default do double else extends final finally float for goto * if implements import instanceof int interface long native new package private protected public return short static strictfp ** super switch synchronized this throw throws transient try void volatile while * Gegenwärtig nicht benutzt. ** In Java 2 dazugekommen. true, false und null sind keine Schlüsselwörter, sie sind aber dennoch reserviert. 9.2 Einige Java-Begriffe J2SE: Java 2 Standard Edition J2EE: Java 2 Enterprise Edition für die Programmierung auf Server-Seite J2ME: Java 2 Micro Edition für eingebettete Systeme (embedded systems) Abstract Windows Toolkit (AWT, ab JDK 1.0): Stellt elementare Grafik- und Fensterfunktionen zur Verfügung. Swing Toolset (in der aktuellen Form ab JDK 1.2): Konzept und Schnittstellen für plattformübergreifende GUI Applikationen (Graphical User Interface). Swing Applikationen können das typische Aussehen (Look & Feel) einer Plattform annehmen (Windows, Macintosh, Solaris). Die Swing Komponenten enthalten Duplikate aller AWT-Komponenten und darüber hinaus zahlreiche zusätzliche Komponenten. Beans, Javabeans (ab JDK 1.1): Wiederverwendbare Softwarebausteine. Beans ermöglichen das "Zusammenstellen" von Programmen aus vorgefertigten Komponenten in einer grafischen Entwicklungsumgebung (IDE, Integrated Development Environment). Java Database Connectivity (JDBC): Schnittstelle zum Zugriff auf Datenbanken über SQLBefehle (System Querry Language). Die Schnittstelle muss dazu von der Datenbank unterstützt werden. Remote Method Invocation (RMI): Programme können mit RMI in verteilten Systemen auf Objekte von anderen Anwendungen zugreifen, die im Netzwerk von anderen Java-VM ausgeführt werden. 9 Anhang 9.3 148 Package java.lang 9.3.1 Class Summary Boolean Byte Character The Boolean class wraps a value of the primitive type boolean in an object. The Byte class is the standard wrapper for byte values. The Character class wraps a value of the primitive type char in an object. Instances of this class represent particular subsets of the Unicode character set. Character.Subset A family of character subsets representing the character blocks defined by the Character.UnicodeBlock Unicode 2.0 specification. Class Instances of the class Class represent classes and interfaces in a running Java application. ClassLoader The class ClassLoader is an abstract class. Compiler The Compiler class is provided to support Java-to-native-code compilers and related services. Double The Double class wraps a value of the primitive type double in an object. Float The Float class wraps a value of primitive type float in an object. This class extends ThreadLocal to provide inheritance of values from parent Thread InheritableThreadLocal to child Thread: when a child thread is created, the child receives initial values for all InheritableThreadLocals for which the parent has values. Integer The Integer class wraps a value of the primitive type int in an object. Long The Long class wraps a value of the primitive type long in an object. Math Number Object Package Process Runtime RuntimePermission SecurityManager Short StrictMath String StringBuffer System Thread ThreadGroup ThreadLocal The class Math contains methods for performing basic numeric operations such as the elementary exponential, logarithm, square root, and trigonometric functions. The abstract class Number is the superclass of classes Byte, Double, Float, Integer, Long, and Short. Class Object is the root of the class hierarchy. Package objects contain version information about the implementation and specification of a Java package. The Runtime.exec methods create a native process and return an instance of a subclass of Process that can be used to control the process and obtain information about it. Every Java application has a single instance of class Runtime that allows the application to interface with the environment in which the application is running. This class is for runtime permissions. The security manager is a class that allows applications to implement a security policy. The Short class is the standard wrapper for short values. The class StrictMath contains methods for performing basic numeric operations such as the elementary exponential, logarithm, square root, and trigonometric functions. The String class represents character strings. A string buffer implements a mutable sequence of characters. The System class contains several useful class fields and methods. A thread is a thread of execution in a program. A thread group represents a set of threads. This class provides ThreadLocal variables. 9 Anhang Throwable Void 9.3.2 149 The Throwable class is the superclass of all errors and exceptions in the Java language. The Void class is an uninstantiable placeholder class to hold a reference to the Class object representing the primitive Java type void. Class java.lang.Math Field Summary static double E The double value that is closer than any other to e, the base of the natural logarithms. static double PI The double value that is closer than any other to pi, the ratio of the circumference of a circle to its diameter. Method Summary static double abs(double a) Returns the absolute value of a double value. static float abs(float a) Returns the absolute value of a float value. static int abs(int a) Returns the absolute value of an int value. static long abs(long a) Returns the absolute value of a long value. static double acos(double a) Returns the arc cosine of an angle, in the range of 0.0 through pi. static double asin(double a) Returns the arc sine of an angle, in the range of -pi/2 through pi/2. static double atan(double a) Returns the arc tangent of an angle, in the range of -pi/2 through pi/2. static double atan2(double a, double b) Converts rectangular coordinates (b, a) to polar (r, theta). static double ceil(double a) Returns the smallest (closest to negative infinity) double value that is not less than the argument and is equal to a mathematical integer. static double cos(double a) Returns the trigonometric cosine of an angle. static double exp(double a) Returns the exponential number e (i.e., 2.718...) raised to the power of a double value. static double floor(double a) Returns the largest (closest to positive infinity) double value that is not greater than the argument and is equal to a mathematical integer. static double IEEEremainder(double f1, double f2) Computes the remainder operation on two arguments as prescribed by the IEEE 754 standard. static double log(double a) Returns the natural logarithm (base e) of a double value. static double max(double a, double b) Returns the greater of two double values. static float max(float a, float b) Returns the greater of two float values. 9 Anhang 150 static int max(int a, int b) Returns the greater of two int values. static long max(long a, long b) Returns the greater of two long values. static double min(double a, double b) Returns the smaller of two double values. static float min(float a, float b) Returns the smaller of two float values. static int min(int a, int b) Returns the smaller of two int values. static long min(long a, long b) Returns the smaller of two long values. static double pow(double a, double b) Returns of value of the first argument raised to the power of the second argument. static double random() Returns a double value with a positive sign, greater than or equal to 0.0 and less than 1.0. static double rint(double a) Returns the double value that is closest in value to a and is equal to a mathematical integer. static long round(double a) Returns the closest long to the argument. static int round(float a) Returns the closest int to the argument. sin(double a) static double Returns the trigonometric sine of an angle. static double sqrt(double a) Returns the correctly rounded positive square root of a double value. static double tan(double a) Returns the trigonometric tangent of an angle. static double toDegrees(double angrad) Converts an angle measured in radians to the equivalent angle measured in degrees. static double toRadians(double angdeg) Converts an angle measured in degrees to the equivalent angle measured in radians. 9.3.3 Class java.lang.String Method Summary char charAt(int index) Returns the character at the specified index. int compareTo(Object o) Compares this String to another Object. int compareTo(String anotherString) Compares two strings lexicographically. int compareToIgnoreCase(String str) Compares two strings lexicographically, ignoring case considerations. String concat(String str) Concatenates the specified string to the end of this string. static String copyValueOf(char[] data) Returns a String that is equivalent to the specified character array. 9 Anhang 151 static String copyValueOf(char[] data, int offset, int count) Returns a String that is equivalent to the specified character array. boolean endsWith(String suffix) Tests if this string ends with the specified suffix. boolean equals(Object anObject) Compares this string to the specified object. boolean equalsIgnoreCase(String anotherString) Compares this String to another String, ignoring case considerations. getBytes() byte[] Convert this String into bytes according to the platform's default character encoding, storing the result into a new byte array. void getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin) Deprecated. This method does not properly convert characters into bytes. As of JDK 1.1, the preferred way to do this is via the getBytes(String enc) method, which takes a character-encoding name, or the getBytes() method, which uses the platform's default encoding. byte[] getBytes(String enc) Convert this String into bytes according to the specified character encoding, storing the result into a new byte array. void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) Copies characters from this string into the destination character array. hashCode() int Returns a hashcode for this string. int indexOf(int ch) Returns the index within this string of the first occurrence of the specified character. int indexOf(int ch, int fromIndex) Returns the index within this string of the first occurrence of the specified character, starting the search at the specified index. int indexOf(String str) Returns the index within this string of the first occurrence of the specified substring. int indexOf(String str, int fromIndex) Returns the index within this string of the first occurrence of the specified substring, starting at the specified index. String intern() Returns a canonical representation for the string object. int lastIndexOf(int ch) Returns the index within this string of the last occurrence of the specified character. lastIndexOf(int ch, int fromIndex) int Returns the index within this string of the last occurrence of the specified character, searching backward starting at the specified index. int lastIndexOf(String str) Returns the index within this string of the rightmost occurrence of the specified substring. int lastIndexOf(String str, int fromIndex) Returns the index within this string of the last occurrence of the specified substring. int length() Returns the length of this string. boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) Tests if two string regions are equal. boolean regionMatches(int toffset, String other, int ooffset, int len) Tests if two string regions are equal. 9 Anhang 152 String replace(char oldChar, char newChar) Returns a new string resulting from replacing all occurrences of oldChar in this string with newChar. boolean startsWith(String prefix) Tests if this string starts with the specified prefix. startsWith(String prefix, int toffset) boolean Tests if this string starts with the specified prefix beginning a specified index. String substring(int beginIndex) Returns a new string that is a substring of this string. String substring(int beginIndex, int endIndex) Returns a new string that is a substring of this string. char[] toCharArray() Converts this string to a new character array. String toLowerCase() Converts all of the characters in this String to lower case using the rules of the default locale, which is returned by Locale.getDefault. String toLowerCase(Locale locale) Converts all of the characters in this String to lower case using the rules of the given Locale. String toString() This object (which is already a string!) is itself returned. String toUpperCase() Converts all of the characters in this String to upper case using the rules of the default locale, which is returned by Locale.getDefault. String toUpperCase(Locale locale) Converts all of the characters in this String to upper case using the rules of the given locale. String trim() Removes white space from both ends of this string. static String valueOf(boolean b) Returns the string representation of the boolean argument. static String valueOf(char c) Returns the string representation of the char argument. static String valueOf(char[] data) Returns the string representation of the char array argument. static String valueOf(char[] data, int offset, int count) Returns the string representation of a specific subarray of the char array argument. static String valueOf(double d) Returns the string representation of the double argument. static String valueOf(float f) Returns the string representation of the float argument. static String valueOf(int i) Returns the string representation of the int argument. static String valueOf(long l) Returns the string representation of the long argument. static String valueOf(Object obj) Returns the string representation of the Object argument. 9 Anhang 9.4 153 Class java.awt.Graphics Method Summary abstract clearRect(int x, int y, int width, int height) void Clears the specified rectangle by filling it with the background color of the current drawing surface. abstract clipRect(int x, int y, int width, int height) void Intersects the current clip with the specified rectangle. abstract copyArea(int x, int y, int width, int height, int dx, int dy) void Copies an area of the component by a distance specified by dx and dy. abstract create() Graphics Creates a new Graphics object that is a copy of this Graphics object. Graphics create(int x, int y, int width, int height) Creates a new Graphics object based on this Graphics object, but with a new translation and clip area. abstract dispose() void Disposes of this graphics context and releases any system resources that it is using. void draw3DRect(int x, int y, int width, int height, boolean raised) Draws a 3-D highlighted outline of the specified rectangle. abstract drawArc(int x, int y, int width, int height, int startAngle, void int arcAngle) Draws the outline of a circular or elliptical arc covering the specified rectangle. void drawBytes(byte[] data, int offset, int length, int x, int y) Draws the text given by the specified byte array, using this graphics context's current font and color. void drawChars(char[] data, int offset, int length, int x, int y) Draws the text given by the specified character array, using this graphics context's current font and color. abstract drawImage(Image img, int x, int y, Color bgcolor, boolean ImageObserver observer) Draws as much of the specified image as is currently available. abstract drawImage(Image img, int x, int y, ImageObserver observer) boolean Draws as much of the specified image as is currently available. abstract drawImage(Image img, int x, int y, int width, int height, boolean Color bgcolor, ImageObserver observer) Draws as much of the specified image as has already been scaled to fit inside the specified rectangle. abstract drawImage(Image img, int x, int y, int width, int height, boolean ImageObserver observer) Draws as much of the specified image as has already been scaled to fit inside the specified rectangle. abstract drawImage(Image img, int dx1, int dy1, int dx2, int dy2, boolean int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) Draws as much of the specified area of the specified image as is currently available, scaling it on the fly to fit inside the specified area of the destination drawable surface. abstract drawImage(Image img, int dx1, int dy1, int dx2, int dy2, boolean int sx1, int sy1, int sx2, int sy2, ImageObserver observer) Draws as much of the specified area of the specified image as is currently available, scaling it on the fly to fit inside the specified area of the destination drawable surface. abstract drawLine(int x1, int y1, int x2, int y2) void Draws a line, using the current color, between the points (x1, y1) and (x2, y2) in this graphics context's coordinate system. 9 Anhang 154 abstract drawOval(int x, int y, int width, int height) void Draws the outline of an oval. abstract drawPolygon(int[] xPoints, int[] yPoints, int nPoints) void Draws a closed polygon defined by arrays of x and y coordinates. void drawPolygon(Polygon p) Draws the outline of a polygon defined by the specified Polygon object. abstract drawPolyline(int[] xPoints, int[] yPoints, int nPoints) void Draws a sequence of connected lines defined by arrays of x and y coordinates. drawRect(int x, int y, int width, int height) void Draws the outline of the specified rectangle. abstract drawRoundRect(int x, int y, int width, int height, void int arcWidth, int arcHeight) Draws an outlined round-cornered rectangle using this graphics context's current color. abstract drawString(AttributedCharacterIterator iterator, int x, int y) void Draws the text given by the specified iterator, using this graphics context's current color. abstract drawString(String str, int x, int y) void Draws the text given by the specified string, using this graphics context's current font and color. void fill3DRect(int x, int y, int width, int height, boolean raised) Paints a 3-D highlighted rectangle filled with the current color. abstract fillArc(int x, int y, int width, int height, int startAngle, void int arcAngle) Fills a circular or elliptical arc covering the specified rectangle. fillOval(int x, int y, int width, int height) abstract void Fills an oval bounded by the specified rectangle with the current color. abstract fillPolygon(int[] xPoints, int[] yPoints, int nPoints) void Fills a closed polygon defined by arrays of x and y coordinates. void fillPolygon(Polygon p) Fills the polygon defined by the specified Polygon object with the graphics context's current color. abstract fillRect(int x, int y, int width, int height) void Fills the specified rectangle. abstract fillRoundRect(int x, int y, int width, int height, void int arcWidth, int arcHeight) Fills the specified rounded corner rectangle with the current color. finalize() void Disposes of this graphics context once it is no longer referenced. abstract getClip() Shape Gets the current clipping area. abstract getClipBounds() Rectangle Returns the bounding rectangle of the current clipping area. Rectangle getClipBounds(Rectangle r) Returns the bounding rectangle of the current clipping area. Rectangle getClipRect() Deprecated. As of JDK version 1.1, replaced by getClipBounds(). abstract getColor() Color Gets this graphics context's current color. abstract getFont() Font Gets the current font. FontMetrics getFontMetrics() Gets the font metrics of the current font. 9 Anhang 155 abstract getFontMetrics(Font f) FontMetrics Gets the font metrics for the specified font. boolean hitClip(int x, int y, int width, int height) Returns true if the specified rectangular area intersects the bounding rectangle of the current clipping area. abstract setClip(int x, int y, int width, int height) void Sets the current clip to the rectangle specified by the given coordinates. abstract setClip(Shape clip) void Sets the current clipping area to an arbitrary clip shape. abstract setColor(Color c) void Sets this graphics context's current color to the specified color. setFont(Font font) abstract void Sets this graphics context's font to the specified font. abstract setPaintMode() void Sets the paint mode of this graphics context to overwrite the destination with this graphics context's current color. abstract setXORMode(Color c1) void Sets the paint mode of this graphics context to alternate between this graphics context's current color and the new specified color. String toString() Returns a String object representing this Graphics object's value. abstract translate(int x, int y) void Translates the origin of the graphics context to the point (x, y) in the current coordinate system.