Berner Fachhochschule Hochschule für Technik und Informatik Programmieren in Java Dr. Stephan Fischli Herbst 2004 Einführung 3 Warum Java? Java ist eine Programmiersprache für das Internet: • einfach • objektorientiert • verteilt • interpretiert • robust • sicher • Architektur-neutral • portierbar • schnell • parallel • dynamisch einfach: Java ist im Vergleich zu C++ eine einfache Sprache objektorientiert: In Java gibt es nur Klassen und Objekte verteilt: Java unterstützt die Entwicklung verteilter Applikationen interpretiert: Der Java-Compiler erzeugt Bytecode, der von einer virtuellen Java-Maschine interpretiert wird robust: Java ist streng typisiert, hat ein einfaches MemoryModell und unterstützt Exception-Handling sicher: Java enthält Sicherheitsmechanismen zum Schutz vor Eingriffen ins lokale Betriebssystem Architektur-neutral: Ein kompiliertes Java-Programm läuft auf jeder Plattform, auf der eine virtuelle Maschine existiert portierbar: Die Java-Sprache hat keine implementationsabhängigen Aspekte schnell: Im Vergleich mit anderen interpretierten Hochsprachen ist die Ausführung eines Java-Programms schnell parallel: Java unterstützt die Parallelprogrammierung mittels Threads dynamisch: Runtime-Typinformation erlaubt das dynamsiche Laden von Klassen 4 Geschichte 1990 Sun startet Entwicklung einer neuen Umgebung für Consumer Electronics unter James Gosling Programmiersprache Oak entsteht 1994 Ausrichtung des Projekts auf das Internet Oak wird in Java umbenannt und fertiggestellt Jan 1995 JDK 1.0 wird verteilt zusammen mit dem Web-Browser HotJava Feb 1997 JDK 1.1 wird herausgegeben Dez 1998 Java 2 SDK, Version 1.2 Mai 2000 Java 2 SDK, Version 1.3 Feb 2002 Java 2 SDK, Version 1.4 5 Java-Architektur Java-Sourcecode Java-Compiler Java-Bytecode Virtuelle Maschine Klassenbibliothek Plattform Ein Java-Sourcecode-Programm wird durch den Java-Compiler in einen optimierten Zwischencode, den sogenannten Bytecode, übersetzt. Dieser wird dann von der virtuellen Java-Maschine ausgeführt, welche die zugrundeliegende Plattform (Hardware, Betriebssystem, Graphiksystem) abstrahiert. Ebenfalls zu Java gehört eine grosse Klassenbibliothek, die u.a. Klassen zur Graphik-, Netzwerk- und Parallelprogrammierung enthält. 6 Klassenbibliothek Die Java-Klassenbibliothek besteht aus: • java.applet - Applets • java.awt - Abstract Window Toolkit • java.beans - Java Beans • java.io - Ein- und Ausgabe • java.lang - Basisklassen • java.math - Arithmetik langer Zahlen • java.net - Networking • java.rmi - Remote Method Invocation • java.security - Kryptographie • java.sql - Java Database Connectivity • java.text - Internationalisierung • java.util - Hilfsklassen Die Java-Klassenbibliothek ist in sogenannte Packages unterteilt. Neben den Kern-Klassen gibt es eine Reihe von StandardErweiterungen. Dazu gehören zum Beispiel die Swing-Klassen (Package javax.swing), die Teil der Java Foundation Classes (JFC) sind. 7 Entwicklungsumgebung Das Java 2 SDK von Sun umfasst: • javac - Compiler • java - Interpreter • javadoc - Dokumentationsgenerator • appletviewer - Applet-Betrachter • jar - Handhabung von Java Archive Files • jdb - Debugger • javah - C-File Generator für native Methoden • javap - Klassen-Disassembler • rmic - Erzeugung von Stubs und Skeletons für Remote-Objekte • rmiregistry - Eintrag von Remote-Objekten • rmid - RMI Activation Daemon • serialver - Erzeugung von Versionsnummern Das Java 2 Software Development Kit von Sun ist eine frei erhältliche Sammlung von Befehlszeilen-orientierten Tools. Daneben gibt es eine grosse Anzahl von kommerziellen graphischen Entwicklungsumgebungen. 8 Programmbeispiel Sourcefile Hello.java: public class Hello { public static void main(String[] args) { System.out.println("Hello " + args[0]); } } Kompilieren: C:\> javac Hello.java Ausführen: C:\> java Hello world Ein Java-Programm enthält immer eine Klasse, die eine statische main()-Methode hat. Diese ist der Startpunkt für die Ausführung durch den Interpreter. 9 Literatur • • • • • • Ken Arnold, James Gosling, David Holmes The Java Programming Language, Addison-Wesley Mary Campione, Kathy Walrath The Java Tutorial, Addison-Wesley http://java.sun.com/docs/books/tutorial/ David Flanagan Java in a Nutshell, O'Reilly Peter van der Linden Just Java 2, Prentice Hall Bruce Eckel Thinking in Java, Prentice Hall http://www.bruceeckel.com/TIJ2/ Guido Krüger Go To Java 2, Addison-Wesley http://www.javabuch.de/ 10 Einfache Sprachkonstrukte 11 Kommentare /* Dies ist ein Blockkommentar */ // Dies ist ein Zeilenkommentar /** * Dies ist ein Dokumentationskommentar */ Ein Dokumentationskommentar wird vom Tool javadoc erkannt und in die zu erzeugende HTML-Dokumentation übernommen. 12 Datentypen Primitive Datentypen • Zahlen, Zeichen, Boolean • haben Wertsemantik Referenz-Datentypen • Objekte und Arrays • haben Referenz-Semantik • Initialwert null Wertsemantik bedeutet, dass eine entsprechende Variable einen Wert enthält. Bei Referenz-Semantik dagegen enthält die Variable eine Referenz, die auf ein Objekt oder einen Array zeigt. 13 Referenzen als Parameter void change(Button b1) { b1.setLabel("green"); } void replace(Button b2) { b2 = new Button("blue"); } Button b = new Button("red"); change(b); System.out.println(b.getLabel()); replace(b); System.out.println(b.getLabel()); b // green // green green b1 b2 blue Wird einer Methode eine Referenz auf ein Objekt übergeben und das Objekt in der Methode verändert, so ist die Änderung auch ausserhalb der Methode sichtbar. Eine Änderung der Referenz selbst ist dagegen ausserhalb der Methode wirkungslos. 14 Vergleichen von Referenzen Button b1 = new Button("red"); Button b2 = new Button("red"); Button b3 = b2; if (b1 == b2) System.out.println("equal"); if (b2 == b3) System.out.println("equal"); b1 red b2 red // false // true b3 Wendet man den Vergleichs- oder Zuweisungsoperator auf zwei Referenzvariabeln an, so werden nur die Referenzen verglichen bzw. einander zugweisen und nicht die Objekte, auf welche die Referenzen zeigen. 15 Primitive Datentypen Typ byte short int long float double char boolean Grösse 8 16 32 64 32 64 16 1 In Java sind alle numerischen Typen vorzeichenbehaftet und haben einen fest definierten Wertebereich. Characters werden als 16 Bit-Unicode-Zeichen dargestellt. Unicode ist ein Codiersystem, das über 34000 Zeichen aus 24 Sprachen enthält. Es ist kompatibel zu ASCII und ISO Latin 1. Boolean ist der Resultattyp aller Vergleichsoperatoren und der Typ von Bedingungen in Kontrollstrukturen. Boolean ist nicht kompatibel mit den andern Typen! Für alle primitiven Datentypen gibt es auch Wrapper-Klassen, mit denen aus einem Wert ein entsprechendes Objekt erzeugt werden kann. 16 Literale Integer-Zahlen • dezimal: • oktal: • hexadezimal: 2748 05274 0xabc Fliesskommazahlen • Fixpunkt: 3.14156 • Fliesskomma: 2.0e-10 Zeichen • normal: • oktal: • Escape: • Unicode: 'a' '\141' '\n' '\u0061' Boolean: • wahr: • falsch: true false 17 Operatoren • • • • • • • • • arithmetische Operatoren: Vergleichsoperatoren: Alternative-Operator: logische Operatoren: bitweise Operatoren: Shift-Operatoren: Konkatenationsoperator: Cast-Operator: Typ-Operator: + - * / % ++ -== != > < >= <= ?: & | ! ^ && || &|~^ << >> >>> + () instanceof Der AND-Operator & wertet immer beide Operanden aus, während der Operator && den zweiten Operanden nicht auswertet, wenn der erste bereits false ist. Analoges gilt für die OR-Operatoren | und ||. Der Shift-Operator >> behält das Vorzeichen-Bit bei, während der Operator >>> den Operanden als vorzeichenlose Zahl behandelt. Mit dem Konkatenationsoperator + können zwei Strings zusammengehängt werden. Der Cast-Operator erlaubt, den Typ eines Literals oder einer Variablen in einen andern Typ umzuwandeln. Mit dem Typ-Operator instanceof kann geprüft werden, ob ein Objekt eine Instanz einer bestimmten Klasse ist oder ein bestimmtes Interface implementiert. 18 Alternativen if (month == 2) days = 28; else if (month == 4 | month == 6 | month == 9 | month == 11) days = 30; else days = 31; switch (month) { case 1 : days = 31; break; case 2 : days = 28; break; ... case 12 : days = 31; break; default : } Durch ein if-else-Statement werden abhängig von einer Bedingung alternative Codeblöcke ausgeführt. Bei einem switch-Statement wird eine aus mehreren Alternativen anhand eines numerischen Ausdrucks ausgewählt. 19 Schlaufen int i = 1; while (i <= 10) { System.out.println(i); i++; } int i = 1; do { System.out.println(i); i++; } while (i <= 10); for (int i = 1; i <= 10; i++) System.out.println(i); Durch eine while-Schlaufe wird ein Codeblock wiederholt ausgeführt, solange die entsprechende Bedingung erfüllt ist. Bei einer do-while-Schlaufe wird die Bedingung jeweils am Ende des Codeblocks getest und somit der Codeblock mindestens einmal ausgeführt. for-Schlaufen eignen sich vor allem zur Implementation von Zählschlaufen. 20 Sprünge int m = 315; int n = 196; while (true) { int r = m % n; if (r == 0) break; m = n; n = r; } System.out.println(n); loop: for (int p = 2; p <= 100; p++) { for (int d = 2; d * d <= p; d++) if (p % d == 0) continue loop; System.out.println(p); } Mittels break und continue kann eine Schlaufe verlassen oder mit einer Schlaufe unmittelbar fortgefahren werden. Durch Angabe eines Labels kann auch eine äussere Schlaufe verlassen bzw. mit einer äusseren Schlaufe fortgefahren werden. 21 Arrays Button[] buttons; // Deklaration buttons = new Button[3]; // Konstruktion buttons[0] = new Button("red"); // Initialisierung buttons[1] = new Button("green"); buttons[2] = new Button("blue"); ... for (int i = 0; i < buttons.length; i++) System.out.println(buttons[i].getLabel()); buttons red green blue Ein Array kann entweder mit dem Operator new und anschliessender Zuweisung der Array-Elemente oder mit einem statischen Initialisierer erzeugt werden. Jeder Array kennt seine Länge. Diese ist über das Attribut length abrufbar und wird vom Runtime-System verwendet um zu prüfen, ob die Indizes im gültigen Bereich liegen. 22 Strings • Strings sind Objekte der Klasse String • String-Literale werden als String-Objekte interpretiert String s = "This is a string literal"; int maxNameLength = "Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogo gogoch".length(); if ("nobody".equals(myName)) ... • Strings können mit dem Operator + konkateniert werden String s = "He is " + age + " years old"; • Strings können nicht verändert werden Ist beim Konkatenationsoperator einer der beiden Operanden kein String, so wird er implizit in einen String umgewandelt. Bei primitiven Datentypen geschieht dies automatisch, bei ReferenzDatentypen durch Aufruf der Methode toString(). Soll der Inhalt eines Strings verändert werden, so muss ein Objekt der Klasse StringBuffer verwendet werden. 23 24 Klassen und Objekte 25 Definieren einer Klasse class Clock { int hour = 0; int min = 0; int sec = 0; void tick() { if (++sec == 60) { sec = 0; if (++min == 60) { min = 0; if (++hour == 24) hour = 0; } } } // Attribute // Methode } Eine Klasse definiert einen Datentyp, der aus Attributen (Daten) besteht und Methoden (Funktionen) hat, um auf die Attribute zuzugreifen. Die Attribute können mit einem Initialwert versehen werden. Sonst werden sie vom Compiler auf ihren Defaultwert (0 für primitive Datentypen, null für Referenz-Datentypen) initialisiert. 26 Erzeugen von Objekten Clock c = new Clock(); c.hour = 8; c.min = 20; c.sec = 0; c.tick(); Objekte einer Klasse werden mit dem Operator new erzeugt. Jedes Objekt erhält seine eigene Kopie der Attribute, auf die mit dem Punktoperator zugegriffen werden kann. Die Methoden eines Objekts werden ebenfalls mit dem Punktoperator aufgerufen. 27 Zugriffsrechte class Clock { private int hour, min, sec; ... public int getHour() { return hour; } public int getMin() { return min; } public int getSec() { return sec; } } Clock c = new Clock(); c.hour = 8; // Fehler Werden die Attribute einer Klasse als private spezifiziert, so kann nur von Methoden der Klasse aus auf sie zugegriffen werden (Datenkapselung). public-Attribute dagegen sind auch ausserhalb der Klasse zugreifbar. Ebenso gibt es private- und public-Methoden, die nur von Methoden der Klasse aus bzw. global aufgerufen werden können. 28 Konstruktor class Clock { private int hour, min, sec; public Clock(int h, int m, int s) { hour = h; min = m; sec = s; } ... } Clock c = new Clock(8, 20, 0); Konstruktoren sind spezielle Methoden, die denselben Namen wie die Klasse aber keinen Returnwert haben. Sie werden implizit bei der Erzeugung eines Objekts aufgerufen und dienen der Initialisierung der Attribute. Wird für eine Klasse kein Konstruktor definiert, so erzeugt der Compiler einen Defaultkonstruktor, der die Attribute auf ihren Defaultwert initialisiert. 29 Überladen von Methoden class Clock { ... public void tick() { ... } public void tick(int nticks) { for (int i = 0; i < nticks; i++) tick(); } } Clock c = new Clock(); c.tick(10); Es ist möglich, in einer Klasse mehrere Methoden mit demselben Namen zu definieren, sofern sie sich in den Parametern unterscheiden. Bei einem Aufruf entscheidet dann der Compiler anhand der aktuellen Argumente, welche Methode ausgeführt wird (Argument Matching). 30 Mehrfache Konstruktoren class Clock { private int hour, min, sec; public Clock(int h, int m, int s) { hour = h; min = m; sec = s; } public Clock() { // Defaultkonstruktor this(0, 0, 0); } ... } Wie jede Methode kann auch der Konstruktor einer Klasse überladen werden. Dabei kann als erstes Statement in einem Konstruktor mittels this() ein anderer Konstruktor aufgerufen werden. 31 this-Referenz class Clock { private int hour, min, sec; public Clock(int hour, int min, int sec) { this.hour = hour; this.min = min; this.sec = sec; } ... } Jeder Methode wird implizit eine Referenz auf das Objekt übergeben, für das sie aufgerufen wird. Diese Referenz kann in der Methode mittels this angesprochen werden. Sie wird u.a. in Konstruktoren verwendet, um einen Namenskonflikt zwischen Attributen und Parametern aufzulösen. 32 Klassenattribute und -methoden class Clock { private static int count = 0; private int hour, min, sec; public static int getCount() { return count; } public Clock(int h, int m, int s) { hour = h; min = m; sec = s; count++; } ... } int nclocks = Clock.getCount(); Klassenattribute sind Attribute, die unabhängig von den Objekten nur einmal pro Klasse existieren. Sie werden zur Speicherung von Objekt-übergreifenden Daten verwendet. Alle Methoden der Klasse können darauf zugreifen. Klassenmethoden sind Methoden, die nicht auf ein bestimmtes Objekt angewendet sondern mit dem Klassennamen wie globale Funktionen aufgerufen werden. Sie können nur auf Klassenattribute zugreifen. 33 Konstante Attribute class Clock { private static final int MAX_HOUR = 24; private static final int MAX_MIN = 60; private static final int MAX_SEC = 60; private int hour, min, sec; ... void tick() { if (++sec == MAX_SEC) { sec = 0; if (++min == MAX_MIN) { min = 0; if (++hour == MAX_HOUR) hour = 0; } } } } Konstante Attribute müssen entweder bei ihrer Definition oder in den Konstruktoren der entsprechenden Klasse initialisiert werden. Danach kann ihr Wert nicht mehr verändert werden. 34 Zerstören von Objekten class Clock { private static int count = 0; ... public Clock(int h, int m, int s) { hour = h; min = m; sec = s; count++; } ... public void finalize() { count--; } } Der Speicherplatz nicht mehr gebrauchter Objekte wird vom Garbage Collector automatisch freigegeben. Dabei wird implizit die Methode finalize() aufgerufen, in der u.a. zusätzliche Ressourcen eines Objekts freigegeben werden können. 35 36 Vererbung 37 Ableiten einer Klasse class AlarmClock extends Clock { private int ahour, amin, asec; public void setAlarmTime(int h, int m, int s) { ahour = h; amin = m; asec = s; } } Clock AlarmClock Eine abgeleitete Klasse erbt alle Attribute und Methoden der Basisklasse, zu denen sie neue Attribute und Methoden hinzufügen kann. Eine abgeleitete Klasse ist also eine Erweiterung der Basisklasse und definiert somit einen zum Typ der Basisklasse kompatiblen Datentyp. 38 Konstruktorverkettung class AlarmClock extends Clock { private int ahour, amin, asec; public AlarmClock(int h, int m, int s, int ah, int am, int as) { super(h, m, s); ahour = ah; amin = am; asec = as; } ... } Konstruktoren werden nicht vererbt und müssen in der abgeleiteten Klasse neu implementiert werden. Als erstes Statement in einem Konstruktor der abgeleiteten Klasse kann mittels super() ein Konstruktor der Basisklasse aufgerufen werden. Fehlt ein solcher Aufruf, so wird implizit der Defaultkonstruktor der Basisklasse aufgerufen. 39 Zugriffsrecht protected class Clock { protected int hour, min, sec; ... } class AlarmClock extends Clock { ... private boolean alarmTimeArrived() { return hour == ahour && min == amin && sec == asec; } } Werden die Attribute oder Methoden einer Klasse als protected spezifiziert, so kann nur innerhalb der Klasse selbst und in den abgeleiteten Klassen auf sie zugegriffen werden. 40 Überschreiben von Methoden class AlarmClock extends Clock { private int ahour, amin, asec; ... public void tick() { if (++sec == 60) { sec = 0; if (++min == 60) { min = 0; if (++hour == 24) hour = 0; } } if (alarmTimeArrived()) beep(); } } Wird in einer abgeleiteten Klasse eine Methode mit demselben Namen und denselben Parametern wie in der Basisklasse definiert, so überschreibt die Methode der abgeleiteten Klasse diejenige der Basisklasse. In diesem Fall muss auch der Rückgabewert der beiden Methoden übereinstimmen. 41 Aufruf überschriebener Methoden class AlarmClock extends Clock { private int ahour, amin, asec; ... public void tick() { super.tick(); if (alarmTimeArrived()) beep(); } } Eine überschriebene Methode ist in der abgeleiteten Klasse nicht mehr direkt zugänglich. Über die Referenz super kann sie aber weiterhin aufgerufen werden. 42 Dynamische Bindung Clock[] clocks = new Clock[10]; clocks[0] = new Clock(); clocks[1] = new AlarmClock(); ... for (int i = 0; i < clocks.length; i++) clocks[i].tick(); Da der Typ der abgeleiteten Klasse kompatibel zum Typ der Basisklasse ist, kann eine Referenz der Basisklasse auch auf ein Objekt der abgeleiteten Klasse zeigen. Wird für eine solche Referenz eine in der abgeleiteten Klasse überschriebene Methode aufgerufen, so entscheidet der Typ des Objekts, auf das die Referenz zeigt, welche Methode ausgeführt wird. 43 Abstrakte Methoden und Klassen abstract class Figure { protected int x, y; public move(int dx, int dy) { ... } public abstract void draw(); ... } class Circle extends Figure { public void draw() { ... } } class Rectangle extends Figure { public void draw() { ... } } Abstrakte Methoden sind Methoden, die keine Implementation haben. Hat eine Klasse mindestens eine abstrakte Methode, so ist sie eine abstrakte Klasse. Eine abstrakte Klasse kann nicht instanziert werden, sie kann aber als Basisklasse anderer Klassen dienen. 44 Modifizierer final class Clock { public final int getHour() { return hour; } public final int getMin() { return min; } public final int getSec() { return sec; } ... } final class AlarmClock { ... } Wird eine Methode einer Klasse als final spezifiziert, so kann sie in einer abgeleiteten Klasse nicht überschrieben werden. Wird eine ganze Klasse als final spezifiziert, so können von ihr keine Klassen abgeleitet werden. 45 46 Objektmodell 47 Klassenhierarchie Object String Clock AlarmClock Component Button Container Window Dialog Panel Frame In Java gibt es keine Mehrfachvererbung und alle Klassen sind implizit von der Klasse Object abgeleitet. Die Klassenhierarchie bildet somit einen Baum mit der Klasse Object als Wurzel. 48 Klasse Object Object Object boolean String void void void void int Class clone() equals() toString() finalize() notify() notifyAll() wait() hashCode() getClass() Die Methoden der Klasse Object definieren ein gemeinsames Verhalten aller Objekte: - clone() und equals() werden für das Kopieren und Vergleichen von Objekten verwendet - toString() erzeugt eine Stringdarstellung eines Objekts - finalize() wird vom Garbage Collector aufgerufen, bevor ein Objekt zerstört wird - notify(), notifyAll() und wait() dienen der Synchronisation von Threads - hashCode() erzeugt einen Hashcode für ein Objekt - getClass() gibt die Klasse eines Objekts zurück 49 Vergleichen von Objekten class Clock { private int hour, min, sec; public boolean equals(Object obj) { if (!(obj instanceof Clock)) return false; Clock c = (Clock)obj; return hour == c.hour && min == c.min && sec == c.sec; } ... } Clock c1 = new Clock(...); Clock c2 = new Clock(...); if (c1.equals(c2)) ... Die Methode equals() der Klasse Object vergleicht die Referenzen zweier Objekte. Um den Zustand zweier Objekte zu vergleichen, muss die Methode equals() entsprechend überschrieben werden. In diesem Fall sollte auch die Methode hashCode() so überschrieben werden, dass sie für gleiche Objekte denselben Wert zurückgibt. 50 Kopieren von Objekten class Clock { private int hour, min, sec; public Object clone() { return new Clock(hour, min, sec); } ... } Clock c1 = new Clock(...); Clock c2 = (Clock)c1.clone(); Die Methode clone() der Klasse Object macht eine flache Kopie eines Objekts. Da sie protected ist, muss die Methode clone() in abgeleiteten Klassen überschrieben werden, insbesondere wenn tiefe Kopien von Objekten erzeugt werden sollen. 51 Stringdarstellung von Objekten class Clock { private int hour, min, sec; public String toString() { return hour + ":" + min + ":" + sec; } ... } Clock c = new Clock(...); System.out.println("Time: " + c); Die Methode toString() der Klasse Object erzeugt einen String bestehend aus dem Klassennamen und dem Hashcode eines Objekts. Um eine andere Stringdarstellung von Objekten zu erzeugen, kann die Methode toString() überschrieben werden. Diese wird dann zum Beispiel vom Konkatenationsoperator implizit aufgerufen. 52 Generische Klassen class Buffer { private Object[] elements; ... public void put(Object obj) { ... } public Object get() { ... } } Buffer b = new Buffer(); b.put(new Clock(...)); ... Clock c = (Clock)b.get(); Da die Klasse Object der allgemeinste Typ ist, kann sie als Objekttyp für generische Klassen verwendet werden. So können zum Beispiel Container-Klassen Objekte beliebigen Typs aufnehmen. Beim Herauslesen müssen dann die Objekte mittels Casting wieder in ihren ursprünglichen Typ umgewandelt werden. Dabei wird aber vom Runtime-System die Typkompatibilität geprüft. 53 54 Packages 55 Einführung • • • Packages sind Sammlungen von Klassen Packages definieren Namensräume Packages sind hierarchisch aufgebaut 56 Definieren eines Package package math.number; public class Complex { public double re, im; ... } math number Complex Um ein Package zu definieren, wird am Anfang eines Sourcefiles ein package-Statement eingefügt. Die nachfolgend definierten Klassen gehören dann automatisch zu diesem Package. Ohne package-Statement gehören die Klassen zum Default-Package ohne Namen. 57 Importieren eines Package package math.util; import math.number.Complex; import math.number.*; // alle Klassen public class Calculator { public static Complex square(Complex z) { Complex v = new Complex(); v.re = z.re * z.re - z.im * z.im; v.im = z.re * z.im + z.im * z.re; return v; } ... } Der vollständige Name einer Klasse setzt sich aus dem Packageund dem Klassennamen zusammen. Ein import-Statement macht aber einzelne oder alle Klassen eines Package sichtbar, so dass sie mit dem Klassennamen allein verwendet werden können. Defaultmässig werden die Klassen aus dem Package java.lang importiert. 58 Directory-Struktur und Klassenpfad Directory-Struktur: C:\src\Calculator.java C:\bin\math\number\Complex.class Kompilation und Ausführung: C:\> set CLASSPATH=bin;... C:\> javac -d bin src\Calculator.java C:\> java math.util.Calculator Die Package-Hierarchien müssen sich in der Directory-Struktur des Filesystems widerspiegeln. Dies bedeutet, dass eine kompilierte Klasse in einem Directory abgelegt wird, dessen Pfad dieselben Komponenten hat wie das Package, zu dem sie gehört. In der Umgebungsvariablen CLASSPATH können die RootDirectories der Package-Hierarchien spezifiziert werden. 59 Zugriffsrechte gleiches Package Subklasse gleiches Package Nicht-Subklasse anderes Package Subklasse anderes Package Nicht-Subklasse default public protected private ja ja ja nein ja ja ja nein nein ja ja nein nein ja nein nein Da Packages Namensräume definieren, haben sie auch einen Einfluss auf die Sichtbarkeit von Attributen und Methoden. Zudem kann eine Klasse nur dann ausserhalb ihres Package verwendet werden, wenn sie als Klasse public spezifiziert wird. 60 Aufbau eines Java-Sourcefiles Ein Java-Sourcefile enthält: 1. Ein package-Statement (optional) 2. Beliebig viele import-Statements (optional) 3. Eine public-Klasse 4. Beliebig viele Package-interne Klassen (optional) Eine public-Klasse muss in einem Sourcefile definiert werden, das denselben Namen wie die Klasse hat. Deshalb kann in einem Sourcefile nur eine public-Klasse definiert werden. Der Compiler erzeugt für jede Klasse ein eigenes Klassenfile. 61 62 Interfaces 63 Einführung • • • Ein Interface beschreibt das Verhalten von Klassen als Sammlung von abstrakten Methoden Ein Interface kann von mehreren Klassen implementiert werden Eine Klasse kann mehrere Interfaces implementieren Klasse1 Client Interface Klasse2 64 Definieren eines Interface public interface Comparable { int compareTo(Object obj); } Ein Interface definiert einen abstrakten Datentyp. Die Methoden eines Interface sind implizit public und abstract. Sie können keine Modifikatoren haben, die Eigenschaften der Implementierung definieren (zum Beispiel synchronized). 65 Implementieren eines Interface public class Rational extends Number implements Comparable { public int p, q; ... public int compareTo(Object obj) { Rational r = (Rational)obj; return p * r.q - q * r.p; } } Number Rational Comparable Eine Klasse implementiert ein Interface, indem sie jede Methode des Interface implementiert. Sie definiert damit einen zum Typ des Interface kompatiblen Datentyp. 66 Verwenden eines Interface public class Util { public static void sort(Comparable[] x) { for (int i = x.length - 1; i > 0; i--) for (int j = 0; j < i; j++) if (x[j].compareTo(x[j + 1]) > 0) { Object t = x[j]; x[j] = x[j + 1]; x[j + 1] = t; } } } Rational[] r = new Rational[10]; ... Util.sort(r); Interfaces können u.a. als Parameter generischer Methoden verwendet werden, die auf ein bestimmtes Verhalten der Objekte angewiesen sind. Einer solchen Methode können dann Objekte irgendeiner Klasse, die das Interface implementiert, übergeben werden. 67 Interface-Konstanten public interface Verbose { int SILENT = 0; int TERSE = 1; int NORMAL = 2; int VERBOSE = 3; int getVerbosity(); void setVerbosity(int level); } Ein Interface kann auch Attribute definieren, diese sind aber implizit public, static und final. Zudem müssen die Attribute initialisert werden. 68 Vererbung von Interfaces public interface Figure { void move(int dx, int dy); void rotate(int angle); void draw(Graphics g); } interface ClosedFigure extends Figure { void fill(Color c); } Figure ClosedFigure Circle Ein Interface kann von einem anderen Interface abgeleitet werden. Dadurch erbt es alle Methoden und Konstanten des Basis-Interfaces. Es ist sogar Mehrfachvererbung erlaubt. 69 70 Exception-Handling 71 Einführung • • • Exceptions ermöglichen, die Fehlerbehandlung vom normalen Ablauf einer Methode zu trennen Exceptions werden automatisch im Callstack eines Programms nach oben propagiert Exceptions können detaillierte Informationen über den aufgetretenen Fehler enthalten Wird eine Exception nirgends abgefangen, so gibt das RuntimeSystem eine Fehlermeldung und den aktuellen Callstack aus und bricht das Programm ab. 72 Exception-Klassen Exception Runtime Exception IndexOut OfBounds Exception NullPointer Exception IOException Security Exception Exceptions sind Objekte, die Informationen über den aufgetretenen Fehler enthalten. Diese Information besteht primär aus dem Typ der Exception. Entsprechend gibt es in der JavaKlassenbibliothek eine grosse Anzahl vordefinierter ExceptionKlassen. 73 Definieren von Exceptions public class OutOfRangeException extends Exception { public OutOfRangeException(String message) { super(message); } ... } Eigene Exceptions können definiert werden, indem von der Klasse Exception neue Klassen abgeleitet werden. Wird dabei dem Konstruktor der Klasse Exception eine Fehlermeldung übergeben, so kann diese über die Methode getMessage() wieder abgefragt werden. 74 Weitergeben von Exceptions public class PrimeTest { ... public static int readInt() throws IOException { BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); String line = stdin.readLine(); return Integer.parseInt(line); } } Ruft eine Methode eine andere Methode auf, die eine Exception wirft, so muss sie die Exception abfangen oder weitergeben. Im zweiten Fall muss die aufrufende Methode die Exception in der throws-Klausel deklarieren. Davon ausgenommen sind die Runtime-Exceptions, die auch ohne Deklaration weitergegeben werden. 75 Werfen von Exceptions public class PrimeTest { ... public static void checkRange(int value, int min, int max) throws OutOfRangeException { if (value < min || value >= max) throw new OutOfRangeException( value + " not between " + min + " and " + max); } } Tritt in einer Methode ein Fehler auf, so kann mittels throw eine Exception geworfen und dadurch die Methode unmittelbar verlassen werden. Wirft eine Methode eine Exception, so muss diese in der throws-Klausel der Methode deklariert werden. 76 Abfangen von Exceptions public class PrimeTest { public static void main(String[] args) throws IOException { try { int n = readInt(); checkRange(n, 1, 1000000); ... } catch (NumberFormatException e) { System.out.println("Invalid number format"); } catch (OutOfRangeException e) { System.out.println(e.getMessage()); } finally { ... } } } Das Abfangen und Behandeln von Exceptions geschieht mit einem try-catch-Statement. Trifft in dem durch try definierten Codeblock eine Exception ein, so wird der normale Programmfluss unterbrochen und das erste auf den try-Block folgende catch-Statement ausgeführt, dessen Parametertyp mit dem Typ der geworfenen Exception übereinstimmt. Optional kann das try-catch-Statement durch einen finally-Block ergänzt werden, der in jedem Fall nach dem Verlassen des tryBlocks ausgeführt wird. 77 78 Ein- und Ausgabe 79 Aufbau der Bibliothek Byte-Streams Character-Streams Ein-/AusgabeStreams VerarbeitungsStreams Die Ein- und Ausgabe erfolgt in Java über sogenannte Streams. Diese lassen sich je nach Typ der zu lesenden oder zu schreibenden Daten in Byte-Streams und Character-Streams unterteilen. Ausserdem gibt es Streams, welche die eigentliche Ein- und Ausgabe der Daten von bzw. in Datenquellen ausführen, und solche, welche die Daten nach dem Lesen oder vor dem Schreiben verarbeiten. 80 Ein-/Ausgabe-Streams Datenquelle Byte-Streams Character-Streams File FileInputStream FileOutputStream ByteArrayInputStream ByteArrayOutputStream FileReader FileWriter CharArrayReader CharArrayWriter StringReader StringWriter PipedReader PipedWriter Array String Pipe PipedInputStream PipedOutputStream Piped-Streams implementieren sogenannte Pipes, die für den Datenaustausch zwischen Threads verwendet werden. 81 Verarbeitungs-Streams Verarbeitung Byte-Streams Character-Streams Pufferung BufferedInputStream BufferedOutputStream FilterInputStream FilterOutputStream BufferedReader BufferedWriter FilterReader FilterWriter InputStreamReader OutputStreamWriter Filterung Konversion Verkettung ObjektSerialisierung Datenkonversion Nummerierung Vorausschauen Drucken SequenceInputStream ObjectInputStream ObjectOutputStream DataInputStream DataOutputStream LineNumberReader PushbackInputStream PushbackReader PrintStream PrintWriter Buffered-Streams puffern die zu lesenden und zu schreibenden Daten. Filter-Streams werden verwendet, um Daten zu filtern. InputStreamReader und OutputStreamWriter wandeln die gelesenen Bytes in Characters bzw. die zu schreibenden Characters in Bytes umwandeln. SequenceInputStream verkettet mehrere Input-Streams zu einem Input-Stream. ObjectInputStream und ObjectOutputStream werden verwendet, um Objekte in serialisierter Form einzulesen bzw. auszugeben. DataInputStream und DataOutputStream lesen und schreiben primitive Datentypen in binärer Form. LineNumberStream kennt die Anzahl Zeilen, die gelesen wurden. PushbackInputStream und PushbackReader ermöglichen das Zurüchschreiben eines gelesenen Bytes bzw. Characters. PrintStream und PrintWriter enthalten Methoden zur Ausgabe von primitiven Datentypen in lesbarer Form. 82 Verketten von Streams File FileInput Stream DataInput Stream Program Ein Entwurfsprinzip der Ein-/Ausgabe-Bibliothek besteht darin, dass sich sowohl die Eingabe- als auch die Ausgabe-Streams fast beliebig miteinander verknüpfen lassen. Am Anfang bzw. Ende einer solchen Verarbeitungskette steht jeweils ein eigentlicher Ein- oder Ausgabe-Stream. 83 Standard-Ein-/Ausgabe import java.io.*; public class Echo { public static void main(String[] args) throws IOException { BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); PrintWriter stdout = new PrintWriter(System.out, true); while (true) { String line = stdin.readLine(); if (line.equals(".")) break; stdout.println(line); } } } Das obige Programm liest Textzeilen von der Standard-Eingabe (System.in) ein und gibt diese wieder auf die Standard-Ausgabe (System.out) aus. 84 Kopieren eines Files import java.io.*; public class Copy { public static void main(String[] args) throws IOException { FileInputStream fin = new FileInputStream(args[0]); FileOutputStream fout = new FileOutputStream(args[1]); byte[] buffer = new byte[1024]; while (true) { int nbytes = fin.read(buffer); if (nbytes == -1) break; fout.write(buffer, 0, nbytes); } fin.close(); fout.close(); } } Das obige Programm kopiert binär ein File auf ein anderes. Die Namen der beiden Files werden als Argumente übergeben. 85 Objekt-Serialisierung import java.io.*; import java.util.*; class Message implements Serializable { Date time = new Date(); String text; } class PostIt { public static void main(String[] args) throws Exception { BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); Message msg = new Message(); msg.text = stdin.readLine(); ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(args[0])); out.writeObject(msg); out.close(); } } Das obige Programm schreibt eine Meldung bestehend aus dem aktuellen Datum und einer eingelesenen Textzeile in ein File. Damit die Objekte einer Klasse serialisiert werden können, muss die Klasse das leere Interface Serializable implementieren. Enthält ein Objekt Referenzen auf andere Objekte, so werden diese bei der Ausgabe automatisch mitserialisiert und beim Einlesen wieder deserialisiert. 86 Objekt-Serialisierung (ff.) class GetIt { public static void main(String[] args) throws Exception { ObjectInputStream in = new ObjectInputStream( new FileInputStream(args[0])); Message msg = (Message)in.readObject(); System.out.println(msg.time + "\n" + msg.text); in.close(); } } Das obige Programm liest eine zuvor gespeicherte Meldung aus einem File und gibt sie am Bildschirm aus. 87 88 Threads 89 Einführung Hauptthread start() Thread 1 start() Thread 2 join() Threads sind parallele Ablaufeinheiten innerhalb eines Prozesses. Sie werden u.a. eingesetzt bei - Ein-/Ausgabe-Operationen, die blockieren können - der Behandlung externer Ereignisse - der Ausführung periodischer Aufgaben 90 Klasse Thread Thread void void void void void void void run() start() join() sleep() interrupt() setPriority() yield() In Java werden Threads durch Objekte der Klasse Thread repräsentiert. Ein Thread-Objekt enthält einerseits in der run()Methode den Code, der im zugehörigen Thread ausgeführt werden soll. Andererseits kann über das Thread-Objekt der zugehörige Thread gesteuert werden: - start() startet den Thread - join() wartet auf die Terminierung des Threads - sleep() unterbricht die Ausführung des Threads für eine bestimmte Zeit - interrupt() setzt ein internes Flag, um den Thread vorzeitig zu beenden - setPriority() bestimmt die Priorität des Threads - yield() bewirkt einen Kontext-Switch, so dass andere Threads laufen können 91 Thread-Zustände yield() start() Created Runnable Dead NonRunnable Ein Thread durchläuft während seiner Lebensdauer verschiedene Zustände: - Created: das Thread-Objekt existiert, aber der zugehörige Thread wurde noch nicht gestartet - Runnable: der Thread ist lauffähig und läuft, wann immer ihm der Scheduler Prozessorzeit zuteilt - Non-Runnable: der Thread ist nicht lauffähig, weil er zum Beispiel in einer Ein-/Ausgabe-Operation blockiert ist oder vorübergehend unterbrochen wurde - Dead: der Thread hat terminiert, indem er die run()-Methode verlassen hat 92 Erzeugen von Threads (Variante 1) public class CounterThread extends Thread { private int max; public CounterThread (int max) { this.max = max; } public void run() { for (int i = 1; i <= max; i++) System.out.println(i); } } CounterThread thread = new CounterThread(100); thread.start(); ... thread.join(); Um einen Thread zu erzeugen, muss eine von Thread abgeleitete Klasse definiert und darin die run()-Methode überschrieben werden. Dann wird die Klasse instanziert und vom entsprechenden Thread-Objekt die start()-Methode aufgerufen. Diese erzeugt den eigentlichen Thread, der die run()-Methode des Thread-Objekts ausführt. 93 Erzeugen von Threads (Variante 2) public class Counter implements Runnable { private int max; public Counter(int max) { this.max = max; } public void run() { for (int i = 1; i <= max; i++) System.out.println(i); } } Counter counter = new Counter(100); Thread thread = new Thread(counter); thread.start(); ... thread.join(); Um ein Thread-Objekt zu erzeugen, kann auch die Klasse Thread selbst instanziert werden. In diesem Fall muss dem Konstruktor ein Objekt einer Klasse übergeben werden, die das Interface Runnable implementiert. 94 Beenden eines Threads public class InfiniteCounter implements Runnable { public void run() { int i = 1; while (!Thread.interrupted()) { try { Thread.sleep(1000); } catch (InterruptedException e) { break; } System.out.println(i); i++; } } } InfiniteCounter counter = new InfiniteCounter(); Thread thread = new Thread(counter); thread.start(); ... thread.interrupt(); Um einen Thread vorzeitig zu beenden, kann mittels interrupt() im Thread-Objekt ein internes Flag gesetzt werden, welches der Thread in seiner run()-Methode mittels interrupted() periodisch prüft. Ist der Thread in einem join(), sleep()- oder wait()-Aufruf blockiert, so wird das Flag nicht gesetzt sondern eine InterruptedException geworfen. 95 Synchronisieren von Methoden public class Buffer { private Object[] elements; private int size = 0; public Buffer(int capacity) { elements = new Object[capacity]; } public synchronized void put(Object obj) { elements[size++] = obj; } public synchronized Object get() { Object obj = elements[0]; for (int i = 0; i < size - 1; i++) elements[i] = elements[i + 1]; size--; return obj; } } Um Zugriffskonflikte auf die Attribute eines Objekts zu vermeiden, können die Methoden einer Klasse mit dem Schlüsselwort synchronized versehen werden. Dies bewirkt, dass für dasselbe Objekt immer nur ein Thread gleichzeitig eine der entsprechenden Methoden ausführen kann. 96 Warten auf eine Bedingung public class Buffer { ... public synchronized void put(Object obj) { elements[size++] = obj; notify(); } public synchronized Object get() { while (size == 0) try { wait(); } catch (InterruptedException e) {} Object obj = elements[0]; for (int i = 0; i < size - 1; i++) elements[i] = elements[i + 1]; size--; return obj; } } Mittels wait() kann ein Thread innerhalb einer synchronisierten Methode auf eine Bedingung warten, bis diese von einem andern Thread mittels notify() signalisiert wird. Warten mehrere Threads innerhalb desselben Objekts, so wird durch notify() nur einer dieser Threads geweckt. Mittels notifyAll() können alle wartenden Threads geweckt werden. 97 Scheduling • • • Jeder Thread hat eine Priorität zwischen 1 und 10 Threads mit hoher Priorität sollten bei einem Kontext-Switch bevorzugt werden Ein Kontext-Switch findet statt, wenn der laufende Thread den Prozessor freiwillig abgibt oder vorzeitig unterbrochen wird Der laufende Thread gibt den Prozessor freiwillig ab, wenn er nicht mehr lauffähig ist oder die Methode yield() aufruft. Er kann vom Scheduler vorzeitig unterbrochen werden, wenn ein anderer Thread mit höherer Priorität lauffähig wird (Preemptive PriorityScheduling) oder sein Zeitquantum abgelaufen ist (Time Slicing). 98