Programmieren in Java - Berner Fachhochschule

Werbung
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
Herunterladen