Einführung in die OOP in Java 6 WS 07/08

Werbung
Einführung in die OOP in Java 6
WS 07/08
Ashraf Abu Baker
Zuletzt aktualisiert am: 09.06.2008 15:32
1
Ich bedanke mich bei Igor Geier und Gerold Kühne für
ihre Unterstützung bei der Erstellung dieses Begleitmaterials.
2
Inhaltsverzeichnis
Inhaltsverzeichnis ..................................................................................................................................... 3
1 Einleitung............................................................................................................................................... 5
2 Historie .................................................................................................................................................. 6
3 Entwicklung von Java-Programmen ...................................................................................................... 9
3.1 Vorbereitung ................................................................................................................................... 9
3.2 Klassendefinition ............................................................................................................................ 9
3.3 Kommentare.................................................................................................................................. 10
3.4 Applikationen vs. Applets............................................................................................................. 11
4 Grundlagen........................................................................................................................................... 12
4.1 Java-Bezeichner (identifier).......................................................................................................... 12
4.2 Variablen und Datentypen ............................................................................................................ 12
4.2.1 Deklaration und Initialisierung .............................................................................................. 15
4.2.2 Wrapper-Klassen.................................................................................................................... 16
4.2.3 Arithmetik- und Fließkommaprobleme ................................................................................. 16
4.3 Typkonvertierung und Casting ..................................................................................................... 18
4.4 Autoboxing (boxing/unboxing) .................................................................................................... 20
5 Arrays................................................................................................................................................... 20
5.1 Definition ...................................................................................................................................... 21
5.2 Irreguläre Arrays .......................................................................................................................... 22
5.3 Zugriff auf Arrays ........................................................................................................................ 23
6 Kontrollstrukturen ............................................................................................................................... 24
6.1 if-else............................................................................................................................................. 24
6.2 switch ............................................................................................................................................ 25
6.3 Schleifen ....................................................................................................................................... 26
6.3.1 while-Schleife ........................................................................................................................ 26
6.3.2 do … while-Schleife .............................................................................................................. 28
6.3.3 for-Schleife ............................................................................................................................ 29
6.3.4 for-each-Schleife ................................................................................................................... 30
6.4 break und continue........................................................................................................................ 31
7 Grundkonzepte OOP............................................................................................................................ 34
7.1 Klassen und Objekte ..................................................................................................................... 34
7.2 Klassen in Java.............................................................................................................................. 34
7.3 Methoden und Konstruktoren ....................................................................................................... 35
7.4 Überladen von Methoden.............................................................................................................. 36
7.5 Vererbung ..................................................................................................................................... 38
7.6 Überschreibung von Methoden..................................................................................................... 40
7.7 Polymorphismus ........................................................................................................................... 41
8 Java-spezifische Konzepte ................................................................................................................... 43
8.1 Referenzen in Java ........................................................................................................................ 43
8.2 this, super, this() und super() ........................................................................................................ 44
8.3 Klassenvariablen (statische Variablen)......................................................................................... 45
8.4 Statische Methoden (Klassenmethoden)....................................................................................... 47
8.5 Die main-Methode ...................................................................................................................... 47
8.6 Statische Initialisierer.................................................................................................................... 47
8.7 Nicht statische Initialisierer .......................................................................................................... 48
8.8 Initialisierungsreihenfolge ............................................................................................................ 48
8.9 Call-by-reference oder call-by-value? .......................................................................................... 50
3
8.10 Pakete (packages)........................................................................................................................ 51
8.11 Import.......................................................................................................................................... 52
8.12 Statischer Import......................................................................................................................... 53
8.13 Datenkapselung und Modifier .................................................................................................... 53
8.13.1 public.................................................................................................................................... 54
8.13.2 private .................................................................................................................................. 54
8.13.3 default .................................................................................................................................. 54
8.13.4 protected............................................................................................................................... 54
8.13.5 final ..................................................................................................................................... 57
9 Abstrakte Klassen und Methoden ....................................................................................................... 58
10 Interfaces............................................................................................................................................ 61
10.1 Das Interface Comparable........................................................................................................... 62
11 Namenskonventionen......................................................................................................................... 64
12 Object, Math und String..................................................................................................................... 65
12.1 Object ......................................................................................................................................... 65
12.2 Math ............................................................................................................................................ 67
12.3 String .......................................................................................................................................... 67
13 Ausnahmen (Exceptions) ................................................................................................................... 72
13.1 Throwable .................................................................................................................................. 74
13.2 Ausnahmetypen........................................................................................................................... 74
13.3 Auslösen einer Exception .......................................................................................................... 77
13.4 Weitergabe von Exceptions ....................................................................................................... 77
13.5 Behandlung von mehreren Ausnahmen ..................................................................................... 78
13.6 finally ......................................................................................................................................... 78
13.7 Eigene Exception-Klasse deklarieren ......................................................................................... 79
14 Generics ............................................................................................................................................. 80
15 Collections (Sammlungen)................................................................................................................. 85
15.1 Listen........................................................................................................................................... 86
15.1.1 ArrayList und Vector ........................................................................................................... 88
15.1.2 LinkedList ............................................................................................................................ 89
15.2 Mengen (Sets) ............................................................................................................................. 90
15.2.1 HashSet ................................................................................................................................ 90
15.2.2 LinkedHashSet..................................................................................................................... 91
15.2.3 TreeSet ................................................................................................................................. 91
15.3 Tabellen (Maps) .......................................................................................................................... 92
15.3.1 HashMap und Hashtable ...................................................................................................... 94
15.3.2 TreeMap .............................................................................................................................. 94
15.3.3 LinkedHashMap .................................................................................................................. 95
15.4 Generische Collections ............................................................................................................... 96
15.5 Iteratoren (Iterator)...................................................................................................................... 97
16 I/O ...................................................................................................................................................... 99
16.1 Dateien und Verzeichnisse.......................................................................................................... 99
16.2 Wahlfreier Zugriff auf Dateien (RandomAccessFile) .............................................................. 102
16.3 Streams...................................................................................................................................... 104
16.3.1 ByteStreams ....................................................................................................................... 104
16.3.2 FileReader .......................................................................................................................... 105
16.3.3 BufferedReader .................................................................................................................. 106
16.3.4 FileWriter........................................................................................................................... 107
16.3.5 BufferedWriter................................................................................................................... 108
17 Applets ............................................................................................................................................. 109
18 AWT/Swing ..................................................................................................................................... 117
4
1 Einleitung
Java ist eine von der Firma Sun Microsystems entwickelte Technologie, die aus einer modernen
objektorientierten Programmiersprache und einer Plattform besteht.
Eine Plattform ist eine Hardware- oder Softwareumgebung, in der Programme gestartet und ausgeführt
werden können. In vielen Fällen kann eine Plattform als das Betriebssystem und die darunter liegende
Hardware verstanden werden. Zu den bekanntesten Plattformen zählen Microsoft Windows, Linux,
Mac OS und Solaris. Die Java-Plattform unterscheidet sich von den meisten Plattformen dadurch, dass
sie nur eine Software-Plattform ist, und auf der Spitze einer hardwarebasierten Plattform läuft. Sie
besteht aus der Java-API (Java Application Programming Interface) und der Java-VM (Java Virtual
Machine).
Abbildung 1: Java-Plattform
Die Java-API ist eine große Sammlung von Java-Programmen, die in sog. Pakete (packages) aufgeteilt
sind. Pakete sind vergleichbar mit Bibliotheken in anderen Programmiersprachen und umfassen u.a.
Klassen- und Schnittstellendefinitionen (interfaces).
Abbildung 2: Dokumentation zur Java-API
5
Die Java-VM ist eine plattformunabhängige Softwareumgebung, die Java-Bytecode interpretiert, in
Maschinensprache übersetzt und ausführt. Sie kann als Interpretier- und Ausführungsumgebung für
Bytecode verstanden werden. Der Bytecode ist eine Datei, die die Erweiterung „.class“ hat. Sie
entsteht, wenn ein Java-Compiler ein Java-Programm kompiliert und übersetzt. Ein Bytecode ist das
einzige Codeformat, das von der virtuellen Maschine interpretiert und ausgeführt werden kann.
Abbildung 3: Bytecode/JVM
Da Java-Programme von der JVM und nicht direkt von der Hardware ausgeführt werden, sind sie
plattformunabhängig.
6
2 Historie
Die Geschichte der Java-Plattform begann im Januar 1996 als Sun Microsystems das Java
Development Kit (JDK) 1.0 herausgegeben hat. Dieses JDK bestand aus einer Klassenbibliothek,
einem Compiler und einem Interpreter. Die Klassenbibliothek des JDK 1.0 bestand aus lediglich sechs
Paketen: java.applet mit den Basisklassen für Applets, java.awt für grafische Oberflächen, java.io mit
I/O-Funktionen, java.lang, das den Kern der Sprache bildet, java.net mit Klassen zur
Netzwerkkommunikation sowie java.util mit nützlichen Hilfsklassen.
1997 trennte Sun den Interpreter vom JDK und gab eine neue Version (1.1) des JDK und eine
Laufzeitumgebung (Java Runtime Environment (JRE)) heraus. Die JRE enthält alle zum Ausführen
von Programmen notwendigen Werkzeuge.
Neu in dieser Version war die Entwicklung der Remote Methode Invocation (RMI), die Java Database
Connectivity (JDBC) und das AWT. Die RMI ist eine Schnittstelle, die eine einfache Verteilung von
Java-Programmen erlaubt. JDBC ist eine Schnittstelle zum Datenbankzugriff. Das AWT ist eine
Bibliothek zur Gestaltung von graphischen Oberflächen.
Da das AWT viele Probleme mit sich brachte, entwickelte Sun 1998 eine neue API zur Erstellung von
graphischen Oberflächen namens Swing und gab damit die Version 1.2 heraus.
http://java.sun.com/developer/technicalArticles/RoadMaps/Features/
Im Jahre 2000 brachte Sun die Version 1.3 auf den Markt, die u.a. das Servlet Development Kit und die
HotSpot Virtual Machine enthielt. Servlets sind Java-Programme, die in einem Web-Server laufen und
Web-Seiten dynamisch generieren.
http://java.sun.com/j2se/1.3/docs/relnotes/features.html
2002 wurde Version 1.4 herausgegeben. Zu den wichtigsten funktionalen Erweiterungen dieser
Version zählen die API für XML-Processing und eine neue API für schnelle I/O-Zugriffe auf Dateien.
http://java.sun.com/j2se/1.4.2/docs/relnotes/features.html
Im JDK 1.5 vom Jahre 2004 wurde die Syntax der Programmiersprache erweitert und neue wichtige
Konzepte wie Generics, Boxing/Unboxing sowie Aufzählungstypen und Annotationen eingeführt.
http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html
Die aktuellste Version des JDK, auf die sich diese Einführung bezieht, ist die Version 1.6. Sie wurde
Ende 2006 herausgegeben und brachte viele Neuerungen in den Bereichen Web Services, Sicherheit,
Datenbanken, etc. gegenüber ihrer Vorgängerversion mit sich. Darüber hinaus bietet sie zahlreiche
Klassen zum Einbetten von Skripten in Skriptsprachen wie JavaScript.
http://java.sun.com/developer/technicalArticles/J2SE/Desktop/javase6/beta2.htm
7
Jahr
1996
1997
1998
Version
1.0
1.1
1.2
Pakete
7
22
59
Klassen
210
ca. 600
1524
2000
1.3
76
1840
2002
2004
1.4
1.5
135
165
2723
3279
2006
1.6
202
3777
Neue Features
Applets, AWT, I/O, Net
RMI, JDBC,…
Swing, IDL,
Collections,…
CORBA ORB, Sound,
JNDI, Servlets…
XML Processing, JNI,…
Generics, Enhanced for
Loop, Autoboxing,
Enums, Annotations…
Scripting, Compiler
Access, Desktop
Deployment…
Tabelle 1: JDK-Entwicklung
8
3 Entwicklung von Java-Programmen
3.1 Vorbereitung
Zur Entwicklung von Java-Programmen wird das Java Development Kit und in der Regel eine
Entwicklungsumgebung benötigt. Eine Entwicklungsumgebung ist eine Sammlung von komplexen
Programmiertools zur Entwicklung von Software-Produkten und Programmen. Die während des
Kurses eingesetzte Entwicklungsumgebung heißt Eclipse. Sie kann von der folgenden Internet-Seite
heruntergeladen werden: www.eclipse.org
Das JDK 6.0 kann von der Sun-Seite unter der folgenden Adresse heruntergeladen werden:
http://java.sun.com/javase/downloads/index.jsp
3.2 Klassendefinition
Eine Java-Quellcodedatei besteht aus höchstens einer public-Klassendefinition, genannt public top
level class definition, und einer unbeschränkten Anzahl von nicht public-Klassendefinitionen (non
public top level class definitions), die in einer Java-Quelldatei gespeichert werden. Java-Quelldateien
sind Unicode-Dateien, die mit der Erweiterung „.java“ gespeichert werden. Enthält ein Java-Programm
eine public top level-Klasse, die beispielsweise MyJavaProgram heißt, so muss seine Quelldatei
MyJavaProgram.java heißen:
/**
* A Java program containing 1 public and 2 non public class
* definitions.
*/
public class MyJavaProgram {// public class definition
public static void main(String [] input) {
System.out.println("Hi, this is my first Java program!");
}
}
class MyFirstClass {// non public class definition
}
class MySecondClass {// non public class definition
}
9
Ein Programm kann drei sog. top-level-Kompiliereinheiten enthalten:
1. Paket-Deklaration (package declaration)
2. Import-Anweisungen (import statements)
3. Klassen- oder Interface-Definitionen
package mypackage;// package declaration
import javax.swing.JButton;// import statement
import javax.swing.JPanel;// import statement
/*
* A top-level class definition
*/
public class MyClass{
//class body (Klassenrumpf)
}
/*
* A top-level interface definition
*/
interface MyInterface{
//Interface-Body (Interface-Rumpf)
}
Alle genannten Kompiliereinheiten sind optional. Wenn sie jedoch in einer Java-Quelldatei
vorkommen, dann müssen sie in der obigen Reihenfolge erscheinen. Pakete dienen der logischen
Gruppierung und Organisation von Klassen und Interfaces und entsprechen
Programmierbibliotheken in anderen Programmiersprachen. Import-Anweisungen dienen der
Verfügbarkeitsmachung von (Hilfs)klassen aus anderen Paketen.
3.3 Kommentare
Zur Kommentierung von Java-Programmen unterstützt Java drei Arten von Kommentaren:
1. Zeilenkommentare:
Ein Zeilenkommentar ist genau eine Zeile lang:
// import list
10
2. Blockkommentare:
Blockkommentare können sich über mehrere Zeilen strecken:
/*
* A top-level class definition
*/
3. Dokumentationskommentare:
Das javadoc-Tool benutzt Dokumentationskommentare, um eine
Dokumentation für Klassen und Interfaces automatisch zu generieren. Sie beginnen mit “/**“ und
enden mit „*/“
/**
* This is a class which prints a string on the console.
*/
3.4 Applikationen vs. Applets
Es gibt zwei Arten von Java-Programmen: Applikationen und Applets.
Eine Applikation ist ein Java-Programm, das als eigenständige Anwendung (stand-alone application)
von einer auf einem Rechner installierten JVM ausgeführt werden kann.
Applets sind Java-Programme, die in HTML-Seiten eingebettet werden und von einem Java-fähigen
Web-Browser als Web-Anwendungen gestartet werden.
Der wesentliche Unterschied zwischen Applets und Applikationen ist, dass Applets einen Java-fähigen
Browser benötigen. Ein Browser ist Java-fähig, wenn die JVM als Plug-in für ihn installiert ist.
Applets werden weiter unten ausführlich behandelt.
Es ist die Aufgabe des Programmierers festzulegen, ob sein Programm als Applikation, als Applet oder
auch als beides, funktionieren soll.
Jedes Programm, das als eigenständige Applikation ausgeführt werden soll, muss eine sog. mainMethode enthalten, die als Startpunkt der Applikation dient:
//Signatur der main-Methode
public static void main(String [] input) {}
//bzw. (seit JDK 5)
public static void main(String ... input) {}
Wenn eine Applikation gestartet werden soll, wird ihre Klasse in die JVM geladen. Die JVM
durchsucht dann die Klasse nach der main-Methode. Wird die main-Methode gefunden, übergibt ihr
die JVM ein Parameter-Array und fängt anschließend mit der sequenziellen Ausführung der in ihr
11
enthaltenen Anweisungen an. Das Parameter-Array enthält ggf. die Eingabeparameter, die an das
Programm übergeben werden sollen. Enthält die Klasse keine solche Methode, so wird eine
Fehlermeldung ausgegeben.
12
4 Grundlagen
4.1 Java-Bezeichner (identifier)
Ein Java-Bezeichner ist eine beliebig lange Folge aus Buchstaben, Ziffern und den Unicode-Zeichen $
und _. Ein Bezeichner muss mit einem Buchstaben, einem $-Zeichen oder einem Unterstrich beginnen
und kann eine beliebige Kombination der o.g. Zeichen enthalten. Schlüsselwörter der
Programmiersprache Java dürfen nicht als Bezeichner verwendet werden.
Bezeichner werden von Programmierern zur Benennung von Variablen, Methoden, Klassen, Interfaces,
Paketen und Labels verwendet und sind case-sensitive.
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
minValue;//legal
$minValue;//legal
_minValue;//legal
$=2;//legal
$_=5;//legal
_=3;//legal
_3=3;//legal
a3;//legal
3a;//illegal
!maxValue;//illegal
–maxValue;//illegal
min-Value;//illegal
min!Value;//illegal
_int;//legal
$boolean;//legal
newVariable;//legal
ifConstant;//legal
If;//legal
New;//legal
Private;//legal
4.2 Variablen und Datentypen
Eine Variable ist ein Symbol für einen Speicherbereich, in dem Daten eines bestimmten Datentyps
gespeichert werden können. Java ist eine stark typisierte Programmiersprache. Jede Variable und jeder
Ausdruck muss zur Kompilierzeit an einen bestimmten Datentypen gebunden sein.
Die Datentypen in Java sind in zwei Hauptkategorien unterteilt: Primitive Datentypen und
Referenzdatentypen.
13
Abbildung 4: Datentypen
Variablen des Datentyps boolean können nur einen booleschen Wert (true, false) speichern. Die
Datentypen byte, short, int und long (Integraltypen) repräsentieren Ganzzahlen und unterscheiden
sich lediglich in ihren Wertebereichen bzw. in der Anzahl der Bits, die verwendet werden um eine Zahl
darzustellen.
Eine char-Variable kann ein Zweibyte Unicode-Zeichen bzw. eine positive Ganzzahl aus dem
Intervall [0, 216-1] speichern.
Datentyp
boolean
char
byte
short
int
Länge (Bytes)
1
2
1
2
4
long
8
float
4
double
8
Wertebereich
true , false
16-Bit-Unicode-Zeichen (0x0000 … 0xffff)
–27 bis 27–1 (–128 … 127)
–215 bis 215–1 (–32768 … 32767)
–231 bis 231–1 (–2147483648 … 2147483647)
–263 bis 263–1 (–
9223372036854775808…9223372036854775807)
1,40239846E-45f … 3,40282347E+38f
4,94065645841246544E-324 …
1,79769131486231570E+308
Tabelle 2: Datentypen
ist der einzige numerische Datentyp, der nicht vorzeichenbehaftet ist. Mit char-Variablen können
ganzzahlige Berechnungen durchgeführt werden:
char
14
/*
* Package: default
* Class:
Samples1.java
* Method: primitiveTypes()
*/
char a='a';//entspricht der Zahl 97 nach Unicode
char b='b';//entspricht der Zahl 98 nach Unicode
int aPlusb=a+b;
char c=99;
int aPlusB_PlusC =aPlusb+c;
System.out.println("a+b="+aPlusb+", a+b+c="+aPlusB_PlusC);
Ausgabe:
a+b=195, a+b+c=294
Variablen vom Typ float bzw. double sind Fließkommazahlen dargestellt nach dem IEEE 754Standard. Sie unterscheiden sich lediglich in ihrer Bytelänge und somit im Wertebereich bzw. in der
Genauigkeit (Anzahl der Nachkommastellen) der Zahlen, die sie repräsentieren.
Um Float- von Double-Fließkommazahlen zu unterscheiden, müssen Float-Zahlen mit einem „f“ (oder
groß „F“) am Ende der Zahl gekennzeichnet werden. Eine Fließkommazahl mit einem „d“ (oder groß
„D“) als Suffix wird vom Compiler als Double-Zahl interpretiert. Eine Fließkommazahl ohne Suffix
wird ebenfalls als Double-Zahl interpretiert.
Das folgende Beispiel zeigt, dass Double-Zahlen genauer als Float-Zahlen sind:
/*
* Package: default
* Class:
Samples1.java
* Method: primitiveTypes()
*/
float _22_divided_by_7_as_float=22/7.f;
double _22_divided_by_7_as_double =22/7.d;
System.out.println("_22_divided_by_7_as_float: "
+ _22_divided_by_7_as_float);
System.out.println("_22_divided_by_7_as_double: "
+ _22_divided_by_7_as_double);
Ausgabe: _22_divided_by_7_as_float: 3.142857
_22_divided_by_7_as_double: 3.142857142857143
Um eine Fließkommazahl von einer Ganzzahl unterscheiden zu können, muss mindestens der
Dezimalpunkt, der Exponent oder der Suffix vorhanden sein.
Alle Zahlen der Integraltypen (char, byte, short, int, long) können als Dezimal-, Hexadezimal- oder
Oktalzahlen (Zahlen zur Basis 8) dargestellt werden.
Die Zahlen 281dezimal, 0431oktal und 0x119hexadezimal sind drei unterschiedliche Zahlendarstellungen der
Dezimalzahl 281:
15
/*
* Package: default
* Class:
Samples1.java
* Method: primitiveTypes()
*/
int _281_as_decimal=281;
int _281_as_octal=0431;
int _281_as_hexadecimal=0x119;
System.out.println(_281_as_decimal+" "+_281_as_octal+"
"+_281_as_hexadecimal);
Ausgabe: 281 281 281
Zahlen, die mit 0 anfangen, werden in Java als Oktalzahlen interpretiert und dürfen nur Ziffern aus
dem Intervall [0,7] enthalten. Alle mit 0x beginnenden Zahlen werden als Hexadezimalzahlen
interpretiert.
4.2.1 Deklaration und Initialisierung
Bei der Deklaration einer Variablen wird sie an einen Datentyp gebunden:
int
a;//Variablendeklaration
Bei der Initialisierung einer Variablen wird ihr ein Anfangswert zugeordnet:
int a;//Variablendeklaration
a=2;//Variableninitialisierung
int
a=4;//Deklaration und Initialisierung in einem Schritt
Bevor auf den Wert einer Variablen zugegriffen werden kann, muss sie mit einem Initialwert belegt
werden. Nicht lokale Variablen, die nicht vom Programmierer explizit initialisiert werden, werden vom
Java-System mit Standardwerten initialisiert. Der Initialwert hängt vom jeweiligen Typ ab:
Datentyp
boolean
char
byte
short
int
long
float
double
Initialwert
false
'\u0015'
0
0
0
0
0.0f
0.0
Tabelle 3: Standard-Initialwerte
16
4.2.2 Wrapper-Klassen
Zu jedem primitiven Datentyp in Java gibt es eine korrespondierende Wrapper-Klasse. Diese kapselt
eine primitive Variable in einer objektorientierten Hülle und stellt eine Reihe von Methoden zum
Zugriff auf die Variable zur Verfügung.
Darüber hinaus verfügen die Wrapper-Klassen über zahlreiche Konstanten, die für viele Berechnungen
sehr hilfreich sein können:
Typ
boolean
char
byte
short
int
long
Wrapper-Klasse
Boolean
Character
Byte
Short
Integer
Long
float
Float
double
Double
Konstanten
FALSE, TRUE
MIN_VALUE, MAX_VALUE, SIZE
MIN_VALUE, MAX_VALUE, SIZE, NaN
MIN_VALUE, MAX_VALUE, SIZE, NaN
MIN_VALUE, MAX_VALUE, SIZE, NaN
MIN_VALUE, MAX_VALUE, SIZE, NaN
MAX_EXPONENT, MIN_EXPONENT,
MIN_VALUE, MAX_VALUE, SIZE, NaN
MIN_EXPONENT, MIN_VALUE,
MAX_VALUE, SIZE, MAX_EXPONENT, NaN
Tabelle 4: Wrapper-Klasse
4.2.3 Arithmetik- und Fließkommaprobleme
In Java können zahlreiche arithmetische Operationen ungültige Werte produzieren bzw.
Rundungsfehler verursachen. Tabelle 5 und das nachfolgende Programm zeigen einige typische Fehler,
die bei arithmetischen Berechnungen auftreten können:
Fehlerart
Überlauf
Rundungsfehler
Undefinierte
Operation
Rundungsfehler
Beispiel
308
Ergebnis
Infinity
10 ∗10
true
1. 0
∗49 ≠ 1. 0
49
0.0/0.0
NaN
1.0/0.0
Infinity
true
3∗∗
3≠ 3
∗
Tabelle 5: Arithmetik- & Fließkommaprobleme
17
/*
* Package: default
* Class:
Samples1.java
* Method: floatingPointProblems()
*/
public static void floatingPointProblems(){
// Beispiel für Overflow:
double d = 1e308;
System.out.println();
System.out.println(d + "*10==" + d*10);
System.out.println("---------------------");
System.out.println();
// Beispiel für Underflow:
d = 1e-305 ;
System.out.println("underflow: " + d );
for (int i = 0; i < 4; i++)
System.out.println(" " + (d /= 100000));
System.out.println("---------------------");
System.out.println();
// Beispiel für NaN:
System.out.print("0.0/0.0 ist 'Not-a-Number': ");
d = 0.0/0.0;
System.out.println(d);
System.out.println("---------------------");
System.out.println();
// Beispiel für Infinity:
System.out.print("1.0/0.0 ist 'Infinity': ");
d = 1.0/0.0;
System.out.println(d);
System.out.println("---------------------");
System.out.println();
// Beispiel für Rundungsfehler:
System.out.print("Ungenaue Ergebnisse mit double:");
for (int i = 0; i < 100; i++) {
double z = 1.0 / i;
if (z * i != 1.0)
System.out.println("(1.0/" + i+")*"+i+"="+(z * i));
}
System.out.print("Wurzel_aus_3="+Math.sqrt(3)+",
Wurzel_aus_3*Wurzel_aus_3= "+
Math.sqrt(3)*Math.sqrt(3));
System.out.println("---------------------");
System.out.println(Long.MAX_VALUE*2);
}
18
4.3 Typkonvertierung und Casting
Wie bereits erwähnt, besitzt jede Variable in Java einen Typ. Ein wichtiger Aspekt von Java ist, dass
der Datentyp einer Variablen in einen anderen Datentyp konvertiert werden kann.
Java unterscheidet zwischen zwei Arten von Typkonvertierungen:
1. Die implizite Typkonvertierung, die automatisch vom Compiler durchgeführt wird.
2. Die explizite Typumwandlung (casting), die vom Programmierer durchgeführt wird.
/*
* Package: default
* Class:
Samples1.java
* Method: typeConversion()
*/
//Beispiele für implizite Typkonvertierungen
System.out.println(710);//implizite Typumwandlung von int in String
System.out.println(3.5);//implizite Typumwandlung von double in String
int k=5/2;//ganzzahlige Division, k hat den Wert 2
System.out.println("5/2="+k);
System.out.println("5/2.0="+5/2.0+" Das Ergebnis ist vom Typ double!");
System.out.println("5/2.0f="+5/2.0f+" Das Ergebnis ist vom Typ float!");
int x='u';//implizite Typumwandlung von char nach int
System.out.println(x);//liefert 117 zurück
Ausgabe:
710
3.5
5/2=2
5/2.0=2.5 Das Ergebnis ist vom Typ double!
5/2.0f=2.5 Das Ergebnis ist vom Typ float!
117
Abbildung 5: Typkonvertierung
Ob eine Typkonvertierung explizit oder automatisch durchgeführt wird, hängt vom Zieltyp ab.
Konvertierungen in Pfeilrichtung werden erweiternde Konvertierungen genannt und müssen nicht
explizit durchgeführt werden.
Jede Typkonvertierung entgegen der Pfeilrichtung beschreibt eine sog. einschränkende Konvertierung,
die explizit angegeben werden muss. Man spricht hier von Casting (explizite Typkonvertierung).
Im Folgenden sind alle erlaubten Konvertierungen zwischen primitiven Datentypen aufgelistet:
Erweiternde Typkonvertierungen:
• byte zu short, int, long, float oder double
• short zu int, long, float oder double
19
•
•
char zu int, long, float oder double
int zu long, float oder double
•
long zu float oder double
float zu double
•
Einschränkende Typkonvertierungen (erfordern Casting):
• short zu byte oder char
• char zu byte oder short
• int zu byte, short oder char
• long zu byte, short char oder int
• float zu byte, short, char, int oder long
• double zu byte, short, char, int, long oder float
/*
* Package: default
* Class:
Samples1.java
* Method: typeConversion()
*/
int i=32;
short s=4;
char c='g';
double d=i;//erweiternde Typumwandlung
float ff=(float)d;//einschränkende Typumwandlung
byte b=(byte)c;//einschränkende
c=(char)s;//einschränkende
c=(char)b;//einschränkende
int k=5/2;// Ganzzahlige Division, Ergebnis ist 2.
5/2.0f;// Das Ergebnis ist 2.5f. Da 2.0f eine float-Zahl ist,
// findet hier eine implizite Typumwandlung in float statt.
5/2.0;// Das Ergebnis ist 2.5d. Da 2.0 eine double-Zahl ist,
// findet eine implizite Typumwandlung in double statt.
5.f/2.0;// Das Ergebnis ist 2.5d. Da 2.0 eine double-Zahl ist,
// findet hier eine implizite Typumwandlung in double statt.
Sowohl bei erweiternden als auch bei einschränkenden Typkonvertierungen können Daten verloren
gehen:
/*
* Package: default
* Class:
Samples1.java
* Method: typeConversion()
*/
int big = 1234567890;
float approx = big;
System.out.println(big - (int)approx);//Das Ergebnis sollte 0 sein, //
ist jedoch -46 --> Datenverlust
double d=10e10;
float f=(float)d;
System.out.println((d-f) );//Das Ergebnis sollte 0 sein, ist
jedoch 2048.0 --> Datenverlust
20
float f=4.39f;
int i=(int)f;//Verlust der Nachkommazahl
4.4 Autoboxing (boxing/unboxing)
Das Konzept des Autoboxing wurde erst in der Version 5 eingeführt und bezeichnet die automatische
Konvertierung zwischen primitiven Typen und Wrapper-Klassen. Vor Version 5 waren die primitiven
Datentypen und ihre korrespondierenden Wrapper-Klassen nicht zuweisungskompatibel. So war es
z.B. nicht möglich eine primitive Integer-Variable einem Integer-Objekt zuzuweisen oder umgekehrt:
//folgende Zuweisungen waren vor Version 5 nicht erlaubt:
Integer x=2;
int y=new Integer(3);
Dank des Autoboxing-Konzepts ist dies nun möglich.
Unter Boxing versteht man die automatische Umwandlung eines primitiven Wertes in ein WrapperKlassenobjekt. Unboxing ist die Umkehrung, sprich die automatische Umwandlung eines WrapperKlassenobjektes in einen Wert:
//boxing/unboxing
Boolean b=false;//boxing
float f=new Float(3.4f);//unboxing
Da die Umwandlung automatisch vom Compiler durchgeführt wird, sind primitive Datentypen und
ihre korrespondierenden Wrapper-Klassen zuweisungskompatibel.
Vor Version 5 hätte man die Umwandlung manuell durchführen müssen:
//Vor Version 5
Boolean b=new Boolean(false);
Float f1=new Float(3.4f);
float f=f1.floatValue();
21
5 Arrays
Ein Array ist eine geordnete Folge von Elementen des gleichen Datentyps. Die Felder eines Arrays
können Elemente eines primitiven Datentyps oder Referenzen auf andere Arrays bzw. auf Objekte
enthalten.
5.1 Definition
Die Definition eines Arrays erfolgt in drei Schritten:
1. Deklaration
2. Konstruktion
3. Initialisierung
Die Deklaration teilt dem Compiler den Namen der Arrayvariablen und den Typ seiner Elemente mit:
/*
* Package: default
* Class:
Samples2.java
* Method: arrays()
*/
int [] list1; // Arraydeklaration
int list2 [];//Arraydeklaration
float [][] twoDimArray; // array of arrays of float
float [][] twoDimArray; // array of arrays of float
float []twoDimArray2[]; // array of arrays of float
int [] list3, list4 ; //Deklaration mehrerer Arrays
// Deklaration mehrerer Arrays. list9 und list10
// sind zweidimensionale Arrays
int [] list7, list8, list9[], list10[] ;
Bei Deklarationen ist es erlaubt, den Arraynamen vor nach oder zwischen den eckigen Klammerpaaren
zu positionieren. Gleiches gilt für Methoden, die Arrays als Parameter übergeben bekommen bzw.
zurück liefern:
public static void main(String [] args)
public static void main(String args [])//ist äquivalent zur
//vorigen Deklaration
public float []getList();
public float getList () [];//beide Deklarationen sind
//äquivalent
Bei der Konstruktion eines Arrays wird ein Arrayobjekt mit dem new-Operator instanziiert und die
Länge des Arrays festgelegt:
list1 = new int[10]; // Arraykonstruktion
22
Immer wenn ein Array generiert wird, werden seine Felder automatisch mit den Standardwerten ihres
Datentyps initialisiert:
/*
* Package: default
* Class:
Samples2.java
* Method: arrays()
*/
byte size=3;
float list6 []=new float[size];
int [] list5=new int[3];
for (int i=0;i<list5.length;i++)
System.out.print(list5[i]+" ");//Ausgabe 0 0 0 ,
System.out.println();
for (int i=0;i<list6.length;i++)
System.out.print(list6[i]+" ");//Ausgabe 0.0 0.0 0.0
Will man während der Definition das Array mit anderen Werten initialisieren, so muss die Deklaration,
Konstruktion und Initialisierung zu einem einzelnen Schritt kombiniert werden:
/*
* Package: default
* Class:
Samples2.java
* Method: arrays()
*/
//Explizite Array-Initialisierung
int [] factorial = { 1, 1, 2, 6, 24, 120, 720, 5040 };
char ac[] = { 'n', 'o', 't', ' ', 'a', ' ','S', 't', 'r', 'i',
'n', 'g' };
float [][] _3X2Matrix={{1.0f,2.1f},{.5f,2.1f},{3.1f,.72f}};
5.2 Irreguläre Arrays
Ein zweidimensionales Array ist ein eindimensionales Array, dessen Felder Referenzen auf andere
eindimensionale Arrays enthalten. Es ist irreführend sich derartige Arrays als eine Matrix vorzustellen
(umgekehrt kann eine Matrix sehr wohl als ein solches Array von Arrays implementiert werden). In
einigen anderen Programmiersprachen haben alle Zeilen eines zweidimensionalen Arrays die gleiche
Anzahl von Elementen. In Java ist es möglich, irreguläre mehrdimensionale Arrays zu definieren. Das
sind Arrays deren Unterarrays unterschiedliche Längen aufweisen:
/*
* Package: default
* Class:
Samples2.java
* Method: irregularArray ()
*/
int [][] irregularArray = new int[][]{{3,1},{-1},{5,2,0}};
irregularArray[0] // entspricht {3,1} (erste Zeile)
23
irregularArray[1] // entspricht {-1} (zweite Zeile)
irregularArray[1][0] // entspricht -1 (erstes Element in der zweiten Zeile)
irregularArray[2] // entspricht {5,2,0} (dritte Zeile)
Abbildung 6: Irreguläres Array
5.3 Zugriff auf Arrays
Jedes Arrayelement hat einen numerischen Index. Die Nummerierung der Felder eines Arrays fängt
mit dem Index 0 an. So sind die Felder eins Arrays der Länge n von 0 bis n-1 nummeriert. Der
Versuch, auf ein Element mit einem falschen Index zuzugreifen, wirft eine sog.
ArrayIndexOutOfBoundsException (Exceptions werden in Abschnitt 13 Ausnahmen (Exceptions)
behandelt):
/*
* Package: default
* Class:
Samples2.java
* Method: wrongIndex()
*/
public static void wrongIndex(){
int [][] intArray=new int[3][3];
intArray=new int[][]{{3,1,3},{-1,0,1},{5,2}};
for(int i=0; i<3; i++)
for (int j=0; j<3; j++)
System.out.print(intArray[i][j]+" ");
}
Ausgabe: 3 1 3 -1 0 1 5 2 Exception in thread "main"
java.lang.ArrayIndexOutOfBoundsException: 2
at Samples.wrongIndex(HelloWorldApp.java:218)
at SamplesApplication.main(HelloWorldApp.java:19)
Da bei jedem Zugriff auf ein Arrayelement von der JVM ein sog. Bound-check
durchgeführt wird, sind Zugriffe auf Arrayelemente langsamer als Zugriffe auf Variablen.
24
6 Kontrollstrukturen
6.1 if-else
Eine if-Anweisung testet eine boolesche Variable oder einen Ausdruck. Wenn die boolesche Variable
oder der Ausdruck den Wert true hat, wird die nachstehende Anweisung oder der nachstehende
Anweisungsblock ausgeführt. Ansonsten, wenn der Wert false ist, wird die nachstehende Anweisung
oder der nachstehende Anweisungsblock ignoriert und mit dem folgenden Block bzw. der folgenden
Anweisung fortgefahren.
Eine Erweiterung der if-Anweisung ist die if-else-Anweisung, die einen zusätzlichen else-Teil hat.
Der else-Teil wird dann ausgeführt, wenn der boolesche Test im if-Teil der Anweisung den Wert
false ergibt:
/*
* Package: default
* Class:
Samples2.java
* Method: roots()
*/
public static void nullStellen(){
/*
* Für eine quadratische Funktion f(X)=aX²+bX+c mit a!=0
* ist Xi eine Nullstelle falls gilt: f(Xi)=0
* Die Nullstellen einer quadratischen Funktion lassen sich mithilfe
* der pq-Formel wie folgt leicht ermitteln:
* */
//f(X)=2X²+3X+1
float a=2f, b=3f, c=1;
float p=b/a;
float q=c/a;
float d=(float)Math.pow(p/2, 2)-q;//d=(p/2)²-q;
if(d<0)
System.out.println("Die Funktion f(X)="+a+"X^²+"+b+"X+"+c+" hat
keine Nullstellen!");
else if(d==0){
float x0=-p/2;
System.out.println("Die Funktion f(X)="+a+"X²+"+b+"X+"+c+"
hat nur eine Nullstelle: X0="+x0);
}
else {
float x0=-p/2+(float)Math.sqrt(d);
float x1=-p/2-(float)Math.sqrt(d);
System.out.println("Die Funktion f(X)="+a+"X²+"+b+"X+"+c+"
hat zwei Nullstellen: X0="+x0+" und
X1="+x1);
}
}
Der Fragezeichenoperator kann an bestimmten Stellen anstelle einer if-else-Anweisung verwendet
werden. Er hat die folgende Syntax:
(boolean_condition) ? expression1 : expression2;
25
expression1 und expression2 müssen entweder numerisch sein, einen Referenztyp oder den Typ
boolean haben. Bei der Auswertung wird zunächst der Wert von boolean_condition ermittelt. Ist
dieser wahr, so wird expression1, sonst expression2 ausgewertet. Das Ergebnis des Ausdrucks a ? b
: c ist also b, falls a wahr ist und c, falls a falsch ist. Der Typ des Rückgabewerts entspricht dem Typ
des allgemeineren der beiden Ausdrücke b und c.
6.2 switch
Das Testen einer Variablen mit Hilfe einer if-Anweisung kann unter Umständen sehr unübersichtlich
werden, wenn sehr viele if-else-Alternativen berücksichtigt werden müssen. Eine kompaktere
Anweisungsform bietet das switch-Konstrukt. Die Testvariable, die dem Schlüsselwort switch folgt,
muss dabei zuweisungskompatibel mit dem Typ int sein. Dies ist genau dann der Fall, wenn sie einen
der folgenden Typen hat: byte, char, short oder int. Die sog. case-Labels geben die möglichen
Werte der Testvariablen an. Diese Werte müssen Konstanten sein. Es dürfen also für die Testwerte
keine Variablen, Ausdrücke oder Methodenaufrufe verwendet werden.
/*
* Package: default
* Class: Samples2.java
* Method: switchSample()
*/
public static void switchSample(int command){
final
final
final
final
int
int
int
int
OPEN_FILE=0;
DELETE_FILE=1;
SAVE_FILE=2;
RENAME_FILE=3;
switch(command){
case OPEN_FILE: System.out.println("opening file...");break;
case DELETE_FILE: System.out.println("deleting file...");break;
case SAVE_FILE: System.out.println("saving file...");break;
case RENAME_FILE: System.out.println("renaming file...");break;
default: System.out.println("invalid command !!!!!");
}
}
Samples.switchSample(2);
Ausgabe: saving file...
In diesem Beispiel wurde das Schlüsselwort break verwendet, um nach einer Übereinstimmung die
switch-Anweisung zu verlassen (für Näheres siehe z.B.
http://java.sun.com/docs/books/tutorial/java/nutsandbolts/switch.html). Für andere Anwendungen von
break siehe auch Abschnitt 6.4 break und continue.
26
6.3 Schleifen
Schleifen erlauben die mehrmalige Ausführung von Anweisungen. Eine Schleife besteht aus einem
logischen Ausdruck und dem Schleifenkörper. Liefert der logische Ausdruck den Wert true, wird der
Schleifenkörper ausgeführt. Wird der Wert false angenommen, verlässt das Programm die Schleife.
Java kennt drei verschiedene Schleifenkonstrukte: while, do und for.
6.3.1 while-Schleife
Eine while-Schleife hat die folgende Syntax:
while(boolean_condition)
repeated_statement_or_block
boolean_condition
ist ein boolescher Ausdruck, der entweder zu einen false oder true
ausgewertet wird.
ist eine Anweisung bzw. ein Block von Anweisungen, die solange
wiederholt werden sollen, bis boolean_condition zu einem false ausgewertet wird:
repeated_statement_or_block
/*
* Package: default
* Class: Samples2.java
* Method: whileSample()
*/
public static void whileSample() {
Random rand=new Random();
int randomSum=0;
int nextRandInt=0;
while (randomSum<150) {
nextRandInt=rand.nextInt(10);// liefert eine zufällige Zahl
// aus dem geschlossenen [0,10] zurück
System.out.println("nextInt= "+nextRandInt);
randomSum+=nextRandInt;// äquivalent zu
// randomSum=randomSum*nextRandInt
}
System.out.println(randomSum);// randomSum>=150
}
27
Eine while-Schleife, die die Binärdarstellung einer positiven Zahl berechnet:
/*
* Package: default
* Class: Samples2.java
* Method: whileBinary(int number)
*/
public static void whileBinary(int number) {
String binary="";
int div=number;
int rest;
while(div>0){
rest=div%2;
div=div/2;
binary=rest+binary;
}
}
System.out.println("Die Binärdarstellung von "+number+
" ist "+binary);
}
whileBinary(123);
Ausgabe: Die Binärdarstellung von 123 ist 1111011
Wenn eine while-Schleife nur eine einzige Anweisung wiederholen soll, muss diese Anweisung nicht
in geschweifte Klammern gesetzt werden. Man spricht in diesem Fall von einer single statement while
loop.
/*
* Package: default
* Class: Samples2.java
* Method: singleStatementWhile1()
*/
public static void singleStatementWhile1() {
byte b=0;
while(++b<3)
System.out.println("this statement is printed twice");
System.out.println("this statement is printed only once");
}
Ausgabe: this statement is printed twice
this statement is printed twice
this statement is printed only once
28
Beispiele für endlose Schleifen:
/*
* Package: default
* Class: Samples2.java
* Method: endlessWhileLoop()
*/
public static void endlessWhileLoop() {
byte b=0;
while(b<3);
b++;//this statement will never be executed
}
byte y=0;
while(true)
System.out.println((++y));//overflow after 128 iterations
byte x=1;
byte y=10;
while(Math.min(x,y)<5);// endless loop:
++x;
6.3.2 do … while-Schleife
Die do-Schleife ist die fußgesteuerte Variante der while-Schleife; d.h. die Schleifenbedingung wird
nach der Ausführung des Schleifenkörpers geprüft, wodurch der Schleifenkörper immer mindestens ein
mal ausgeführt wird. Die allgemeine Form einer do-Schleife ist
do
repeated_statement_or_block
while (boolean_condition);
Beispiel:
/*
* Package: default
* Class: Samples2.java
* Method: doWhileSumOfDigits(int number)
*/
public static void doWhileSumOfDigits(int number) {
//Berechnung der Quersumme einer positiven Zahl:
int sumOfDigits = 0;
do {
sumOfDigits += number % 10;// sumOfDigits=sumOfDigits+number%10
number /= 10;// number=number/10
}while (number != 0);
System.out.println(sumOfDigits);
}
doWhileSumOfDigits(283746);
Ausgabe: 30
29
6.3.3 for-Schleife
Die allgemeine Form einer for-Schleife sieht folgendermaßen aus:
for(statement; boolean_condition; expression)
repeated_statement_or_block
Bei einer for-Schleife wird zuerst statement ein einziges Mal ausgeführt. Danach wird
boolean_condition ausgewertet. Wenn die Auswertung der boolean_condition wahr ergibt, wird
der Anweisungsblock repeated_statement_or_block und anschließend der Ausdruck expression
jeweils einmal ausgeführt. Der darauf folgende Schleifendurchlauf beginnt mit einer erneuten
Auswertung der Schleifenbedingung boolean_condition.
Beispiel (Fibonacci-Zahlen):
/*
* Package: default
* Class: Samples2.java
* Method: fibonacci(int n)
*/
public static void fibonacci(int n) {
int fib1 = 0, fib2 = 1;
int fib = fib1 + fib2;
for (int i = 3; i <= n; i++) {
fib1 = fib2;
fib2 = fib;
fib = fib1 + fib2;
}
System.out.println(fib);
}
fibonacci(11);
Ausgabe: 89
Weitere Beispiele:
/*
* Package: default
* Class: Samples2.java
* Method: strangeForLoop()
*/
public static void strangeForLoop() {
// strange loop for(System.out.println("Ich werde ein einziges
Mal zu Beginn ausgeführt!");
i<5;System.out.println("Ich werde am Ende eines
Schleifendurchlaufs ausgeführt! "))
{
System.out.println("Ich werde zum "+(i++)+". Mal ausgeführt!");
}
}
30
//Die beiden Schleifen sind äquivalent
for(System.out.println("Ich werde ein einziges Mal zu Beginn ausgeführt!");
i<5;)
{
System.out.println("Ich werde zum "+(i++)+". Mal ausgeführt!");
System.out.println("Ich werde am Ende eines
Schleifendurchlaufs ausgeführt! ");
}
for(int i=0,j=10; i<5; i++)
System.out.println(i*j--);
int i=0;
for( ;i<5;)
System.out.println(i++);
// Endlose for-Schleifen:
for( ;true;)
System.out.println("endless loop");
for( int j=0;;j++)
System.out.println("endless loop");
6.3.4 for-Each-Schleife
Die for-each-Schleife gibt es erst seit Version 5. Sie bietet eine komfortable Möglichkeit über Arrays
und Kollektionen (Collections) zu iterieren:
/*
* Package: default
* Class: Samples2.java
* Method: forEachLoop()
*/
public static void forEachLoop() {
double [] trigonometry=new
double[]{Math.PI,Math.sin(Math.PI/2),Math.cos(Math.PI/2)};
for (double d : trigonometry) // for each element d in
System.out.println(d);
// the trigonometry array
}
//äquivalent zu:
for(int i=0;i<trigonometry.length;i++)
System.out.println(trigonometry[i]);
31
6.4 break und continue
In vielen Fällen ist das Beenden eines Schleifendurchlaufs bzw. das Verlassen des Schleifenrumpfes
erforderlich. Hierzu stellt Java zwei Anweisungen zur Verfügung: continue und break.
Die Anweisung break bewirkt, dass mit ihrer Ausführung die Abarbeitung der Schleife sofort beendet
wird. Dies geschieht unabhängig davon, ob das Abbruchkriterium der Schleife erfüllt ist oder nicht.
Beispiel:
Gegeben sei eine Liste L mit positiven und negativen Ganzzahlen. Alle Zahlen, die vor der ersten
negativen Zahl stehen, sollen aufsummiert werden. Für die Liste L=[2, 28,-17,9] ist die Summe 30. Das
folgende Beispiel zeigt, wie dies mittels einer break-Anweisung realisiert werden kann:
/*
* Package: default
* Class: Samples2.java
* Method: breakSample ()
*/
public static void breakSample(){
/* Erstelle eine zufällig generierte
* Liste aus Ganzzahlen
*/
int [] numbers= getRandomIntegerList();
/* Ermittle die Summe aller Zahlen, die vor der ersten
* negativen Zahl in der Liste vorkommen.
* Implementierung mit einer for-each-Schleife
*/
int sum=0;
for (int number : numbers) {
if(number<0)
break;
sum+=number;
}
System.out.println("Die Summe beträgt: "+sum);
}
public static int [] getRandomIntegerList(int arraySize){
//Erzeuge eine zufällig generierte Liste aus Ganzzahlen
Random rand=new Random();
int [] numbers=new int[arraySize];
for(int i=0;i<numbers.length;i++){
//sign ist entweder 1 oder -1
byte sign=(byte)Math.pow(-1,rand.nextInt(3));
numbers[i]=rand.nextInt(20)*sign;//
System.out.print(numbers[i]+" ");
}
System.out.println();
return numbers;
}
Durch die Anweisung continue wird im Gegensatz zu break nicht die gesamte Schleife abgebrochen,
sondern nur der aktuelle Schleifendurchlauf unterbrochen. Die continue-Anweisung ermöglicht es
den aktuellen Schleifendurchlauf zu beenden, wobei danach die Schleifenbedingung erneut
ausgewertet wird, und es wird ggf. mit einem neuen Schleifendurchlauf begonnen.
32
Beispiel:
Wir betrachten wieder eine Liste mit negativen und positiven Zahlen. Für jede positive Zahl soll die
Wurzel berechnet und auf dem Bildschirm ausgegeben werden. Die in der Liste enthaltenen negativen
Zahlen sollen dabei übersprungen werden. Der folgende Code zeigt wie dies mit Hilfe einer forSchleife und der continue-Anweisung implementiert werden kann:
/*
* Package: default
* Class: Samples2.java
* Method: continueSample ()
*/
//Berechnet die Wurzel jeder in der Liste vorkommenden positiven Zahl
public static void continueSample(){
//Erzeuge eine zufällig generierte Liste aus ganzen Zahlen
int [] numbers=getRandomIntegerList();
/* Berechne die Wurzel jeder positiven Zahl in der Liste und
* überspringe dabei die negativen Zahlen
* Implementierung mit einer for-each-Schleife
*/
for (int number : numbers) {
if(number<0) {
System.out.println(number+" hat keine Wurzel!");
continue;
}
double sqrt=Math.sqrt(number);
System.out.println("Wurzel von "+number+"= "+sqrt);
}
}
Neben dieser einfachen Form von break und continue gibt es in Java noch die mit einem Label
versehene Form:
//eine mit einem Label versehene break-Anweisung
break LabelName;
//eine mit einem Label versehene continue-Anweisung
continue LabelName;
Zu jedem mit einem Label versehenen break oder continue muss es eine mit einem Label versehene
Kontrollstruktur (for, if, do, while) geben, die die continue- oder break-Anweisung umschließt.
Oft wird ein mit einem Label versehenes break verwendet, um zwei oder mehrere ineinander
verschachtelte Schleifen zu beenden.
Das folgende Programm zählt, wie viele Zeilen einer Matrix mindestens eine Null enthalten und gibt
diese Zahl auf dem Bildschirm aus:
33
/*
* Package: default
* Class: Samples2.java
* Method: continueWithLabel ()
*/
public static void continueWithLabel(){
int [][]matrix=getRandomMatrix(5);
int linesWithZero=0;
//ein Label für die continue-Anweisung
labelForContinue: for(int i=0;i<matrix.length;i++){
for (int j = 0; j < matrix[i].length; j++) {
//wenn eine Null in einer Zeile gefunden wird
if(matrix[i][j]==0){
//erhöhe Anzahl der Zeilen, die mindestens eine Null enthalten
linesWithZero++;
System.out.println();
//restliche Zahlen ignorieren und nächste Zeile nehmen
continue labelForContinue;
}
//gebe Zahl aus und überprüfe die nächste Zahl in derselben Zeile
System.out.print(matrix[i][j]+"\t");
}
System.out.println();
}
if(linesWithZero==1)
System.out.println("Eine Zeile
der Matrix enthält Nullen!");
else System.out.println(linesWithZero+" Zeilen der Matrix
enthalten Nullen!");
public static int getRandomMatrix(int matrixSize) [][]{
Random rand=new Random();
int [] []matrix=new int[matrixSize][matrixSize];
for(int i=0;i<matrix.length;i++){
for (int j=0;j<matrix[i].length;j++)
{
matrix[i][j]=rand.nextInt(20);
System.out.print(matrix[i][j]+"\t");
}
System.out.println();
}
System.out.println();
return matrix;
}
34
7 Grundkonzepte OOP
7.1 Klassen und Objekte
Eine Klasse ist ein abstrakter Datentyp, der die Eigenschaften, das Verhalten und somit die Struktur
einer Menge gleichartiger Objekte (etwa der realen Welt) festlegt. Die Eigenschaften einer Klasse
werden Attribute genannt. Das Verhalten der Objekte einer Klasse legen die Methoden der Klasse fest.
Ein Objekt ist eine Instanz (konkrete Ausprägung) einer Klasse, das durch seinen Zustand (Belegung
seiner Attribute), sein Verhalten und seine Identität (sein Name) gekennzeichnet ist.
Zum Beispiel kann man sich einen abstrakten Datentyp namens Bankkonto vorstellen und festlegen,
dass ein Bankkonto einen Betrag speichert und dass der Betrag über die Operationen abheben und
einzahlen verändert werden kann:
Klasse
Bankkonto
Attribute
Kontostand
Kontonummer
Inhaber
Methoden
einzahlen()
abheben()
7.2 Klassen in Java
Eine Klassendeklaration in Java beginnt mit dem Schlüsselwort class, definiert einen neuen
Referenztyp und beschreibt die Eigenschaften (Attribute) und das Verhalten (Methoden) der Objekte
der Klasse. Die Attribute einer Java-Klasse werden auch Felder genannt.
Der Klassenrumpf deklariert die Mitglieder (members) einer Klasse. Das sind Felder (fields),
Methoden (methods), Konstruktoren (constructors), innere Klassen, Interfaces, Instanz- und statische
Initialisierer (instance and static initializers).
35
Die Implementierung des Bankkontos könnte so aussehen:
/*
* Package: default
* Class: Bankkonto.java
* Method:
*/
public class Bankkonto {//Klassendeklaration
//Beginn des Klassenrumpfes
//Attribute legen den Zustand eines Objektes fest
public int kontoNummer; //Felddeklaration
public float kontoStand;//in € // Felddeklaration
public String inhaber; //Felddeklaration
//Methoden definieren das Verhalten eines Objektes
//und ändern seinen Zustand
public void einzahlen(float betrag){
kontoStand+=betrag;
}
public void abheben(float betrag){
kontoStand-=betrag;
}
public float getKontoStand(){
return kontoStand;
}
public static void main(String [] args ){
Bankkonto alexKonto=new Bankkonto();//Objekt (Instanz)
alexKonto.kontoNummer=2150;
alexKonto.inhaber="Alex Mustermann";
alexKonto.kontoStand=12350;
/*
* Der Zustand von alexKonto ist durch das
* Tupel(2150,"Alex M.",12350) bestimmt
*/
Bankkonto michiKonto=new Bankkonto();
michiKonto.inhaber="Michaela M.";
michiKonto.kontoStand=2103;
michiKonto.kontoNummer=35870;
}
//Ende des Klassenrumpfes
}
Felddeklarationen beschreiben Variablen, die den Zustand eines Objektes festlegen. Methoden legen
fest, wie Variablen verändert werden können. Mit anderen Worten sie legen fest wie der Zustand eines
Objektes verändert werden kann.
36
7.3 Methoden und Konstruktoren
Eine Methode hat eine Signatur (Methodenkopf) und einen Methodenrumpf. Der Methodenkopf
besteht aus der Zugriffsart (public, private, protected), einem Rückgabetyp, dem Methodennamen
und einer Parameterliste.
public void einzahlen(float betrag) //Methodenkopf (Signatur)
{
//Methodenrumpf
}
Eine Methode muss weder Parameter haben noch einen Rückgabewert liefern.
Ein Konstruktor ist eine spezielle Methode, die keinen Rückgabetyp hat (auch nicht void) und so heißt
wie die zugehörige Klasse. Konstruktoren werden im Wesentlichen dazu verwendet, die Attribute eines
Objektes mit Anfangswerten zu initialisieren.
//Konstruktor
public Bankkonto(){
kontoNummer=0;
inhaber="Unbekannt";
kontoStand=0.f;
}
Ein Konstruktor wird immer dann aufgerufen, wenn ein Objekt mit dem new-Operator instanziiert
wird:
Bankkonto alexKonto=new
Bankkonto();//Objekt (Instanz)
Eine Klasse muss keine Konstruktoren definieren. In diesem Fall fügt der Compiler den
parameterlosen Default-Konstruktor in die Klasse ein. Dieser heißt genau so wie die Klasse, hat keine
Parameter und enthält keine Anweisungen.
//Der Default-Konstruktor
public KlassenName(){
}
37
7.4 Überladen von Methoden
Eine Methode ist durch ihren Namen und die exakte Folge ihrer Parametertypen eindeutig bestimmt.
Die Wiederverwendung desselben Methodennamens mit einer anderen Parameterliste und evtl. einem
anderen Rückgabetyp wird als Überladung bezeichnet. Mit der Überladung einer Methode ist gemeint,
dass innerhalb ihrer Klasse eine neue Methode mit demselben Namen jedoch mit anderer
Parameterliste definiert wird. Hierbei muss die neue Methode sich von der alten Methode in der
Anzahl, den Typen oder der Reihenfolge ihrer formalen Parameter unterscheiden.
Zwei Parameterlisten werden als unterschiedlich angesehen wenn einer der folgenden Punkte gilt:
1. Sie haben eine unterschiedliche Anzahl an Parametern:
aMethod(float dx, float dy) //2 Parameter
aMethod()//keine Parameter
aMethod (float dx) //1 Parameter
2.
Sie haben die selbe Anzahl an Parametern, jedoch eine unterschiedliche Reihenfolge der
Parametertypen:
aMethod(float dx, int dy)
aMethod(int dx, float dy)
aMethod(float dx, float dy)
aMethod(int dx, int dy)
Die Bezeichnung der Parameter spielt in beiden Fällen keine Rolle. Demnach sind die folgenden
Parameterlisten absolut identisch:
aMethod(float deltaX, int deltaY)
aMethod(float dx, int dy)
Alle folgenden Methoden stellen Überladungen der Methode translate dar:
public
public
public
public
public
public
public
void
void
void
void
void
void
void
translate(float dx, float dy)
translate(int dx, int dy)
translate(float dx, int dy)
translate(int dx, float dy)
translate(int dxy)
translate(float dxy)
translate()
Keine Überladung:
public void translate(float distance)
public void translate(float abstand)
Keine Überladung:
38
public void translate(int deltX, float deltaY)
public void translate(int dx, float dy)
public float translate(int dx, float dy)
Bemerkung: Konstruktoren können nicht vererbt, jedoch wie Methoden überladen werden. Die
Definition mehrerer Konstruktoren kann also als Überladung von Konstruktoren verstanden werden.
39
7.5 Vererbung
Ein wesentliches Konzept in der OOP ist die Vererbung. Vererbung ist ein Ausdruck der Ähnlichkeit
zwischen Klassen und bietet die Möglichkeit, die nicht privaten Attribute und Methoden einer
existierenden Klasse A auf eine neue Klasse B zu übertragen. Man sagt, dass B die Attribute und
Methoden der Klasse A erbt. B wird als Unterklasse und A als Oberklasse bezeichnet. Klassen können
also in einer hierarchischen Vererbungsbeziehung stehen. Dieses Konzept unterstützt die
Wiederverwendung von bereits existierendem Code. Vererbung macht die in der Vaterklasse
definierten Daten für die Unterklasse verfügbar.
Im obigen Beispiel könnte Girokonto eine Unterklasse von Bankkonto sein. Die Klasse Girokonto
erbt dann alle Attribute und Methoden von Bankkonto und kann beliebige eigene Attribute und
Methoden hinzufügen (z.B. dispositionsLimit, dauerAuftragEinrichten(), etc.)
/*
* Package: default
* Class: Bankkonto.java
* Method:
*/
class Girokonto extends Bankkonto{
float dispositionsLimit;
public void dauerAuftragEinrichten(){
/*
* Code für die Einrichtung eines Dauerauftrags
*/
}
}
40
/*
* Package: default
* Class: Point.java
* Method:
*/
public class Point {
public float x1;
public float y1;
public Point(float x1, float y1){//Konstruktor
this.x1=x1;
this.y1=y1;
}
}
class Line extends Point{//Line ist eine Unterklasse von Point,
float x2;
//die ihrerseits die Oberklasse von Line ist
float y2;
public Line(float x1, float y1,float x2, float y2){
super(x1,y1);//Aufruf des Konstruktors der Oberklasse
this.x2=x2;
this.y2=y2;
}
}
Es ist an dieser Stelle zu betonen, dass nur nicht private Attribute und Methoden geerbt werden;
Konstruktoren werden nicht geerbt. Dennoch kann in der Unterklasse jeder Konstruktor der Oberklasse
mit super(parameter_list) und den entsprechenden Konstruktor-Parametern aufgerufen werden. Im
obigen Beispiel ruft der Konstruktor der Klasse Line den Konstruktor von Point auf.
Hier spricht man von einem expliziten Aufruf des Oberklassenkonstruktors. Dieser Aufruf muss als
erste Anweisung innerhalb des eigenen Konstruktors erfolgen.
Findet der Compiler keinen expliziten Aufruf des Oberklassenkonstruktors, so fügt er einen impliziten
Aufruf des default-Konstruktors der Oberklasse als erste Anweisung ein.
//Die beiden folgenden Konstruktoren sind äquivalent:
public Line(float x2, float y2){
this.x2=x2;
this.y2=y2;
}
public Line(float x2, float y2){
super();//Falls nicht vorhanden, wird vom
//Compiler automatisch eingefügt
this.x2=x2;
this.y2=y2;
}
Eine Klasse darf nur eine einzige Klasse als direkte Oberklasse haben. Mehrfachvererbung ist in Java
nicht erlaubt (im Gegensatz zu z.B. C++).
41
7.6 Überschreibung von Methoden
Aus der Oberklasse geerbte Methoden dürfen in der Unterklasse neu definiert werden. Wird eine von
einer Oberklasse geerbte Methode in der Unterklasse neu definiert, ohne ihren Namen und ihre
Parameterliste zu verändern, so spricht man vom Überschreiben der Methode. Dabei ersetzt die
überschreibende Methode ihre überschriebene Methode. Bei einer Überschreibung muss die
Parameterliste der überschreibenden Methode mit der Parameterliste der überschriebenen Methode bis
auf die Parameternamen identisch sein.
/*
* Überschreibung der Methode translate(float dx, float dy)
*/
@Override
public void translate(float dx, float dy){
super.translate(dx, dy);//Wiederverwendung des Codes der Oberklasse
x2+=dx;
y2+=dy;
}
/*
* Überschreibung der Methode translate(int dx, int dy)
*/
@Override
public void translate(int dx, int dy){
super.translate(dx, dy);//Wiederverwendung des Codes der Oberklasse
x2+=dx;
y2+=dy;
}
/*
* Überschreibung der Methode translate(float distance)
*/
@Override
public void translate(float distance){
x1+=distance;//Überschreiben ohne Wiederverwendung des Codes
//der Oberklasse
y1+=distance;
x2+=distance;
y2+=distance;
}
Bemerkung: Es gibt einen wesentlichen Unterschied zwischen dem Überladen und dem
Überschreiben von Methoden. Das Überladen bezieht sich immer auf Methoden derselben Klasse.
Das Überschreiben bezieht sich dagegen auf geerbte Methoden der Oberklasse. Eine Methode kann
beliebig oft überladen jedoch nur einmal überschrieben werden.
Während bei einer Überschreibung die überschreibende und die überschriebene Methode den
gleichen Namen und die gleiche Parameterliste haben müssen, muss die überladende Methode
denselben Namen jedoch eine andere Parameterliste als die überladene Methode haben.
42
7.7 Polymorphismus
Unter Polymorphismus versteht man die Fähigkeit einer Objektreferenz, Objekte seiner Unterklasse zu
referenzieren und sich abhängig davon zu verhalten. In Java ist es nämlich erlaubt, einer
Oberklassenobjektreferenz ein Unterklassenobjekt X zuzuweisen. Somit stellt sich die folgende Frage:
Verhält sich X in diesem Fall wie ein Objekt der Ober- oder der Unterklasse?
Wird einer Oberklassenobjektreferenz ein Objekt X einer Unterklasse U zugewiesen, so verhält sich X
dementsprechend wie alle anderen Objekte von U.
Das folgende Beispiel soll dies verdeutlichen:
/*
* Package: default
* Class: Polymorphism.java
* Method:
*/
class Polygon {
//Oberklasse
/*
* Gibt den Flächeninhalt des Polygons zurück.
*/
public void getSurface(){
System.out.println("The surface of this polygon is undefined.");
}
}
class Rectangle extends Polygon{//Unterklasse
/**
* Ein Rechteck
*/
public float width;
public float height;
public Rectangle(float width, float height){
this.width=width;
this.height=height;
}
public void getSurface(){
System.out.println("The surface of this
rectangle is: "+this.width*height);
}
}
class Triangle extends Polygon{
float base;
float height;
public Triangle(float base, float height){
this.base=base;
this.height=height;
}
public void getSurface(){
System.out.println("The surface of this triangle is:
"+.5f*base*height);
}
}
//polygon ist eine Oberklassenobjektreferenz
Polygon polygon=new Polygon();
polygon.getSurface();
//der Oberklassenobjektreferenz wird ein Unterklassenobjekt zugewiesen
43
polygon =new Rectangle(2,3);
//Anhand der Ausgabe verhält sich polygon
// wie ein Objekt der Unterklasse
polygon.getSurface();
polygon =new Triangle(3,2);
polygon.getSurface();
Ausgabe:
The surface of this polygon is undefined.
The surface of this rectangle is: 6.0
The surface of this triangle is: 3.0
Umgekehrt ist es in Java und in vielen anderen Programmiersprachen nicht erlaubt einer Referenz einer
Unterklasse ein Objekt einer Oberklasse zuzuweisen. Folgende Codezeilen lassen sich nicht
kompilieren:
Triangle triangle=new Polygon();//Kompilierfehler
Rectangle rectangle=new Polygon();//Kompilierfehler
Rectangle rectangle=new Triangle();//Kompilierfehler
44
8 Java-spezifische Konzepte
8.1 Referenzen in Java
Wie bereits erwähnt, sind Typen in Java in zwei Kategorien unterteilt: Primitive Datentypen und
Referenztypen, zu denen Arrays, Klassen und Interfaces zählen. Es gibt einen wesentlichen
Unterschied zwischen einer primitiven Variablen (Variable eines primitiven Typs) und einer
Referenzvariablen (Variable eines Referenztyps):
/*
* Package: default
* Class: Student.java
* Method:
*/
public class Student {
public
public
public
public
String name;//Referenzvariable (Objektreferenz)
Date gebDatum;// Referenzvariable (Objektreferenz)
String studiengang;// Referenzvariable (Objektreferenz)
int semesteranzahl; //primitive Variable
}
Student student = new Student("Alex", null, "BWL",3);
Abbildung 7: Referenzen
Während eine primitive Variable wie z.B. semesteranzahl ihren tatsächlichen Wert (3) speichert,
speichert eine Referenzvariable wie name oder gebDatum eine Referenz auf ein Objekt.
Wenn ein Objekt mittels eines Konstruktorsaufrufs erzeugt wird
(student = new Student("Alex", null, "BWL",3);liefert der new-Operator eine Referenz auf
das erzeugte Objekt zurück. Die Referenz wird dann in der Variablen student gespeichert.
In manchen Implementierungen der JVM wird eine Referenz als Speicheradresse implementiert. Eine
Referenzvariable speichert dann die Speicheradresse ihres Objekts. Man sagt, dass die Variable auf das
Objekt zeigt bzw. das Objekt referenziert. Es gibt allerdings eine Referenz, die kein Objekt
referenziert. Sie heißt null-Referenz und hat den Wert null. Dies ist auch der Wert, mit dem
Referenzvariablen standardmäßig initialisiert werden, bevor ihnen ein Objekt zugewiesen wird.
45
8.2 this, super, this() und super()
Neben den eigenen und geerbten Attributen einer Klasse, verfügen die Instanzmethoden und die
Konstruktoren einer Klasse über zwei weitere Referenzen this und super.
this ist eine Referenz auf das Objekt selbst. super ist eine Referenz auf das Objekt der Oberklasse.
Mit this kann eine nicht statische Methode oder ein Konstruktor auf jede Methode oder Variable der
Klasse zugreifen. Insbesondere wird die this-Referenz zur Unterscheidung zwischen einer
Objektvariablen und einer übergebenen Variablen innerhalb einer Methode oder eines Konstruktors
verwendet, wie das folgende Beispiel zeigt:
/*
* Package: default
* Class: Point.java
* Method:
*/
public Line(float x2, float y2){
this.x2=x2;
this.y2=y2;
}
Während x2 hier der übergebene Parameter ist, ist this.x2 die Objektvariable, die ebenfalls x2 heißt.
Analog kann innerhalb einer Klasse mit super auf die Attribute und Methoden der Oberklasse
zugegriffen werden.
Wie bereits erwähnt, fügt der Compiler automatisch einen Aufruf des Default-Konstruktors der
Oberklasse super() ein, wenn in einem Konstruktor kein Konstruktor der Oberklasse explizit
aufgerufen wird. Konstruktoren der eigenen Klasse bzw. der Oberklasse können mit
this(parameter_list) bzw. super(parameter_list) aufgerufen werden. Ein solcher Aufruf muss als
erste Anweisung im Konstruktor erfolgen:
46
/*
* Package: default
* Class: Point.java
* Method:
*/
public Line(float x1, float y1,float x2, float y2){
this(x2,y2);//Aufruf des lokalen Konstruktors
//Line(float x2, float y2)
this.x1=x1;
this.y2=y2;
}
public Line(float x1, float y1,float x2, float y2){
super(x1,y1);//Aufruf des Konstruktors der Oberklasse
this.x2=x2;
this.y2=y2;
}
public Line(float x1, float y1,float x2, float y2){
this.x1=x1;
this.y2=y2;
this(x2,y2); //Fehlermeldung. Konstruktoraufruf steht
//nicht als erste Anweisung
}
8.3 Klassenvariablen (statische Variablen)
Java unterscheidet zwischen zwei Arten von Variablen: Objekt- und Klassenvariablen.
Eine Variable, die innerhalb einer Klasse mit dem Schlüsselwort static deklariert wird, nennt man
eine statische Variable oder Klassenvariable. Klassenvariablen werden instanziiert, wenn eine Klasse
in die JVM geladen wird, und sind vorhanden, bis die Klasse „vernichtet“ wird. Sie sind an die
Existenz eines Objektes nicht gebunden. Von jeder statischen Variablen v existiert genau eine Kopie
für alle Objekte der Klasse. Alle Objekte sehen somit den gleichen Wert für v. Ändert sich der Wert
von v, so ist diese Änderung in allen Objekten sichtbar.
Objektvariablen dagegen sind an Objekte gebunden und werden immer erzeugt, wenn ein Objekt
erzeugt wird. Jedes Objekt speichert eigene Kopien seiner Objektvariablen. Die Änderung des Wertes
einer Instanzvariablen in einem Objekt hat keine Auswirkung auf die anderen Objekte. Objektvariablen
sind daran zu erkennen, dass sie nicht als statisch deklariert sind.
Während auf eine Klassenvariable sowohl über den Klassennamen mit Mitarbeiter.chef (siehe
Beispiel unten) oder auch über eine Instanzreferenz (mitarbeiter1.chef) zugegriffen werden kann,
können Instanzvariablen nur über eine Instanzreferenz gelesen oder manipuliert werden. Das folgende
Beispiel soll die Unterschiede verdeutlichen:
47
/*
* Package: default
* Class: Mitarbeiter.java
* Method:
*/
public class
public
public
public
Mitarbeiter {
static String chef="Oliver P.";//Klassenvariable
String name;//Instanzvariable
String abteilung;//Instanzvariable
public static void chefwechsel(String neuerChef){//statische Methode
chef=neuerChef;
}
public void abteilungwechsel(String neueAbteilung){
abteilung=neueAbteilung;
}
public Mitarbeiter(String name, String abteilung){
this.name=name;
this.abteilung=abteilung;
}
public void print(){
System.out.println();
}
public static void main(String [] str){
Mitarbeiter mitarbeiter1=new Mitarbeiter("Nadia O.",
"Marketing");
Mitarbeiter mitarbeiter2=new Mitarbeiter("Klaus M.",
"Controlling");
System.out.println(mitarbeiter1.abteilung);
System.out.println(mitarbeiter2.abteilung);
System.out.println(mitarbeiter1.chef);
System.out.println(mitarbeiter2.chef);
mitarbeiter1.abteilungwechsel("Finanz");
mitarbeiter1.chefwechsel("Paul J.");
System.out.println(mitarbeiter1.abteilung);
System.out.println(mitarbeiter2.abteilung);
System.out.println(mitarbeiter1.chef);
System.out.println(mitarbeiter2.chef);
}
}
Ausgabe:
Marketing
Controlling
Oliver P.
Oliver P.
Finanz
Controlling
Paul J.
Paul J.
Weitere Beispiele für Klassenvariablen: Math.PI, Integer.MAX_VALUE, Float.MIN_EXPONENT.
48
8.4 Statische Methoden (Klassenmethoden)
Eine statische Methode, auch Klassenmethode genannt, wird mit dem Schlüsselwort static
deklariert. Sie ist, wie eine statische Variable an die Existenz eines Objektes nicht gebunden und kann
über den Klassennamen aufgerufen werden:
Math.sin(a); Math.cos(a); Math.sqrt(a); Float.parseFloat(s);
Statische Methoden dürfen innerhalb einer Klasse nur auf Klassenvariablen oder auf andere
Klassenmethoden zugreifen. Sie verfügen nicht über die this-Referenz und dürfen sie somit nicht
verwenden.
8.5 Die main-Methode
Die am meisten verwendete und bekannteste statische Methode ist die main-Methode:
public static void main(String [] str)
//bzw.
public static void main(String ... str)//Signatur der mainMethode seit Version 5
Sie ist der Eintrittspunkt jeder stand-alone-Java-Applikation und wird als deren Startpunkt verwendet.
Das Starten einer Applikation erfolgt ohne Instanziierung ihrer Klasse. Wenn eine Applikation
gestartet werden soll, wird ihre Klasse in die JVM geladen. Die JVM durchsucht dann die Klasse nach
der main-Methode. Wird die main-Methode gefunden, übergibt ihr die JVM ein Parameterarray und
fängt anschließend mit der sequenziellen Ausführung der in ihr enthaltenen Anweisungen an. Das
Parameterarray enthält ggf. die Eingabeparameter, die an das Programm übergeben wurden. Enthält die
Klasse keine solche Methode, so wird eine Fehlermeldung ausgegeben.
8.6 Statische Initialisierer
Für komplexe Initialisierung von Klassenvariablen können sog. statische Initialisierer verwendet
werden. Das sind Codeblöcke innerhalb einer Klasse, die mit dem Schlüsselwort static deklariert
sind. Sie werden nur ein einziges Mal beim Laden einer Klasse aufgerufen. Sie dürfen nur auf die
Klassenvariablen zugreifen und haben somit weder Zugriff auf die this-Referenz noch auf die
Instanzvariablen der Klasse. Eine Klasse kann beliebig viele statische Initialisierer enthalten.
/*
* Package: default
* Class: InitializationDemo.java
* Method:
*/
public class InitializationDemo {
public static Date date;//Klassenvariable
public static int classLuckyNumber;//Klassenvariable
public static int numberOfCreatedInstances;//Klassenvariable
static{//Statischer Initialisierer
date=Calendar.getInstance().getTime();
Random rand=new Random();
classLuckyNumber=rand.nextInt(10);
}}
49
8.7 Nicht statische Initialisierer
Nicht-statische Initialisierer unterscheiden sich von den statischen in zwei Punkten:
Sie ähneln Konstruktoren und werden unmittelbar vor dem Aufruf eines Konstruktors aufgerufen.
Sie können auf die this-Referenz und sowohl auf die statischen als auch auf die Instanzvariablen
zugreifen. Eine Klasse kann beliebig viele Initialisierer enthalten.
/*
* Package: default
* Class: InitializationDemo.java
* Method:
*/
public class InitializationDemo {
public int instanceNumber;//Instanzvariable
public int instanceLuckyNumber;//Instanzvariable
{//nicht-statischer Initialisierer
Random rand=new Random();
instanceNumber=rand.nextInt(10);
System.out.println();
this.instanceLuckyNumber=rand.nextInt(10);
}}
8.8 Initialisierungsreihenfolge
Beim Laden einer Klasse werden alle statischen Variablen in der Reihenfolge initialisiert, in der sie
vorkommen. Nach ihrer Initialisierung, werden die statischen Initialisierer hintereinander ausgeführt.
Objektvariablen dagegen werden immer dann initialisiert, wenn ein Objekt mittels eines
Konstruktoraufrufs erstellt werden soll. Nach der Initialisierung der Objektvariablen und unmittelbar
vor dem Aufruf des Konstruktors werden die nicht statischen Blöcke ausgeführt:
50
/*
* Package: default
* Class: InitializationDemo.java
* Method:
*/
public class InitializationDemo {
{//nicht-statischer Initialisierer
System.out.println("Hi, ich bin ein nicht statischer Initialisierer!");
System.out.println("Ich werde immer aufgerufen, wenn Du versuchst ein
Objekt von meiner Klasse zu instanziieren!");
System.out.println("Du bist gerade dabei einen Konstruktor meiner Klasse
zum"+
(++InitializationDemo.numberOfCreatedInstances)+
". aufzurufen!");
Random rand=new Random();
instanceNumber=rand.nextInt(10);
System.out.println();
this.instanceLuckyNumber=rand.nextInt(10);
}
public int instanceNumber;//Instanzvariable
public int instanceLuckyNumber;//Instanzvariable
public static Date date;//Klassenvariable
public static int classLuckyNumber;//Klassenvariable
public static int numberOfCreatedInstances;//Klassenvariable
static{//Statischer Initialisierer
System.out.println("Hi, ich bin ein statischer Initialisierer!");
System.out.println("Die Klasse "+InitializationDemo.class.getName()
+" wird gerade in die JVM geladen!");
System.out.println("Ich werde nur ein einziges Mal aufgerufen!");
date=Calendar.getInstance().getTime();
System.out.println("Heute ist "+date.toString());
Random rand=new Random();
classLuckyNumber=rand.nextInt(10);
System.out.println("Deine Glückszahl ist "+classLuckyNumber);
System.out.println();
}
51
public static void changeLuckyNumber(int number){
InitializationDemo.classLuckyNumber=number;
System.out.println("-----------------------------------------");
}
public InitializationDemo( ){
System.out.println("Hallo, ich bin der Konstruktor Deiner Klasse!");
instanceNumber=numberOfCreatedInstances;
System.out.println();
}
public void printLuckyNumber(){
System.out.println("Hi, ich bin Instanz Nummer "+instanceNumber);
System.out.println("Mein Glückszahl ist "+instanceLuckyNumber);
System.out.println("Die Glückszahl meiner Klasse ist
"+InitializationDemo.classLuckyNumber);
System.out.println();
}
public static void main(String ... args){
InitializationDemo initializer1=new InitializationDemo();
InitializationDemo initializer2=new InitializationDemo();
InitializationDemo initializer3=new InitializationDemo();
initializer1.printLuckyNumber();
initializer2.printLuckyNumber();
initializer3.printLuckyNumber();
InitializationDemo.changeLuckyNumber(11);
initializer1.printLuckyNumber();
initializer2.printLuckyNumber();
initializer3.printLuckyNumber();
}
}
8.9 Call-by-reference oder call-by-value?
Bevor eine primitive Variable an eine Methode übergeben wird, erstellt die JVM eine Kopie der
Variablen, die sie dann an die Methode übergibt. Dies hat zur Konsequenz, dass eine Veränderung der
Kopie innerhalb der Methode keine Wirkung auf den Variablenwert hat. Bei Referenzvariablen wird
ebenfalls eine Kopie der Referenz an die Methode übergeben, nicht das Objekt selbst. Jede Änderung
des Objektes innerhalb der Methode ist nach außen sichtbar wie das unten stehende Beispiel zeigt.
Somit werden Parameter in Java per call-by-value an Methoden übergeben. Das folgende Beispiel soll
dies verdeutlichen:
52
/*
* Package: default
* Class: CallByValue.java
* Method: incrementValues
*/
public static void incrementValues(int primitiveVar, MyInteger refVar){
primitiveVar=primitiveVar+1;
refVar.value=refVar.value+1;
}
public static void main(String ... arg){
int primitiveVar=5;
MyInteger refVar=new MyInteger(5);
System.out.println("Vor der Inkrementierung:
primitiveVar="+primitiveVar+"
refVar.value="+refVar.value);
incrementValues(primitiveVar,refVar);
System.out.println("Nach der Inkrementierung:
primitiveVar="+primitiveVar+"
refVar.value="+refVar.value);
}
}
class MyInteger{
public int value;
public MyInteger(int value){
this.value=value;
}
Ausgabe:
Vor der Inkrementierung: primitiveVar=5 refVar.value=5
Nach der Inkrementierung: primitiveVar=5 refVar.value=6
8.10 Pakete (packages)
Unter einem Paket (package) in Java versteht man eine Sammlung von logisch zusammengehörenden
Klassen, Interfaces und andere Typen. Pakete können als eine logische Gruppierung von Klassen
aufgefasst werden. In anderen Programmiersprachen werden sie als Programmierbibliotheken
bezeichnet. Ein Paketname besteht aus mehreren durch Punkte getrennten Bezeichnern:
//package declaration
package de.frankfurt.uni.cs.gdv.visian3d.datastructures.trees;
Physikalisch werden Pakete auf eine hierarchische Verzeichnisstruktur abgebildet. Jeder im
Paketnamen vorkommende Bezeichner entspricht dabei einem Verzeichnis. Die Verzeichnisse sind
ineinander verschachtelt. Alle Klassen, die im selben Verzeichnis liegen, gehören zu seinem
korrespondierenden Paket.
53
Abbildung 8: Packages
Der voll qualifizierte Name einer Klasse A innerhalb des Paketes
de.frankfurt.uni.cs.gdv.datastructures.trees ist
de.frankfurt.uni.cs.gdv.datastructures.trees.A
Das Paket de.frankfurt.uni.cs.gdv.datastructures ist ein Unterpaket vom Paket
de.frankfurt.uni.cs.gdv.datastructures.trees und kann ebenfalls Klassen und Interfaces
beinhalten. Soll eine Klasse z.B. innerhalb eines (Unter-)Pakets de.frankfurt.uni.cs.gdv geladen
werden, so wird sie im Verzeichnis „de/frankfurt/uni/cs/gdv“ gesucht.
Klassen, die nicht explizit einem Paket zugeordnet sind, werden im sog. default-package gespeichert.
Dies ist der Ordner, in dem die Klasse liegt.
Die Java-API 6.0 enthält etwa 202 Pakete, die 6 377 Klassen und Interfaces umfassen.
8.11 Import
Will eine Klasse auf Bestandteile (z.B. Klassen oder Interfaces) eines Paketes zugreifen, so müssen
diese Bestandteile durch eine import-Anweisung importiert werden, ehe sie benutzt werden können.
Eine Klasse zu importieren bedeutet, sie samt ihren Attributen und Methoden verfügbar zu machen. Es
bedeutet keinesfalls, dass sie in die JVM geladen wird. Ihr Name wird lediglich in den Namensraum
der Codedatei eingefügt.
Jede Klasse oder Schnittstelle, die nicht im eigenen Paket liegt, und verwendet werden soll, muss durch
eine import-Anweisung importiert werden.
Eine Ausnahme stellen hier die Bestandteile des Pakets java.lang dar. Da dieses Paket eines der
wichtigsten Pakete der Java-API ist, werden alle in ihm enthaltenen Klassen vor jedem Compilerlauf
automatisch importiert. Das Paket umfasst Klassen, die häufig von Programmen verwendet werden;
dazu gehören String, Object, Math, Integer, Boolean, etc.
Die folgende Anweisung importiert die Klasse ArrayList aus dem Paket java.util:
//importiert ArrayList
import java.util.ArrayList;
Mit der nachstehenden import-Anweisung werden alle im Paket java.util enthaltenen Klassen und
Interfaces importiert:
//importiert alle Klassen und Interfaces
import java.util.*;
54
8.12 Statischer Import
Außerhalb einer Klasse können ihre statischen Methoden und Konstanten nur über den Klassennamen
angesprochen werden:
long longMaxValue=Long.MAX_VALUE;
Seit Version 5 gibt es mit dem statischen Import die Möglichkeit, die statischen Methoden und
Konstanten ohne den vorangestellten Klassennamen anzusprechen. Hierzu müssen sie statisch
importiert werden. Vergleiche folgende Beispiele:
/*
* Package: default
* Class: StaticImport.java
* Method:
*/
//non static import of java.lang.Math
import java.lang.Math.*; //eigentlich überflüssig
public class WithoutStaticImport{
public static void main(String... args) {
System.out.println("Wurzel aus "+Math.PI+" ist"
+Math.sqrt(Math.PI));
}
}
/*
* Package: default
* Class: StaticImport.java
* Method:
*/
//static import of java.lang.Math
import static java.lang.Math.*;
public class StaticImport {
public static void main(String... args) {
System.out.println("Wurzel aus "+PI+" ist"
+sqrt(PI));
}
}
55
8.13 Datenkapselung und Modifier
Durch Datenkapselung sollen einem Objekt nur diejenigen Bestandteile einer Klasse zugänglich
gemacht werden, die für das Objekt relevant sind. Alles andere bleibt verborgen. Zur Unterstützung
des Datenkapselungsaspektes definiert Java sog. Modifier. Modifier sind Schlüsselwörter, die dem
Compiler Informationen wie z.B. Sichtbarkeit, Veränderbarkeit und Lebensdauer von Klassen,
Variablen, Konstruktoren, Methoden und Initialisierern geben.
//modifier
public, private, protected, final, abstract, static,
native, transient, synchronized, volatile
Die Modifier public, private und protected werden als access-Modifier (Zugriffsattribute)
bezeichnet.
Die access-Modifier legen fest, wie auf Klassenfeatures zugegriffen werden darf. Unter Klassenfeature
ist im Folgenden eine Variable, eine Methode, ein Konstruktor oder die Klasse selbst gemeint.
8.13.1 public
Auf als public deklarierte Features kann in jeder Klasse ohne Einschränkung zugegriffen werden.
8.13.2 private
private-Features
können nur innerhalb ihrer eigenen Klasse verwendet werden.
8.13.3 default
Wird ein Feature ohne Modifier deklariert, so kann auf dieses nur innerhalb von Klassen zugegriffen
werden, die im eigenen Paket liegen. Man spricht hier von einem default-Zugriff.
8.13.4 protected
protected ist ein Modifier, der nur zur Deklaration von Attributen, Methoden und Konstruktoren
jedoch nicht für Klassen verwendet werden kann. Auf ein protected-Feature F einer Klasse kann
innerhalb aller Klassen seines Pakets P uneingeschränkt zugegriffen werden. Darüber hinaus können
Unterklassen außerhalb von P eingeschränkt auf F zugreifen. Eingeschränkt bedeutet, dass ein Zugriff
innerhalb des Klassenrumpfes jedoch nicht in Methoden und Konstruktoren erlaubt ist, wie das
folgende Beispiel zeigt:
56
/*
* Package: modifier1
* Class: ModifierClass.java
* Method:
*/
package modifier1;
public class ModifierClass {
public byte publicVar = 1;
protected byte protectedVar = 3;
byte defaultVar = 4;
private byte privateVar = 2;
public byte getPublicVar() {
return publicVar;
}
private byte getPrivateVar() {
return privateVar;
}
protected byte getProtectedVar() {
return protectedVar;
}
byte getDefaultVar() {
return defaultVar;
}
}
/*
* Package: modifier1
* Class: ForeignClassInTheSamePackage.java
* Method:
*/
package modifier1;
public class ForeignClassInTheSamePackage {
public static void main(String... arg) {
ModifierClass modifier = new ModifierClass();
System.out.println(modifier.publicVar);
System.out.println(modifier.protectedVar);
System.out.println(modifier.defaultVar);
System.out.println(modifier.privateVar);//Kompilierfehler
System.out.println(modifier.getPublicVar());
System.out.println(modifier.getProtectedVar());
System.out.println(modifier.getDefaultVar());
System.out.println(modifier.getPrivateVar());//Kompilierfehler
}
}
57
/*
* Package: modifier2
* Class: ForeignClassInDifferentPackage.java
* Method:
*/
package modifier2;
import modifier1.ModifierClass;
public class ForeignClassInDifferentPackage {
public static void main(String... arg) {
ModifierClass modifier = new ModifierClass();
System.out.println(modifier.publicVar);
System.out.println(modifier.getPublicVar());
System.out.println(modifier.protectedVar);// Kompilierfehler
System.out.println(modifier.defaultVar);// Kompilierfehler
System.out.println(modifier.privateVar);// Kompilierfehler
System.out.println(modifier.getProtectedVar());// Kompilierfehler
System.out.println(modifier.getDefaultVar());// Kompilierfehler
System.out.println(modifier.getPrivateVar());// Kompilierfehler
}
}
class SubClassOfModifierClass extends ModifierClass {
byte protected1=protectedVar;// erlaubter Zugriff auf
// ein protected Feature;
byte protected2=super.getProtectedVar();// erlaubter Zugriff auf
// ein protected Feature;
}
public static void main(String... arg) {
ModifierClass modifier = new ModifierClass();
System.out.println(modifier.publicVar);
System.out.println(modifier.getPublicVar());
System.out.println(modifier.protectedVar);//Kompilierfehler
System.out.println(modifier.defaultVar);//Kompilierfehler
System.out.println(modifier.privateVar);//Kompilierfehler
System.out.println(modifier.getProtectedVar());//Kompilierfehler
System.out.println(modifier.getDefaultVar());// Kompilierfehler
System.out.println(modifier.getPrivateVar());// Kompilierfehler
}
}
58
8.13.5 final
kann für Klassen, Variablen und Methoden verwendet werden. Von einer finalKlasse kann nicht abgeleitet werden. Ein Beispiel für eine final-Klasse ist die Klasse
java.lang.Math. Der Versuch diese Klasse abzuleiten führt zu einer Fehlermeldung:
Der final-Modifier
public class MyMath extends Math{//Kompilierfehler
}
Kompilierfehler: The Type MyMath cannot subclass the final class Math
final–Variablen sind die Umsetzung von Konstanten in Java. Es gibt kein anderes explizites
Schlüsselwort dafür. Der Wert einer final-Variablen kann nach ihrer Initialisierung nicht mehr
verändert werden.
Die Klasse Math verfügt über die Konstante PI. Der Versuch, den Wert von PI zu verändern, führt zu
einem Kompilierfehler:
Math.PI=22/7f;
Fehlermeldung: The final field Math.PI cannot be assigned.
final-Methoden
dürfen nicht überschrieben werden:
/*
* Package: modifier1
* Class: Final.java
* Method:
*/
public class Final {
public int x=2;
public final void setX(int x){
this.x=x;
}
public final void setX(){
this.x=x;
}
}
class FinalApplication extends Final{
public final void setX(){//Kompilierfehler
}
public static void main(String ...strings ){
final Final x=new Final();
x.x=2;
}
}
Kompilierfehler: Cannot override the final method.
59
9 Abstrakte Klassen und Methoden
Es kommt häufig vor, dass der Programmierer das Verhalten einer Methode in einer Superklasse nicht
definieren kann.
Die Methode getSurface() der Klasse GeometricObject (im Beispiel unten) soll die Fläche des
geometrischen Objektes berechnen und zurück liefern. Da die Fläche eines geometrischen Objektes
von seiner konkreten Implementierung abhängt, ist eine sinnvolle Implementierung der Methode
getSurface() in der Oberklasse nicht möglich. In solchen Fällen wird die Methode als abstract
deklariert. Ihre Implementierung wird dann auf die Unterklassen verlagert. Eine als abstract
deklarierte Methode wird abstrakte Methode genannt. Im Gegensatz zu einer konkreten Methode
besteht sie nur aus einer Deklaration, enthält keine Implementierung und darf nicht als private,
static oder final deklariert werden. Anstelle der geschweiften Klammern des Methodenrumpfes
wird ein Semikolon gesetzt.
Da nun die Klasse eine abstrakte Methode besitzt, wird sie zu einer abstrakten Klasse und muss
ebenfalls als abstract deklariert werden. Mit anderen Worten: Eine Klasse, die mindestens eine
abstrakte Methode enthält, muss ebenfalls als abstract deklariert werden. Damit ist es nicht mehr
möglich, eine Instanz dieser Klasse zu erzeugen.
Eine Unterklasse der abstrakten Klasse erbt nun die abstrakten Methoden und muss diese
überschreiben. Tut sie das nicht, muss sie selbst als abstrakte Klasse definiert werden. Nicht alle
Methoden einer abstrakten Klasse müssen abstrakt sein. Eine abstrakte Klasse kann auch konkrete
Methoden enthalten.
60
/*
* Package: modifier2
* Class: GeometricObject.java
* Method:
*/
public abstract class GeometricObject {
public
public
public
public
public
static final
static final
static final
static final
int type;
int
int
int
int
CIRCLE=0;
RECTANGLE=1;
TRIANGLE=2;
UNDEFINED=-1;
public abstract float getSurface();//abstract method
public int getType(){//concrete method
return type;
}
}
class Rectangle extends GeometricObject{
public float width;
public float height;
public Rectangle(float width, float height){
this.width=width;
this.height=height;
this.type=GeometricObject.RECTANGLE;
}
public float getSurface(){
return width*height;
}
}
class Triangle extends GeometricObject{
public float base;
public float height;
public Triangle(float base, float height){
this.base=base;
this.height=height;
this.type=GeometricObject.TRIANGLE;
}
public float getSurface(){
return .5f*base*height;
}
}
class Circle extends GeometricObject{
public float radius;
public float getSurface(){
return (float)(Math.PI*radius*radius);
}
}
61
10 Interfaces
Ein Interface (Schnittstelle) in Java kann als eine abstrakte Klasse aufgefasst werden, die nur abstrakte
Methodendeklarationen und statische Konstanten enthält. Sie werden mit dem Schlüsselwort
interface deklariert und dürfen keine Konstruktoren definieren.
/*
* Package: interfaces
* Class: GeometricObjectInterface.java
* Method:
*/
public interface GeometricObjectInterface {//Interface declaration
public static final int CIRCLE=0;
public static final int RECTANGLE=1;
public static final int SQUARE=2;
/**
* Returns the type of the geometric object (circle, square,
*
rectangle, etc. )
*
*/
public int getType();
/**
* Returns the circumference of the geometric object
*
*/
public float getCircumference();//Berechnet den Umfang
// eines geometrischen
//Objektes
}
Alle in einem Interface enthaltenen Attribute sind automatisch statische Konstanten, die initialisiert
werden müssen. Alle Methoden sind automatisch public auch wenn wie nicht als public deklariert
sind. Eine Klasse, die ein Interface implementiert, muss alle Methoden des Interfaces überschreiben:
/*
* Package: interfaces
* Class: Circle.java
* Method:
*/
Public class Circle implements GeometricObjectInterface{
public float radius;
public Circle(float radius){
this.radius=radius;
}
public int getType(){
return GeometricObjectInterface.CIRCLE;
}
public float getCircumference(){
return (float)(2*Math.PI*radius); }
}
62
/*
* Package: interfaces
* Class: Square.java
* Method:
*/
Public class Square implements GeometricObjectInterface{
public float side;
public Square(float side){
this.side=side;
}
public float getCircumference(){
return 4*side;
}
public int getType(){
return GeometricObjectInterface.SQUARE;
}
}
Eine Klasse, die nicht alle Methoden eines Interfaces implementiert, muss als abstract deklariert
werden.
Interfaces können wie Klassen von einander abgeleitet werden. Wie bereits erwähnt, ist die
Mehrfachvererbung in Java nicht erlaubt. Interfaces können jedoch als ein restriktiver Mechanismus
für Mehrfachvererbung verwendet werden. So kann eine Klasse mehrere Interfaces implementieren.
Sie muss dann für jedes Interface alle darin definierten Methoden implementieren.
10.1 Das Interface Comparable
public interface Comparable<T>{
public int compareTo(T o);
}
ist eines der wichtigsten Interfaces der Java-API. Eine Klasse, die dieses
Interface implementiert, definiert eine Ordnung auf ihren Objekten und macht sie paarweise
vergleichbar. Objekte von Klassen, die dieses Interface implementieren, können von Sortiermethoden
sortiert werden.
Ein Beispiel zur Verdeutlichung:
java.lang.Comparable
63
Die Objekte der Klasse Point, wie sie im Abschnitt 7.5 Vererbung implementiert ist, sind nicht
geordnet. Man kann sie nicht miteinander vergleichen und z.B. sagen, dass Punkt p1 kleiner als Punkt
p2 ist. Ein Vergleich kann dennoch in vielen Anwendungen sinnvoll sein. Wenn p1 kleiner als p2 ist,
könnte dies so interpretiert werden, dass p1 links von p2 oder p2 oberhalb von p1 liegt.
Um die Objekte der Klasse Point paarweise vergleichbar zu machen, muss Point das ComparableInterface implementieren und die einzige in ihr enthaltene Methode compareTo() überschreiben.
compareTo() liefert genau dann einen Wert kleiner 0, wenn das Objekt kleiner, größer 0, wenn es
größer und gleich 0, wenn es gleich dem als Argument übergebenen Objekt ist:
/*
* Package: interfaces
* Class: ComparablePoint.java
* Method:
*/
public class ComparablePoint extends Point implements
Comparable<ComparablePoint> {
public float x1;
public float y1;
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(ComparablePoint p) {
if(x1<p.x1 || this.y1<p.y1) return -1;
if(x1==p.x1 && this.y1==p.y1) return 0;
return 1;
}
}
Bemerkung: Alle Wrapper-Klassen und viele andere Klassen wie String, Date, File, etc.
implementieren das Comparable-Interface.
http://www.angelikalanger.com/Articles/JavaSpektrum/01.Equals-Part1/01.Equals1.html
64
11 Namenskonventionen
Eine Namenskonvention ist eine Empfehlung, wie Pakete, Klassen, Variablen, Methoden, Interfaces,
etc. sinnvoll bezeichnet werden sollen.
Bezeichner
-Typ
Konvention
Beispiele
Pakete
Paketnamen bestehen aus
Kleinbuchstaben und sollten
möglichst kurz sein. Ziffern sind
auch erlaubt
Klassennamen beginnen mit einem
Großbuchstaben und sollten Nomen
(Substantive) sein. Besteht ein
Klassenname aus mehreren Silben
wie z.B. BinaryNode,
RedBlackTree so beginnt jede Silbe
ebenfalls mit einem Buchstaben.
Methodennamen sollten Verben sein
und mit einem Kleinbuchstaben
anfangen. Darauf folgende Silben
beginnen mit einem
Großbuchstaben. Boolesche
Funktionen sollten mit „is“, „has“,
„can“, etc. anfangen
Variablennamen beginnen mit
Kleinbuchstaben. Darauf folgende
Silben fangen mit einem
Großbuchstaben an. Sie sollten kurz
und ausdrucksstark sein. Boolesche
Variablen sollten mit „is“, „has“,
„can“ etc. anfangen
Konstanten bestehen aus
Großbuchstaben. Wörter werden
durch Unterstriche getrennt.
Für Interfaces gilt die gleiche
Konvention wie für Klassen
de.frankfurt.cs.gdv.visian3d
Klassen
Methoden
Variablen
Konstanten
Interfaces
Point, ComplexNumber,
VisualBinarySearchTree,
…
run(), getLeftChild(),
setName(), isReady(),
shouldWait(), ...
age, userName, isVisible,
hasTicket, canStart, ...
shouldRestart,...
...
SIZE, MAX_VALUE, EDGE_COLOR,
...
Tabelle 6: Namenskonventionen
65
12 Object, Math und String
12.1 Object
Die Klasse java.lang.Object ist die Oberklasse aller Java-Klassen. Jede Klasse, die nicht explizit
von einer anderen Klasse abgeleitet wird, wird implizit von Object abgeleitet. Die folgenden beiden
Klassendefinitionen sind demnach äquivalent:
class A {
}
class A extends Object {
}
Die Klasse Object verfügt über zahlreiche Methoden, die in allen Klassen verfügbar sind. Die
wichtigsten drei Methoden sind public String toString(), public boolean equals(Object o)
und hashCode(). Sie sollten von den abgeleiteten Klassen überschrieben werden.
http://www.angelikalanger.com/Articles/JavaSpektrum/01.Equals-Part1/01.Equals1.html
Die toString()-Methode gibt eine String-Darstellung eines Objektes zurück. Z.B. wird die Methode
toString() automatisch aufgerufen, wenn ein Objekt einer der print-Methoden
System.out.print() oder System.out.println() übergeben wird. Sie wandelt dann das Objekt in
einen String um.
Wenn die toString()-Methode eines Objektes der Klasse Bankkonto aufgerufen wird, liefert sie einen
String, der etwa so aussehen könnte: Bankkonto@3e25a5
Bankkonto alexKonto=new Bankkonto(35870,"Alex M.",2103);
System.out.println(alexKonto);
Ausgabe: Bankkonto@3e25a5
Die Standardimplementierung der Methode liefert den Klassennamen und den Hash-Code des Objektes
im Hexadezimal-Format als String zurück:
//Standardimplementierung von toString() in der Klasse Object
public String toString(){
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}
Wenn man eine sinnvolle String-Darstellung des Objektes haben will, dann muss die Methode
überschrieben werden:
66
//Überschreibung von toString() in Bankkonto
public String toString(){
return "Kontoinformation:\r\n"+
"Kontonummer: "+kontoNummer+"\r\n"+
"Kontoinhaber(in): "+inhaber+"\r\n"+
"Kontostand: "+kontoStand+" €\r\n";
}
Bankkonto alexKonto=new Bankkonto(35870,"Alex M.",2103);
System.out.println(alexKonto);
Ausgabe:
Kontoinformation:
Kontonummer: 2150
Kontoinhaber(in): Alex M.
Kontostand: 12350.0
In Java gibt es zwei Möglichkeiten, Objekte zu vergleichen. Die erste Möglichkeit besteht in der
Verwendung des Operators ==. Dieser Operator kann auch auf primitive Datentypen angewendet
werden. Er prüft, ob zwei Variablen den gleichen Wert haben. Übertragen auf Referenzen bedeutet
dies, dass der Operator == prüft, ob zwei Referenzen auf den selben Speicherplatz und damit auf
dasselbe Objekt verweisen.
Die zweite Möglichkeit ist die Methode boolean equals(Object o). Sie ist in der Klasse
java.lang.Object definiert und wird somit an alle anderen Klassen vererbt. Der eigentliche Sinn der
Methode equals() ist, zwei verschiedene Objekte auf inhaltliche Gleichheit zu untersuchen.
Allerdings ist die Standardimplementierung von equals() in Object identisch zum Operator ==, d.h.
die von allen Klassen geerbte Methode equals() testet nur, ob zwei Referenzen auf dasselbe Objekt
zeigen:
//Standardimplementierung von equals(Object o) in der Klasse Object
public boolean equals(Object o){
return this==o;
}
Deshalb sollte die Methode equals() in sehr vielen Fällen überschrieben werden.
Ein Hash-Code ist ein Intergerwert, der die Eindeutigkeit eines Objektes repräsentiert. Zwei Objekte
die nach der equals()-Methode gleich sind, müssen den gleichen Hash-Code haben.
Die Methode hashCode() wird häufig in den Collections (Datenstrukturen) verwendet, die hashbasiert
sind. Siehe Abschnitt 15 Collections (Sammlungen).
67
12.2 Math
Die Klasse java.lang.Math enthält eine Sammlung von mathematischen Methoden und Konstanten. Sie
ist als final deklariert und kann daher nicht abgeleitet werden. Da sie einen privaten Konstruktor
enthält, darf sie nicht instanziiert werden.
//Kompilierfehler: The constructor Math() is not visible
Math math=new Math();
Die Tabelle unten zeigt eine Übersicht der wichtigsten Methoden der Klasse Math.
Methode
abs(int i)
ceil(double d)
exp(double a)
floor(double d)
log(double a)
max(int i1, i2)
min(int i1, i2)
pow(double a, double b)
random()
sqrt(double d)
sin(double d)
cos(double d)
tan(double d)
round(double d)
Rückgabe
Absolutbetrag von i
Nächste Ganzzahl größer gleich i
ea
Nächste Ganzzahl kleiner gleich i
ln(a)
Maximum der Zahlen i1 und i2
Minimum der Zahlen i1 und i2
ab
Eine Zahl aus dem Intervall[0.0,1.0]
Wurzel aus d
Sinus-Funktion
Kosinus-Funktion
Tangens-Funktion
Rundet d zur nächsten Ganzzahl
Tabelle 7: Methoden von Math
12.3 String
Eine Zeichenkette (String) in Java ist eine indizierte geordnete Folge von Unicode-Zeichen.
Strings in Java sind unveränderbar (immutable) und werden durch die Klasse java.lang.String
repräsentiert. Die Indizierung der Zeichen beginnt mit 0.
Die Klasse ist final und kann nicht abgeleitet werden. Sie stellt jedoch zahlreiche Methoden zur
Verarbeitung von Zeichenketten zur Verfügung. Ein String-Objekt kann entweder mit einem
Konstruktor der Klasse String oder als String-Literal erstellt werden:
String s2=new String("strings are immutable!")
String s3="strings are immutable!";//String-Literal
68
Eine der wichtigsten Methoden der Klasse ist die Methode equals(), die String von Object erbt und
überschreibt. Sie wird verwendet um zwei Strings auf inhaltliche Gleichheit zu testen und sollte immer
zum Vergleich von Zeichenketten verwendet werden.
Siehe Übung.
/*
* Package: strings
* Class: stringCompare.java
* Method:
*/
public static void stringCompare(){
String s1=new String("use the equals()-method
String s2=new String("use the equals()-method
String s3="use the equals()-method to compare
String s4="use the equals()-method to compare
boolean s1_s2;
boolean s3_s4;
boolean s2_s3;
if(s1==s2)
s1_s2=true;
else s1_s2=false;
to compare strings");
to compare strings");
strings";//String-Literal
strings";//String-Literal
if(s3==s4)
s3_s4=true;
else s3_s4=false;
if(s2==s3)
s2_s3=true;
else s2_s3=false;
System.out.println(s1_s2+" "+s3_s4+" "+s2_s3);
s2=s3;
if(s2==s3)
s2_s3=true;
else s2_s3=false;
System.out.println(s2_s3);
}
Ausgabe: false true false
true
http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht20Ht/java_gebrauch_von_objekten_de
Im Folgenden werden weitere wichtige Methoden der Klasse String kurz erläutert.
int length():Liefert
die Länge der Zeichenkette.
String str="strings are immutable!";
System.out.println(str.length());//liefert 22
69
char charAt(int index):
Liefert das Zeichen an Position index. index muss ein Wert zwischen 0 und length()-1 sein.
Wird charAt() mit einem Index außerhalb dieses Intervalls aufgerufen, so wird eine Exception
geworfen (java.lang.StringIndexOutOfBoundsException). Exceptions werden im folgenden
Kapitel 00ausführlich behandelt.
String str="strings are immutable!";
System.out.println(str.charAt(str.length()-1));//liefert '!'
String substring(int begin,
int end):
Liefert den Teilstring, der am Index begin beginnt und bei end-1 einschließlich endet.
String str="strings are immutable!";
System.out.println(str.substring(2,6));//liefert "ring"
String substring(int begin):Liefert
den Teilstring von der angegebenen Position bis zum
Ende des Strings.
String str="strings are immutable!";
System.out.println(str.substring(12));//liefert "immutable!"
System.out.println(str.substring(12,str.length()));//liefert "immutable!"
String trim():Liefert
den String, der entsteht, wenn alle Leerzeichen am Anfang und am Ende
des Strings entfernt werden.
String str=" etc. ";
System.out.println(str.trim());//liefert "etc."
boolean EqualsIgnoreCase():Vergleicht
Strings miteinander ohne Groß-/Kleinschreibung zu
beachten.
String str="strings are immutable!";
System.out.println(str.equalsIgnoreCase("Strings are immutable!"));
//liefert true
System.out.println(str.equals("Strings are immutable!"));
//liefert false
70
bzw. String toUpperCase():Liefert den String zurück, der entsteht,
wenn alle Zeichen in Kleinbuchstaben bzw. in Großbuchstaben umgewandelt werden.
String toLowerCase()
String str="Strings Are Immutable!";
System.out.println(str. toLowerCase());//liefert "strings are immutable!"
System.out.println(str. toUpperCase());//liefert "STRINGS ARE IMMUTABLE!"
String replace(char oldChar, char newChar):
Ersetzt alle Vorkommen des Zeichens oldChar durch das
Zeichen newChar.
System.out.println("Kik".replace('k', 'K'));//liefert KiK
71
13 Ausnahmen (Exceptions)
Ausnahmen (Exceptions) sind Java-Klassen zur Erkennung und Behandlung von Laufzeitfehlern. Ein
Exception-Objekt repräsentiert einen Fehler, der während der Ausführung eines Programms auftritt
und den normalen Ablauf des Programms stört oder gar beendet. Laufzeitfehler können durch den
Compiler i.Allg. nicht erkannt werden. Es gibt drei unterschiedliche Arten von Laufzeitfehlern in
Java:
1. Vermeidbare Fehler (Bugs):
Das sind Fehler, die durch den Programmierer verursacht werden und vermieden werden
können wie z.B. Division durch 0, Zugriff auf einen ungültigen Array-Index, Zugriff auf
eine null-Referenz, etc.
/*
* Package: exceptions
* Class:
Bugs.java
* Method: buggyMethod
*/
public void buggyMethod(){
Random rand=new Random();
int x=rand.nextInt(6);
System.out.println("x="+x);
int y=2/x;//Division durch Null -->
//ArithmeticException:/by zero
int size=x*2-3;
int [] list = new int[size];//negativer Wert für Array-Länge
//-->NegativeArraySizeException
float [] array=new float[x];
array[x-3]=3.5f;//ungültiger Array-Index-->
//ArrayIndexOutOfBoundsException: 5
String str="extract @";
str.charAt(x-4);//ungültiger Index -->
if(x==4)
//StringIndexOutOfBoundsException
Bugs [] bugs=new Bugs[x];
bugs[0].buggyMethod();//Aufruf einer Methode einer
//null-Referenz--> NullPointerException
System.out.println("Keine Exceptions");
}
2. Unvermeidbare Fehler:
Das sind Fehler, die der Programmierer nicht vermeiden kann. Beispiele hierfür:
a. Ein Programm versucht auf einen Server zuzugreifen,
der gerade ausgeschaltet ist:
URL apacheServer = new URL("http://www.apache.org/");
connect(apacheServer);
b.
Ein Programm versucht eine Datei zu öffnen, die
beschädigt ist oder gelöscht wurde.
72
c.
Benutzerfehler: Ein Programm fordert den Benutzer auf, eine Zahl
einzugeben. Stattdessen gibt der Benutzer Buchstaben ein.
3. Systemfehler (System Errors):
Das sind schwerwiegende nicht behebbare Fehler, nach deren Auftreten die Fortsetzung des
Programms nicht sinnvoll bzw. gar nicht möglich ist. Sie führen zu einem
Programmabbruch und zum Beenden des Interpreters. Beispiele hierfür sind
Speicherüberlauffehler (Memory Overflow) oder Fehler in der JVM selbst.
Wenn ein Programmteil (eine Methode oder Anweisung) einen Fehler verursacht, löst die JVM eine
Exception aus (throwing exception), die vom Programm abgefangen (catching exception) und
behandelt (handling exception) werden kann. Zur Behandlung einer Ausnahme übergibt die JVM ein
Exception-Objekt an das Programm, das Informationen über den Fehler enthält wie z.B. Fehlerart,
Fehlerbezeichnung, Programmzustand zum Zeitpunkt des Auftretens des Fehlers, etc.
Das Behandeln von Ausnahmen erfolgt mittels einer try-catch-Anweisung:
/*
* Package: exceptions
* Class:
SimpleException.java
* Method: convertStringToNumber
*/
//try to convert a string to a number
convertStringToNumber(String numberString){
try{
/*
If numberString can’t be converted to a number, the method
parseFloat will throw a NumberFormatException.
*/
float number = Float.parseFloat(numberString);
System.out.println("Converting "+numberString+
" to a number succeeded: "+number);
}//Catching the exception
catch (NumberFormatException exceptionObject){
//handling the exception
System.out.println("Exception: Unable to convert "
+numberString+" to a number!");
System.out.println(exceptionObject.getMessage());
System.out.println("Printing the stack trace...");
exceptionObject.printStackTrace();
}
}
convertStringToNumber("549.34");
Ausgabe: Converting 549.34 to a number succeeded: 549.34
convertStringToNumber("549,34");
Ausgabe: Exception: Unable to convert 549,34 to a number!
For input string: "549,34"
Printing the stack trace...
java.lang.NumberFormatException: For input string: "549,34"
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at java.lang.Float.parseFloat(Unknown Source)
at
exceptions.SimpleException.convertStringToNumber(SimpleException.java:25)
at exceptions.SimpleException.main(SimpleException.java:39)
73
13.1 Throwable
Wie eingangs erwähnt, sind Exceptions gewöhnliche Java-Klassen. Die Vaterklasse aller Exceptions
ist die Klasse java.lang.Throwable.
Throwable verfügt u.a. über zwei wichtige Methoden: getMessage() und printStackTrace(), die in
allen ihrer Unterklassen verfügbar sind. Wenn ein Laufzeitfehler auftritt, generiert die JVM ein
Ausnahme-Objekt und speichert in ihm eine Textnachricht. Die Nachricht beschreibt die Umstände, in
denen sich der Fehler ereignete, und kann über die Methode getMessage() abgerufen werden. Die
Ausgabe von getMessage() im vorigen Beispiel sieht so aus:
For input string: "549,34"
Die Methode printStackTrace() liefert eine Art Momentaufnahme des Programmstacks (stack
trace) zum Zeitpunkt des Auftretens des Fehlers in Form einer Textnachricht:
java.lang.NumberFormatException: For input string: "549,34"
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at java.lang.Float.parseFloat(Unknown Source)
at
exceptions.SimpleException.convertStringToNubmer(SimpleException.java:25)
at exceptions.SimpleException.main(SimpleException.java:39)
Die Textnachricht enthält die Zeilennummer im Quellcode, in der der Fehler auftrat:
SimpleException.java:25
Sie zeigt auch die Stelle (Klassenname und Zeilennummer), von der aus die Methode aufgerufen
wurde, die die Ausnahme ausgelöst hatte:
SimpleException.main(SimpleException.java:39)
13.2 Ausnahmetypen
Grundsätzlich gibt es in Java zwei Arten von Ausnahmen: geprüfte Ausnahmen (checked exceptions)
und Laufzeitausnahmen (runtime exceptions), auch ungeprüfte Ausnahmen (unchecked exceptions)
genannt. Wie oben erwähnt gibt es in Java drei Fehlerklassen: vermeidbare Fehler (Bugs),
unvermeidbare Fehler und Systemfehler.
Laufzeitausnahmen repräsentieren vermeidbare Fehler. So ist z.B. die Ausnahme
ArithmeticException, die u.a. ausgelöst wird, wenn der Programmierer eine Division durch 0
durchführt. Eine Laufzeitausnahme:
int x=1/3;
int y=2/x;//Division durch null -->
// Laufzeitausnahme: ArithmeticException:/by zero
74
Die Ausnahmen ArrayIndexOutOfBoundsException, NegativeArraySizeException,
NullPointerException und StringIndexOutOfBoundsException aus dem Programmbeispiel
exceptions. Bugs sind ebenfalls Laufzeitausnahmen. Sie alle hätte der Programmierer vermeiden
können. Die geprüften Ausnahmen dagegen repräsentieren unvermeidbare Fehler und Systemfehler.
Die Ausnahmen FileNotFoundException und IOException im folgenden Beispiel sind geprüfte
Ausnahmen:
/*
* Package: exceptions
* Class:
SimpleException.java
* Method: printFile
*/
public void printFile(String fileName){
//try to read and print the given file
BufferedReader input = null;
try {
input = new BufferedReader( new FileReader(fileName) );
String line = null;
while (( line = input.readLine()) != null){
System.out.println(line);
}
}
catch (IOException ex){
ex.printStackTrace();
}
}
Es ist leicht, Laufzeitausnahmen und geprüfte Ausnahmen auseinander zu halten. Die Vaterklasse aller
Laufzeitausnahmen ist die Klasse java.lang.RunTimeException. Alle Exception-Klassen, die weder
direkt noch indirekt von RunTimeException abgeleitet sind, sind geprüfte Ausnahmeklassen.
In Bezug auf die Behandlung von Ausnahmen gibt es einen wesentlichen Unterschied zwischen
Laufzeitausnahmen und geprüften Ausnahmen. Laufzeitausnahmen können, müssen jedoch nicht
abgefangen und behandelt werden. So könnte man in der Methode convertStringToNubmer() im
obigen Beispiel die ArithmeticException abfangen und behandeln, indem man sie mit einer trycatch-Anweisung umschließt:
try{
int x=1/3;
int y=2/x;//Division durch null -->ArithmeticException: / by zero
} catch(ArithmeticException exceptionObject){//Catching the exception
//handling the exception
System.out.println("x ist 0. Division wurde nicht ausgeführt!");
}
Ähnlich kann man bei der Behandlung der Ausnahmen ArrayIndexOutOfBoundsException,
NegativeArraySizeException, NullPointerException und
StringIndexOutOfBoundsException vorgehen.
75
Geprüfte Ausnahmen dagegen müssen vom Programmierer mittels einer try-catch-Anweisung
immer abgefangen und behandelt werden. Wenn man die try-catch-Anweisung in der Methode
printFile() im obigen Beispiel weglassen würde, würde der Compiler mit einem Fehler abbrechen.
76
13.3 Auslösen einer Exception
Der Programmierer kann Methoden definieren, die selbst Ausnahmen auslösen. Jede Methode kann so
deklariert werden, dass sie eine oder mehrere Ausnahmen wirft. Dies erfolgt mithilfe der Anweisung
throws wie das folgende Beispiel zeigt:
/*
* Package: exceptions
* Class:
SimpleException.java
* Method: factorial
*/
public long factorial(int n) throws NegativeValueException {
//if n<0 then throw an exception
if(n<0) throw new NegativeValueException ("invalid parameter: "+n);
long result=1;
for(int i=2;i<=n;i++)
result*=i;
return result;
}}
Da factorial() eine geprüfte Ausnahme wirft (siehe API-Dokumentation), muss jede andere
Methode, die factorial() aufrufen will, dies innerhalb einer try-catch-Anweisung tun:
public long calculator(int n) {
// the calculator method calls the factorial method
try{
return factorial(n);
} catch (Exception exceptionObject){
System.out.println("The input parameter of factorial "+
+"must be a positive number!");
}
13.4 Weitergabe von Exceptions
In vorigen Abschnitt wurde erwähnt, dass geprüfte Ausnahmen immer abgefangen und behandelt
werden müssen. Es ist aber:
public long calculator(int n) throws Exception {
int x= n/2;
return factorial(x);
}
77
Im obigen Beispiel behandelt die Methode calculator() keine Ausnahmen. Wenn factorial() eine
Ausnahme wirft, leitet sie calculator() an die aufrufende Methode (die Methode, die calculator()
aufruft) weiter.
13.5 Behandlung von mehreren Ausnahmen
Es ist durchaus möglich, dass innerhalb einer try-catch-Anweisung mehrere unterschiedliche
Ausnahmen auftreten. Wenn ein Programm mehr als eine Ausnahme behandeln will, dann muss es für
jede Ausnahme eine eigene catch-Anweisung bereitstellen, wie das folgende Beispiel zeigt:
public void printFile(String fileName){
//try to read and print the given file
BufferedReader input = null;
try {
input = new BufferedReader( new FileReader(fileName) );
String line = null;
while (( line = input.readLine()) != null){
System.out.println(line);
}
}
catch (FileNotFoundException ex) {
ex.printStackTrace();
}
catch (IOException ex){
ex.printStackTrace();
}
}
13.6 finally
Am Ende einer try-catch-Anweisung kann ein finally-Block stehen, der immer ausgeführt wird,
unabhängig davon, ob eine Ausnahme aufgetreten ist oder nicht. Tritt eine Ausnahme auf, so wird der
entsprechende catch-Block betreten und ausgeführt. Danach wird der finally-Block ausgeführt. Tritt
keine Ausnahme auf, so wird nur der finally-Block ausgeführt:
78
/*
* Package: exceptions
* Class:
Bugs.java
* Method: finallyMethod
*/
public void finallyMethod(){
try{ Random rand=new Random();
int x=rand.nextInt(6);
System.out.println("x="+x);
int y=2/x;//Division durch null -->ArithmeticException:/by zero
int size=x*2-3;
int [] list = new int[size];//negativer Wert für Array-Länge -->
//NegativeArraySizeException
float [] array=new float[x];
array[x-3]=3.5f;//ungültiger Array-Index-->
//ArrayIndexOutOfBoundsException: 5
String str="extract @";
str.charAt(x-4);//ungültiger Index -->
//StringIndexOutOfBoundsException
Bugs [] bugs=new Bugs[x];
if(x==4)
bugs[0].buggyMethod();//Aufruf einer Methode einer null-Referenz
//--> NullPointerException
System.out.println("Keine Exceptions");
}
catch (ArithmeticException arithmeticExceptionObject){
System.out.println("ArithmeticException...");
}
catch (NegativeArraySizeException arithmeticExceptionObject){
System.out.println("NegativeArraySizeException...");
}
catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsExOb){
System.out.println("ArrayIndexOutOfBoundsException...");
}
catch (StringIndexOutOfBoundsException stringIndexOutOfBoundsExOb){
System.out.println("StringIndexOutOfBoundsException...");
}
catch (NullPointerException nullPointerExceptionObject){
System.out.println("NullPointerException...");
}
finally{
System.out.println("Finally-Block");
}
}
79
13.7 Eigene Exception-Klasse deklarieren
Es ist einfach eine eigene Exception-Klasse in Java zu deklarieren. Zunächst muss entschieden werden,
ob die neue Ausnahme eine geprüfte oder eine Laufzeitausnahme sein soll. Im Falle einer
Laufzeitausnahme muss die Klasse entweder direkt von der Klasse java.lang.RuntimeException
oder einer ihrer Klassen abgeleitet werden. Im Falle einer geprüften Ausnahme muss die Klasse
entweder direkt aus java.lang.Exception oder einer ihrer Unterklassen abgeleitet werden. Der Name
der Klasse sollte mit „Exception“ enden z.B. KreditLimitUeberschrittenException oder
NegativeValueException.
Die Klasse sollte zwei Konstruktoren deklarieren: einen parameterlosen Konstruktor und einen
Konstruktor, der als Parameter eine Textnachricht (message) übergeben bekommt. Dies ist der Text,
der zurückgegeben wird, wenn getMessage() aufgerufen wird.
Beispiele:
/**
* Checked exception used in the method factorial of the class
* SimpleException
*
*/ public class NegativeValueException extends Exception{
public NegativeValueException(){
}
public NegativeValueException(String message){
super(message);
}
}
/**
* Runtime exception used in the method auszahlen() of the class
* exceptions.Bankkonto
*/
public class KreditLimitUeberschrittenException extends
RuntimeException{
public KreditLimitUeberschrittenException(){
}
public KreditLimitUeberschrittenException(String message){
super(message);
}
}
80
14 Generics
Generics sind eine der wichtigsten Erweiterungen von Java in der Version 5. Das Grundprinzip des
Konzeptes soll anhand eines einfachen Beispiels erläutert werden. Betrachten wir die folgende
Implementierung von Bubble-Sort:
/*
* Package: generics
* Class:
NonGenericBubbleSort.java
* Method:
*/
/**
* Sortiert ein Integer-Array
*
*/
class NonGenericBubbleSort{
public void bubbleSort (Integer [] A){
for (int i=0;i<A.length;i++) {
for(int j=A.length-1;j>=i+1;j--){
if (A[j]<A[j-1])){
swap(A, j, j-1);//vertausche A[i] und A[j]
}
}
}
}
public void swap(Integer []A,int i, int j){//vertauscht A[i] und A[j]
Integer temp=A[i];
A[i]=A[j];
A[j]=temp;
}
}//Ende NonGenericBubbleSort
class BubbleSorter {
public static void main(String[] args) {
Integer[] A=new Integer[]{-1,0,4,2,1,0,3};
NonGenericBubbleSort bubbleSorter=new NonGenericBubbleSort();
bubbleSorter.bubbleSort(A);
for (Integer element : A)
System.out.print(element+" ");
}
}
Ausgabe: -1 0 0 1 2 3 4
81
Ein Sortieralgorithmus kann Elemente beliebiger Datentypen sortieren,
sofern diese paarweise vergleichbar sind (auf den Elementen ist eine
Ordnungsrelation definiert; siehe Abschnitt
10.1 Das Interface Comparable). Die obige Implementierung beschränkt sich jedoch nur auf das
Sortieren von Integer-Zahlen. Wenn Elemente eines anderen Typs (z.B. Float) sortiert werden sollen,
muss der Code an mindestens drei Stellen angepasst und die Methoden bubbleSort() und swap()
überladen werden:
/*
* Package: generics
* Class:
NonGenericBubbleSort.java
* Method:
*/
//1. Überladung der Methode bubbleSort()
public void bubbleSort (Float [] A){ //
//...
}
public void swapFloat(Float []A,int i, int j){
Float temp=A[i]; // Anpassung
A[i]=A[j];
A[j]=temp;
}
}
Für die sieben unterschiedlichen primitiven Datentypen (int, long, double, …) müssten dann die
beiden Methoden jeweils 6 Mal überladen werden. Darüber hinaus muss die temp-Variable in der
swap-Methode angepasst werden. Insgesamt hätte man 14 Methoden, die sich kaum unterscheiden.
Dies ist kein guter Stil, einen einfachen Sortieralgorithmus zu implementieren.
Dank des Generics-Konzeptes ist es nun möglich, die nicht generische Klasse NonGenericBubbleSort
in eine generische Klasse umzuschreiben, die Elemente beliebiger Datentypen mit einer einzigen
Implementierung der Methoden bubbleSort und swap sortieren kann:
/*
* Package: generics
* Class:
GenericBubbleSort.java
* Method:
*/
/**
* Generische Klasse zur Sortierung von Integer-Arrays
*
*/
class GenericBubbleSort<T extends Object & Comparable>{
public void bubbleSort(T [] A){
for (int i=0;i<A.length;i++) {
for(int j=A.length-1;j>=i+1;j--){
if (A[j].compareTo(A[j-1])<0){//wenn A[j]<A[j-1]
swap(A, j, j-1);//vertausche A[i] und A[j]
}
}
}
82
}
public void swap(T []A,int i, int j){//vertauscht A[i] und A[j]
T temp=A[i];
A[i]=A[j];
A[j]=temp;
}
}//Ende GenericBubbleSort
class BubbleSorter {
public static void main(String[] args) {
GenericBubbleSort<Integer>bubbleSorter=newGenericBubbleSort<Integer>();
bubbleSorter.bubbleSort(new Integer[]{-1,0,4,2,1,0,3});
}}
Neu sind die spitzen Klammern nach dem Klassennamen (<T extends Object & Comparable>). Sie
besagen, dass die Klasse GenericBubbleSort eine generische Klasse ist, die Elemente eines nicht näher
spezifizierten Datentyps namens T verwendet. Alles was über T bekannt ist, ist dass er von der Klasse
Object abgeleitet ist und das Comparable-Interface implementiert. Dies macht seine Elemente
paarweise vergleichbar. T wird auch Typparameter genannt. GenericBubbleSort ist eine
parametrisierte Klasse.
Um die generische Klasse GenericBubbleSort in BubbleSorter nutzen zu können, müssen wir sie
mit einem konkreten Typ (parametrisierter Typ) etwa Integer, Float oder String instanziieren. Der
konkrete Typ steht in spitzen Klammern hinter dem Klassennamen:
//Sortierung von Integer-Zahlen
GenericBubbleSort<Integer>bubbleSorter=newGenericBubbleSort<Integer>();
bubbleSorter.bubbleSort(new Integer[]{-1,0,4,2,1,0,3});
//Sortierung von Double-Zahlen
GenericBubbleSort<Double> bubbleSorter =new GenericBubbleSort<Double>();
bubbleSorter.bubbleSort(new Double[]{-1.2,0.2,-4.01,2.1,1.2,0.5,3.4});
//Sortierung von Strings
GenericBubbleSort<String> bubbleSorter =new GenericBubbleSort<String>();
bubbleSorter.bubbleSort(new String[]{"is","generics","neu"});
Mit der Implementierung von BubbleSort als eine generische Klasse lassen sich Arrays beliebiger
Datentypen mit einer einzigen Implementierung der Methoden bubbleSort() und swap() sortieren.
Nicht nur Klassen können als generisch deklariert werden, sondern auch Methoden. Zusätzlich zu den
generischen Typen lassen sich auch generische Methoden definieren. Statt die gesamte Klasse
BubbleSort als generisch zu deklarieren, könnte man nur die Methoden bubbleSort() und swap()
generisch deklarieren:
83
//Die Methode bubbleSort ist nun generisch
public<T extends Object & Comparable<T>> void bubbleSort(T [] A){
//gleiche Implementierung wie in GenericBubbleSort
}
//Die Methode swap ist nun generisch
public<T extends Object & Comparable<T>> void swap(T []A,int i, int j){
//gleiche Implementierung wie in GenericBubbleSort
}
}//Ende BubbleSortWithGenericMethods
BubbleSortWithGenericMethods genericBubbleSorter4=new BubbleSortWithGenericMethods();
genericBubbleSorter4.<Integer>bubbleSort(new Integer[]{-1,0,4,2,1,0,3});
genericBubbleSorter4.<Double>bubbleSort(new Double[]{-1.2,0.2,-4.01,2.1,1.2});
genericBubbleSorter4.<String>bubbleSort(new String []{"is","generics","neu"});
Auch hier besagen die spitzen Klammern, die hinter dem Modifikator stehen, dass die Methode einen
Datentyp namens T verwendet, von dem nur bekannt ist, dass seine Objekte das Comparable-Interface
implementieren.
Mehr zum Thema Generics unter:
http://angelikalanger.com/Articles/JavaMagazin/Generics/GenericsPart1.html
und
http://angelikalanger.com/Articles/JavaMagazin/Generics/GenericsPart2.html
84
15 Collections (Sammlungen)
Die Effizienz eines Algorithmus hängt sehr eng mit der Wahl der richtigen Datenstruktur zusammen.
Collections in Java sind Datenstrukturen, die als Container aufgefasst werden können, in denen
Objekte gespeichert, verwaltet und manipuliert werden können. Collections können nur Objekte eines
nicht primitiven Typs speichern.
Den Kern des Collections-Frameworks bilden die sechs Interfaces Collection, List, Set,
und SortedMap:
SortedSet, Map
Abbildung 9: Collection
Alle Collections außer den Maps implementieren das Collection-Interface. Das Interface stellt u.a.
Methoden zum Suchen, Einfügen und Entfernen von Elementen.
//Collection-Interface
Public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
}
Grundsätzlich kann man die Datenstrukturen des Collection-Frameworks in drei Kategorien
unterteilen: Listen (Lists), Mengen (Sets) und Tabellen (Maps).
Listen sind Sammlungen, die dynamischen Arrays ähneln. Ihre Elemente sind geordnet und indiziert.
Sets repräsentieren (mathematische) Mengen, die keine Duplikate zulassen. Eine Map ist eine
Sammlung von Paaren. Jedes Paar besteht aus einem eindeutigen Schlüssel und einem zugehörigen
Objekt (Wert).
85
Maps sind vergleichbar mit assoziativen dynamischen Arrays in anderen Programmiersprachen und
werden mithilfe von Hash-Strukturen implementiert. Es gibt sortierte und unsortierte bzw. geordnete
und nicht geordnete Sets und Maps. Mengen- und Map-Elemente sind nicht indiziert.
15.1 Listen
Eine Liste im Collection-Framework ist vergleichbar mit einem dynamisch wachsenden Array, das
folgende Eigenschaften aufweist:
1. Eine Liste ist geordnet (sie speichert ihre Elemente in der Reihenfolge, in der sie eingefügt
wurden).
2. Jedes Element hat einen Index. Die Indizierung fängt, wie bei Arrays, bei 0 an.
3. Indexbasierter Zugriff auf Listenelemente ist möglich. Das Einfügen und Entfernen von
Elementen an beliebiger Stelle in Listen ist möglich
4. Listen können null-Objekte und Duplikate speichern. Ein und dasselbe Element kann
mehrfach in einer Liste vorkommen.
5. Die Länge einer Liste ist theoretisch unbeschränkt. Listen können dynamisch wachsen.
6. Listen können, wie alle anderen Collections, keine primitiven Elemente aufnehmen, sondern
nur Objektreferenzen.
Die wichtigsten Listen, die das Collection-Framework implementiert, sind ArrayList, Vector, Stack,
und LinkedList. Sie alle sind Klassen, die das List-Interface implementieren:
//List-Interface
interface List<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
}
86
List
enthält Methoden u.a. zum Suchen, Einfügen und Entfernen von Objekten.
boolean add(Object obj):
Fügt das Objekt obj am Ende der
Liste ein. Die Methode gibt true zurück, wenn das Element
erfolgreich in die Liste eingefügt wurde.
void add(int index, Object obj):
Fügt das Objekt an der Stelle index in die Liste ein. Es ist hier zu beachten, dass das Element,
welches sich vorher an der Stelle index befand und alle Elemente rechts von ihm, eine Stelle nach
hinten geschoben werden.
Object remove(int index):
Löscht das Objekt an der Stelle index und gibt es zurück.
Object set(int index, Object obj):
Ersetzt das Objekt an der Stelle index durch das Objekt obj. Das alte Objekt wird zurückgegeben.
Object get(int index):
Gibt das Objekt an der Stelle index zurück.
int size():
Gibt die Anzahl der Objekte zurück, die in der Liste gespeichert sind.
boolean contains(Object obj):
Gibt true zurück, wenn obj mindestens
einmal in der Liste enthalten ist.
int indexOf(Object obj):
Gibt die Indexnummer des ersten Vorkommens von obj zurück. Wenn obj nicht in der Liste
vorkommt, wird -1 zurückgegeben.
int lastIndexOf(Object obj):
Gibt die Indexnummer des letzten Vorkommens von obj zurück. Sollte das Element nicht
vorhanden sein, wird -1 zurückgegeben.
void clear():
Löscht alle Elemente in der Liste.
87
15.1.1 ArrayList und Vector
und java.lang.Vector sind sehr ähnliche Datenstrukturen, die das ListInterface implementieren. Sie unterscheiden sich jedoch darin, dass die Methoden von Vector
synchronisiert sind, so dass mehrere Threads auf einen Vector gleichzeitig zugreifen können, ohne die
Integrität der Daten zu verletzen. Vektoren sind daher Thread-sicher (thread-safe). Aufgrund der
Synchronisation sind Vektoren langsamer als ArrayLists und sollten daher nur verwendet werden,
wenn mehrere Threads auf den Vektor konkurrierend zugreifen sollen.
java.lang.ArrayList
Bemerkung: Vectors sind keine Vektoren im mathematischen Sinne, sondern Listen. Der Begriff
Vektor wird in der Programmierung auch oft für ein eindimensionales Array verwendet.
Beispiel:
/*
* Package: collections
* Class:
ArrayListSample.java
* Method:
*/
public class ArrayListSample {
ArrayList physicalUnits = new ArrayList();
public ArrayListSample(){
physicalUnits.add("Joule");
physicalUnits.add("Pascal");
physicalUnits.add("Volt");
physicalUnits.add("Coulomb");
physicalUnits.add(null);//null-Objekte sind erlaubt
physicalUnits.add("Volt");
physicalUnits.add("Ohm");
physicalUnits.add("Newton");
physicalUnits.add(1,"Celsius");
}
public static void main(String ... args) {
ArrayListSample arrayListSample=new ArrayListSample();
System.out.print("Physical units: ");
for (Object unit : arrayListSample.physicalUnits)
System.out.print(unit+" ");
System.out.println();
System.out.println("physicalUnits.size(): "+
arrayListSample.physicalUnits.size());
System.out.println("physicalUnits.isEmpty(): "+
arrayListSample.physicalUnits.isEmpty());
System.out.println("physicalUnits.contains(\"celsius\"): "+
arrayListSample.physicalUnits.contains("celsius"));
System.out.println("physicalUnits.remove(\"Coulomb\"): "+
arrayListSample.physicalUnits.remove("Coulomb"));
System.out.println("physicalUnits.remove(2):
"+arrayListSample.physicalUnits.remove(2));
System.out.println("physicalUnits.size()-2): "+
88
arrayListSample.physicalUnits.get(
arrayListSample.physicalUnits.size()-2));
System.out.println("physicalUnits.lastIndexOf(\"Pascal\"): "+
arrayListSample.physicalUnits.lastIndexOf("Pascal"));
}
}
Ausgabe:
Physical units: Joule Celsius Pascal Volt Coulomb null Volt Ohm Newton
physicalUnits.size(): 9
physicalUnits.isEmpty(): false
physicalUnits.contains("celsius"): false
physicalUnits.remove("Coulomb"): true
physicalUnits.remove(2): Pascal
physicalUnits.size()-2): Ohm
physicalUnits.lastIndexOf("Pascal"): -1
15.1.2 LinkedList
Eine LinkedList ist eine doppelt verkettete Liste, die neben List das deque-Interface implementiert:
//Das Deque-Interface
public interface Deque<E> extends Queue<E> {
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
void push(E e);
E pop();
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
89
boolean add(E e)
und boolean offer(E e):Fügen das Element e am Ende der Liste ein.
E remove() und E poll():Entfernen das erste Element in der Liste und geben es zurück.
E element() und E peek():Liefern das erste Element in der Liste, ohne es zu entfernen.
LinkedList hat den Vorteil gegenüber ArrayList und Vector, dass das Einfügen und Entfernen in
der Mitte der Liste sehr schnell erfolgt. Die Suche nach einem Element ist dagegen langsam.
Beispiel unter: http://www.roseindia.net/java/jdk6/LinkedListExample.shtml
15.2 Mengen (Sets)
Eine Menge ist eine in der Regel ungeordnete Sammlung von Elementen, die keine Duplikate zulässt.
Bevor ein Element in eine Menge eingefügt werden kann, wird zunächst überprüft, ob es bereits darin
enthalten ist. Erst wenn sichergestellt ist, dass es nicht vorhanden ist, wird es in die Menge eingefügt.
Im Gegensatz zu Listen sind die Elemente einer Menge nicht geordnet. Mit anderen Worten die
Elemente stehen nicht in der gleichen Reihenfolge, in der sie eingefügt wurden. LinkedHashSets
stellen die einzige Ausnahme dar. Im Gegensatz zu Listen ist bei Mengen ein indexbasierter Zugriff
auf die Elemente nicht möglich. Es gibt sortierte und unsortierte Mengen.
Die wichtigsten Mengen-Collections in Java sind TreeSet, HashSet und LinkedHashSet. Ein
TreeSet ist sortiert und lässt keine null-Objekte zu. HashSet ist weder sortiert noch geordnet, lässt
null-Objekte zu. LinkedHashSet ist geordnet, nicht sortiert und lässt ebenfalls null-Objekte zu.
TreeSet, HashSet und LinkedHashSet implementieren das Set-Inteface. Das Set-Interface wird von
Collection abgeleitet, erweitert es jedoch um keine neuen Methoden.
15.2.1 HashSet
Ein HashSet speichert die Elemente in einer Hashtabelle und lässt das Einfügen von null-Objekten,
jedoch nicht von Duplikaten zu. HashSet unterstützt das Finden, Einfügen und Entfernen von
Elementen in konstanter Zeit. Die Elemente eines HashSet sind nicht geordnet. Ein HashSet verlässt
sich auf die Implementierung der hashCode()-Methode ihrer Objekte. Dies ist die Methode, die
HashSet bei der Suche nach Duplikaten aufruft. Damit HashSet richtig funktioniert, sollten ihre
verwalteten Objekte eine überschriebene Implementierung der hashCode()-Methode besitzen. Dies
gilt für alle anderen Collections, die hashbasiert sind wie z.B. LinkedHashSet, HashMap, HashTable
und TreeMap.
/*
* Package: collections
* Class:
HashSetSample.java
* Method:
*/
public class HashSetSample {
Set hashSet=new HashSet();
public HashSetSample() {
for(int i=5;i>=0;i--)
hashSet.add(i*i);
90
hashSet.add(3.5f);
hashSet.add(null);//null-Objekte sind erlaubt
}
public static void main(String ... args) {
HashSetSample hashSetSample=new HashSetSample();
System.out.println(hashSetSample.hashSet);
}
}
Ausgabe:
[null, 0, 1, 16, 3.5, 4, 25, 9]
15.2.2 LinkedHashSet
Eine java.util.LinkedHashSet hat die gleichen Eigenschaften wie ein HashSet. Sie unterscheidet
sich davon lediglich dadurch, dass sie die Elemente in der Reihenfolge speichert, in der sie eingefügt
wurden. LinkedHashSets sind als doppelt verkettete Listen implementiert und können null-Objekte
aufnehmen.
15.2.3 TreeSet
Ein java.util.TreeSet speichert seine Elemente in einem Rot-Schwarz-Baum (ein effizienter
binärer Suchbaum) und lässt weder Duplikate noch null-Objekte zu. TreeSets unterstützen das
Einfügen, Finden und Entfernen von Elementen in logarithmischer Zeit. TreeSet implementiert das
Interface java.util.SortedSet. SortedSet erweitert Set um zahlreiche Methoden für den Umgang
mit sortierten Daten:
interface SortedSet<E> extends Set<E> {
Comparator<?
SortedSet<E>
SortedSet<E>
SortedSet<E>
E first();
E last();
super E> comparator();
subSet(E fromElement, E toElement);
headSet(E toElement);
tailSet(E fromElement);
}
E first()
bzw. E last():Liefert das größte bzw. kleinste Element der Menge zurück.
SortedSet<E> subSet(E fromElement, E toElement):
Liefert alle Elemente im halboffenen Intervall [fromElement, toElement[
SortedSet<E> headSet(E toElement):
Liefert alle Elemente kleiner gleich toElement
zurück.
zurück.
91
/*
* Package: collections
* Class:
TreeSetSample.java
* Method:
*/
public class TreeSetSample {
TreeSet<Integer> sortedSet = new TreeSet<Integer>();
public TreeSetSample() {
sortedSet.add(new Integer(2));
sortedSet.add(new Integer(7));
sortedSet.add(new Integer(1));
//sortedSet.add(null);//null-Objekte sind nicht erlaubt
sortedSet.add(new Integer(-1));
sortedSet.add(new Integer(0));
sortedSet.add(new Integer(-1));
sortedSet.add(new Integer(5));
sortedSet.add(new Integer(5));
}
public static void main(String... args) {
TreeSetSample treeSetSample = new TreeSetSample();
System.out.print("sortedSet: ");
for (Integer element : treeSetSample.sortedSet)
System.out.print(element + " ");
System.out.println();
System.out.println("sortedSet.first(): "+
treeSetSample.sortedSet.first());
System.out.println("sortedSet.last(): "+
treeSetSample.sortedSet.last());
System.out.println("sortedSet.subSet(-1, 5): "+
treeSetSample.sortedSet.subSet(-1, 5));
System.out.println("sortedSet.headSet(2): "+
treeSetSample.sortedSet.headSet(2));
System.out.println("sortedSet.tailSet(2): "+
treeSetSample.sortedSet.tailSet(2));
SortedSet<E> tailSet(E fromElement):
Liefert alle Elemente größer gleich fromElement
zurück.
verlässt sich bei der Suche nach Duplikaten auf die
Implementierung der equals()-Methode ihrer Objekte. Damit TreeSet richtig
funktionieren kann, sollten ihre Elemente die equals()-Methode der Klasse
Object oder das Comparable-Interface implementieren (siehe
TreeSet
10.1 Das Interface Comparable), denn eine Entscheidung, ob ein Element e1 größer oder kleiner als ein
Element e2 ist, wird durch Aufruf der Methode equals(), also e1.equals(e2), getroffen.
92
15.3 Tabellen (Maps)
Eine Map ist eine Datenstruktur, die Schlüssel/Objekt-Paare speichert. Es gibt sortierte und unsortierte,
geordnete und ungeordnete Maps. Die Schlüssel einer Map müssen eindeutig sein und sollten die
Methode hashCode() implementieren. Dies ist die Methode, die von einer Map aufgerufen wird, wenn
zwei Schlüssel auf Gleichheit überprüft werden sollen.
Die wichtigsten Maps sind HashMap, HashTable, TreeMap und LinkedHashMap. Sie alle
implementieren das Map-Interface entweder direkt oder indirekt.
public interface Map<K,V> {
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void putAll(Map<? extends K, ? extends V> m);
void clear();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
}
boolean equals(Object o);
int hashCode();
}
Die wichtigsten Methoden des Map-Interfaces:
V put(K key, V value):
Fügt den Schlüssel key und seinen Wert in die Tabelle ein. Falls ein Schlüssel bereits vorhanden ist,
wird sein alter Wert durch den neuen Wert ersetzt. Die Methode gibt dann den alten Wert des
Schlüssels zurück. Wenn der Schlüssel nicht vorhanden ist, gibt die Methode null zurück.
boolean containsKey(Object key):
Gibt true zurück, wenn der Schlüssel
in der Tabelle existiert.
V get(Object key):
Gibt das dem Schlüssel zugeordnete Objekt zurück, oder null, falls der Schlüssel nicht in der
Tabelle enthalten ist.
Set<K> keySet():
Gibt alle Schlüssel der Tabelle als Menge zurück.
93
Collection<V> values():
Gibt eine Collection der Objekte in der Map zurück. In der Collection können Duplikate enthalten
sein.
94
15.3.1 HashMap und Hashtable
und java.util.Hashtable implementieren das Map-Interface auf der Basis
einer Hashtabelle. Ihre Arbeitsweisen sind identisch. Die Methoden von HashTable sind allerdings
synchronisiert und daher Thread-sicher.
HashMap und Hashtable lassen null-Schlüssel und null-Objekte zu. Ihre Elemente sind nicht
geordnet.
java.util.HashMap
15.3.2 TreeMap
Eine java.lang.TreeMap unterscheidet sich von einer HashMap, dadurch, dass sie ihre Elemente
sortiert speichert. Sie implementiert das SortedMap-Interface, das analog zum Interface SortedSet
aufgebaut ist. TreeMaps lassen keine null-Schlüssel zu.
Ein Schlüssel kann jedoch mit einem null-Objekt assoziiert sein.
/*
* Package: collections
* Class:
TreeMapSample.java
* Method:
*/
public class TreeMapSample {
Map<String, String> treeMap=new TreeMap<String, String>();
public TreeMapSample(){
treeMap.put("Energy", "Joule");
treeMap.put("Work", "Joule");
treeMap.put("Pressure", "Pascal");
treeMap.put("Voltage", "Volt");
treeMap.put("Frequency", "Hertz");
treeMap.put("Resistance", "Ohm");
//treeMap.put(null, "Unkown"); null-Schlüssel sind nicht erlaubt
treeMap.put("Power", "Watt");
treeMap.put("Temperature", "Celsius");
treeMap.put("Conductance", "Siemens");
treeMap.put("Unknown", null);//null-Werte sind erlaubt
}
public static void main(String[] args) {
System.out.println("Sorted Map:");
TreeMapSample treeMapSample = new TreeMapSample();
for (String key : treeMapSample.treeMap.keySet())
System.out.println(key + ": "+treeMapSample.treeMap.get(key));
}}
Ausgabe:
Sorted Map:
Conductance: Siemens
Energy: Joule
Frequency: Hertz
Power: Watt
Pressure: Pascal
Resistance: Ohm
Temperature: Celsius
Unknown: null
Voltage: Volt
Work: Joule
95
15.4.3 LinkedHashMap
Im Gegensatz zu HashMap und Hashtable, merkt sich eine java.util.LinkedHashMap die
Reihenfolge, in der die Schlüssel/Wert-Paare eingefügt werden.
/*
* Package: collections
* Class:
MapSample.java
* Method:
*/
public class MapSample {
public java.util.Map<Integer, Integer> linkedMap=
new LinkedHashMap<Integer, Integer>();
public java.util.Map<Integer, Integer> treeMap=
new TreeMap<Integer, Integer>();
public java.util.Map<String, Integer> hashMap=
new HashMap<String, Integer>();
public MapSample() {
int key=11;
while (key-->0) {
linkedMap.put(key, key*key);
treeMap.put(key, key*key);
hashMap.put(key+"", key*key);
}
}
public static void main(String ... args) {
MapSample mapSample=new MapSample();
System.out.print("Ordered Map:\t ");
Iterator<Integer> it=mapSample.linkedMap.keySet().iterator();
while (it.hasNext())
System.out.print(mapSample.linkedMap.get(it.next())+" ");
System.out.println();
System.out.print("Sorted Map:\t ");
it=mapSample.treeMap.keySet().iterator();
while (it.hasNext())
System.out.print(mapSample.treeMap.get(it.next())+" ");
System.out.println();
System.out.print("Unordered Map:\t ");
Iterator<String> it2=mapSample.hashMap.keySet().iterator();
while (it2.hasNext())
System.out.print(mapSample.hashMap.get(it2.next())+" ");
}
}
Ausgabe:
Ordered Map:
100 81 64 49 36 25 16 9 4 1 0
Sorted Map:
0 1 4 9 16 25 36 49 64 81 100
Unordered Map: 9 4 1 100 0 49 36 25 16 81 64
96
TreeSet
LinkedHash- Hash
-Map
Set
Hashtable
TreeMap
LinkedHash-Map
Collection
ArrayList
Geordnet
ja
ja
ja
nein
nein
ja
nein
nein
nein
ja
Sortiert
nullSchlüssel
nein
nein
nein
nein
ja
nein
nein
nein
ja
nein
Ja
Ja
nein
Ja
null-Werte
ja
ja
ja
ja
nein
ja
ja
ja
ja
ja
Indexzugriff
ja
ja
ja
nein
nein
nein
nein
nein
nein
nein
nein
nein
nein
ja
nein
nein
nein
nein
nein
nein
nein
ja
nein
ja
ja
ja
ja
ja
ja
nein
nein
nein
nein
nein
ja
nein
nein
Implementier
-ung von
equals()
erforderlich
nein
Implementier
ung von
hashCode()
erforderlich
nein
Synchronisiert
nein
Vector
Linked- Hash
List
-Set
Tabelle 8: Collections
15.4 Generische Collections
Vor JDK 1.5 waren alle Collection-Klassen nicht generisch. Mit der add-Methode einer nicht
generischen Liste könnte man z.B. Objekte unterschiedlicher Klassen in dieselbe Datenstruktur
einfügen:
List list=new ArrayList();
list.add(new Integer(5));
list.add("String-Object");
list.add(new Float(2.3f) );
Integer i=(Integer)list.get(0);//Casting erforderlich
String s=(String)list.get(1);//Casting erforderlich
Float f=(Float)list.get(2);//Casting erforderlich
Collections waren daher nicht homogen. Der Zugriff auf ein Element erforderte explizites Casting. Das
Casting in einen falschen Datentyp führt bekanntermaßen zu einer java.lang.ClassCastException.
Dies machte Programme fehleranfällig.
Seit Version 5 sind alle Collection-Klassen und -Interfaces generisch und somit homogen. Zugriffe
auf Elemente erfordern kein Casting mehr:
List<Integer> list2=new ArrayList<Integer>();
list2.add(new Integer(5));
list2.add(3);
list2.add(new Integer(2) );
Integer ii= list2.get(0);//Casting ist nicht erforderlich
Integer ss= list2.get(1);//Casting ist nicht erforderlich
Integer ff= list2.get(2);//Casting ist nicht erforderlich
97
15.5 Iteratoren (Iterator)
Ein Iterator ist ein Objekt, das das java.util.Iterator-Interface implementiert. Iteratoren bieten
einen abstrakten Mechanismus zur Traversierung von Collections. Das Iterator-Interface enthält nur
drei Methoden:
//Iterator-Interface
interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
Ein Iterator hat einen Zeiger. Wenn ein Iterator initialisiert wird, zeigt dieser Zeiger auf das erste
Element in der Sammlung.
E next():
liefert das Objekt zurück, auf das der Iterator-Zeiger zeigt und bewegt anschließend den Zeiger zum
nächsten Element. Wenn der Zeiger beim letzten Element angelangt ist, liefern nachfolgende
next()-Aufrufe null zurück. Der Iterator kann dann nicht mehr verwendet werden.
boolean hasNext():liefert
genau dann true zurück, wenn die Sammlung nicht leer ist und der
Zeiger nicht das Ende der Liste erreicht hat.
void remove():ruft next()
auf und entfernt ggf. das Element, das next() zurück liefert.
Das Collection-Interface verfügt über die Methode Iterator<E> iterator(), die einen Iterator für
die Collection zurück liefert. Der Iterator liefert die Elemente seiner Collection in der Reihenfolge
zurück, in der sie gespeichert sind.
98
/*
* Package: collections
* Class:
IteratorSample.java
* Method:
*/
public class
public
public
public
Integer>();
IteratorSample {
List<Integer> list = new ArrayList<Integer>();
Set<Integer> set = new HashSet<Integer>();
Map<Integer, Integer> map = new HashMap<Integer,
public IteratorSample(){
int ii=0;
for(int i=0; i<11; i++){
ii=i*i;
list.add(ii);
set.add(ii);
map.put(i, ii);
}
}
public static void main(String[] args) {
IteratorSample is=new IteratorSample();
Iterator<Integer> it=is.list.iterator();
System.out.print("List: ");
while(it.hasNext())
System.out.print(it.next()+" ");
System.out.println();
it=is.set.iterator();
System.out.print("Set: ");
while(it.hasNext())
System.out.print(it.next()+" ");
System.out.println();
it=is.map.keySet().iterator();
System.out.print("Map: ");
while(it.hasNext()){
int key=it.next();
System.out.print("("+key+","+is.map.get(key)+") ");
}
}
}
99
16 I/O
16.1 Dateien und Verzeichnisse
Dateien und Verzeichnisse eines beliebigen Dateisystems werden in Java durch die Klasse java.io.File
abstrahiert. Ein Objekt der Klasse File repräsentiert den Namen einer Datei bzw. eines Verzeichnisses,
die (das) physikalisch gar nicht existieren muss.
File bietet zahlreiche Methoden zur Navigation des lokalen Dateisystems und zur Manipulation der
sich dort befindlichen Dateien und Verzeichnisse.
Die Klasse verfügt u. a. über den folgenden Konstruktor:
File(String pathName):
Er erzeugt ein Objekt, das lediglich eine abstrakte Repräsentation der Datei oder des Verzeichnisses
kapselt. Es ist an dieser Stelle zu betonen, dass das Instanziieren eines File-Objektes weder das
Erstellen einer Datei oder eines Verzeichnisses bedeutet noch deren Existenz nicht voraussetzt. Es
handelt sich lediglich um eine abstrakte Beschreibung einer Datei oder eines Verzeichnisses. So ist es
zum Beispiel möglich ein File-Objekt zu erstellen, das einen syntaktisch falschen Dateinamen enthält.
pathName gibt den Pfad Datei oder des Verzeichnisses. Unix bzw. Windows verwendet einen
Schrägstrich bzw. Backslash zur Trennung von Verzeichnissen in einer Verzeichnisstruktur.
Beispiele:
File f=new File("c:\\daten");//absoluter Pfad eines Ordners
File f2=new File("c:\\daten\\Veranstaltung.java");//absoluter Pfad einer
Datei
File f3=new File(".\\daten\\Veranstaltung.java");//relativer Pfad einer
Datei
File f4=new File(".././beispiele");//relativer Pfad eines Ordners
public boolean mkdir(): Legt ein Verzeichnis an.
public boolean delete():
public boolean renameTo(File dest):
public String [] list():
Die Klasse File verfügt u. a. über folgende Methode:
public String getName(): Liefert den Namen der Datei oder des Verzeichnisses.
public String getAbsolutPath(): Liefert den absoluten Pfad der Datei oder des
Verzeichnisses.
public boolean exist(): Testet, ob die Datei oder das Verzeichnis existiert.
public long lastModified():
Liefert den Zeitpunkt der letzten Änderung.
public boolean length():
Liefert die Länge der Datei oder des Verzeichnisses.
public boolean istFile():
Testet, ob das File-Objekt eine Datei repräsentiert.
public boolean istDirectory():
Testet, ob das File-Objekt ein Verzeichnis
100
repräsentiert.
public boolean mkdir():
public boolean delete():
Legt ein Verzeichnis an.
Löscht die Datei bzw. das Verzeichnis
public boolean renameTo(File dest):
Die Datei bzw. das Verzeichnis wird in das als
Parameter übergebene Objekt umbenannt.
Liefert ein Array von String, das für jeden gefundenen
Verzeichniseintrag ein Element enthält.
public String [] list():
/*
* Package: io
* Class:
SimpleFileApplication.java
* Method: main()
*/
package io;
import java.io.File;
public class SimpleFileApplication {
private static File f = new File("SimpleFileApplication.java");// relativer
Pfad einer Datei
private static File d = new File("./");// relativer Pfad eines Ordners
public static void main(String[] args) {
System.out.println("Die Datei "+f.getName()+" befindet sich im Ordner: "+
d.getAbsolutePath());
System.out.println(f.getName());
System.out.println(f.getPath());
System.out.println(f.getAbsolutePath());
System.out.println(f.getParent());
System.out.println(f.exists());
System.out.println(f.canWrite());
System.out.println(f.canRead());
System.out.println(f.isFile());
System.out.println(f.isDirectory());
System.out.println(f.lastModified());
System.out.println(f.length());
System.out.println(d.getName());
System.out.println(d.getPath());
System.out.println(d.getAbsolutePath());
System.out.println(d.getParent());
System.out.println(d.exists());
System.out.println(d.canWrite());
System.out.println(d.canRead());
System.out.println(d.isFile());
System.out.println(d.isDirectory());
System.out.println(d.lastModified());
101
System.out.println(d.length());
}
}
Ausgabe:
Die Datei SimpleFileApplication.java befindet sich im Ordner:
U:\Daten\EclipseWorkspace07.09.05\Java-Vorkurs\.
SimpleFileApplication.java
SimpleFileApplication.java
U:\Daten\EclipseWorkspace07.09.05\Java-Vorkurs\SimpleFileApplication.java
null
false
false
false
false
false
0
0
.
.
U:\Daten\EclipseWorkspace07.09.05\Java-Vorkurs\.
null
true
true
true
false
true
1191992321885
512
Das folgende Programm listet alle im angegebenen Verzeichnis enthaltenen Dateien und Verzeichnisse
rekursiv auf und gibt sie aus.
/*
* Package: io
* Class:
FileLister.java
* Method:
*/
package io;
import java.io.File;
/**
*
* Listet alle im angegebenen Verzeichnis enthaltenen Dateien und
* Verzeichnisse rekursiv auf
*
*/
public class FileLister {
public void listRec(File directory, int depth) {
String[] list = directory.list();
for (String fileName : list) {
for (int i = 0; i < depth; i++)
System.out.print("
");
System.out.println(fileName);
File child = new File(directory, fileName);
102
if (child.isDirectory())
listRec(child, depth + 1);
}
}
// Das Programm erwartet den Namen eines existierenden Verzeichnisses.
public static void main(String[] args) {
// Wenn das Programm nicht den Namen eines Verzeichnisses als Parameter
übergeben bekommt, listet es den Inhalt des aktuellen Verzeichnisses.
String path = "./";// Das aktuelle Verzeichnis.
if (args.length > 0)
path = args[0];
File f = new File(path);
if (!f.isDirectory()) {
System.out.println(path + " doesn't exist or not a dir.");
System.exit(0);// Das Programm wird beendet.
}
FileLister fl = new FileLister();
fl.listRec(new File(path), 0);
}
}
16.2 Wahlfreier Zugriff auf Dateien (RandomAccessFile)
Die Klasse java.io.RandomAccessFile bietet einen Mechanismus zum wahlfreien Zugriff auf Dateien.
Objekte der Klasse RandomAccessFile sehen eine Datei als ein Array von Bytes, auf das an jeder
beliebigen Stelle zugegriffen werden kann. So können an jeder Position der Datei Daten gelesen oder
geschrieben werden.
Die Klasse verfügt u. a. über den folgenden Konstruktor:
RandomAccessFile(String file, String mode)
Der mode-String muss “r”, “rw” sein und gibt an, ob die Datei zum Lesen “r” oder zum Lesen und
Schreiben “rw” geöffnet werden soll.
Ein Objekt der Klasse verfügt über einen Positionierungszeiger, der zu jedem Zeitpunkt auf eine
Position innerhalb der Datei zeigt. Mithilfe dieses Zeigers kann innerhalb der Datei navigiert werden.
Die Methode long getFilePointer() liefert die aktuelle Position des Zeigers innerhalb der Datei. void
seek(long position) bewegt den Zeiger zur angegeben Position.
Im Gegensatz zu seek() positioniert int skipBytes( int n ) relativ zur aktuellen Position. n ist
die Anzahl, um die der Dateizeiger bewegt wird. Ist n negativ, werden keine Bytes übersprungen.
int read() liefert das Byte, auf das der Zeiger gerade zeigt, als int-Wert zurück.
int read(byte dest[]) versucht so viele Bytes zu lesen, so dass das Array gefüllt wird. Sie liefert die
Anzahl der gelesenen Bytes bzw. -1, wenn der Zeiger auf das Ende der Datei zeigt.
int read(byte dest[], int offset,int len) versucht len Bytes ab Position offset zu lesen und sie in dest zu
schreiben.
Alle Methoden erhöhen die Zeigerposition um die Anzahl der gelesenen Bytes.
103
Die folgenden Schreibmethoden funktionieren analog zu ihren korrespondierenden Lesemethoden,
geben jedoch keine Rückgabewerte zurück:
void read(int b), void write(byte dest[]), void write(byte dest[], int offset, int len).
Die folgenden Methoden lesen jeweils ein Element des angegebenen Typs und erwarten, dass es in der
Datei in dem durch die korrespondierende write-Methode vorgegebenen binären Format vorliegt:
public
public
public
public
public
public
public
public
public
public
public
public
public
public
final
final
final
final
final
final
final
final
final
final
final
final
final
final
boolean readBoolean()
byte readByte()
char readChar()
double readDouble()
float readFloat()
int readInt()
long readLong()
short readShort()
String readUTF()
void readFully(byte[] b)
void readFully(byte[] b, int off, int len)
String readLine()
int readUnsignedByte()
int readUnsignedShort()
readUnsignedByte() Liest ein als vorzeichenlos interpretiertes Byte.
eadUnsignedShort() Liest zwei als vorzeichenlos interpretierte Bytes.
readFully(byte[] b) Versucht, den gesamten Puffer b zu füllen.
void close() Schließt eine geöffnete Datei wieder.
Alle oben genannten Methoden werfen eine IOException.
Beispiel:
/*
* Package: io
* Class:
RandomAccessFileApplication.java
* Method:
*/
package io;
import
import
import
import
java.io.File;
java.io.IOException;
java.io.RandomAccessFile;
java.util.Calendar;
/**
* Das Programm öffnet eine Datei und fügt eine Zeichenkette an das Ende der
* Datei
*
*/
public class RandomAccessFileApplication {
public void writeMySignature(String filename) {
try {
File file = new File(filename);
RandomAccessFile randAccessFile = new
RandomAccessFile(file, "rw");
// Seek to end of file
104
randAccessFile.seek(file.length());
// Append to the end
randAccessFile.writeBytes("\r\n/*Signed by: "
+ System.getProperty("user.name") + " at "
+ Calendar.getInstance().getTime().toString() +
"*/");
randAccessFile.close();
} catch (IOException e) {
System.out.println("Can't write ");
}
}
public static void main(String[] args) {
RandomAccessFileApplication randAccessFileApp = new
RandomAccessFileApplication();
randAccessFileApp
.writeMySignature("src/io/RandomAccessFileApplication.java");
}
}
16.3 Streams
Streams (Ströme) in Java sind Klassen, mit denen Dateien sequentiell gelesen und beschrieben werden
können. Während ein RandomAccessFile-Objekt eine Datei als ein indiziertes Array von Bytes sieht,
ist eine Datei für einen Stream nichts anderes als eine geordnete Folge von Bytes bzw. Zeichen, auf die
er nur sequenziell zugreifen kann. Streams können nicht, wie RandomAccessFile, auf eine Datei
wahlfrei über einen Index zugreifen.
Ein Input-Stream (Eingabestrom) ist ein Objekt, das Bytes aus einer Datei liest und sie zurückgibt. Ein
Output-Stream (Ausgabestrom) ist ein Objekt, mit dem Bytes in eine Datei sequentiell geschrieben
werden können.
Somit gibt es zwei Arten von Streams. Stream-Writer (Ausgabeströme). Beide kann man wiederum in
ByteStreams und Character-Streams unterteilen.
Während ByteStreams Bytes lesen und schreiben können, verwenden Character-Streams andere
ByteStreams zum Lesen und Schreiben von Zeichen in Dateien
16.3.1 ByteStreams
ByteStreams werden auch Low-Level-Streams genannt, weil sie direkt aus Dateien lesen.
Character-Streams dagegen können nicht direkt aus der Dateien lesen oder in sie schreiben.
Character-Streams verwenden ByteStreams um schreiben oder lesen zu können.
105
Die wichtigsten Low-Level-Streams sind FileInputStream und FileOutputStream.
Sie verfügen u a. über folgende Konstruktoren. FileInputStream (String pathname), FileInputStream
(File file), FileOutputStream (String pathname), FileOutputStream (File file)
Objekte dieser Klassen stellen zahlreiche Methoden zum Lesen und Schreiben von Dateien.
Beispiel:
/*
* Package: io
* Class:
FileCopy.java
* Method:
*/
/**
* Das Programm kopiert den Inhalt seiner Quelldatei in eine andere Datei
*
*/
import java.io.*;
public class FileCopy {
public static void main(String[] args)
{
try {
//Quell-Datei (path muss evtl. angepasst werden)
FileInputStream in = new
FileInputStream("src/io/FileCopy.java");
//Ziel-Datei (path muss evtl. angepasst werden)
FileOutputStream out = new
FileOutputStream("src/io/CopyOfFileCopy.java");
int len;
while ((len = in.read()) > 0) {
out.write( len);
}
out.close();
in.close();
} catch (IOException e) {
System.err.println(e.toString());
}
}
}
106
16.3.2 FileReader
FileReader:
Ein FileReader-Objekt liest eine Datei zeichenweise:
/*
* Package: io
* Class:
CharacterFileReader.java
* Method:
*/
public class CharacterFileReader
{
public static void main(String[] args)
{
FileReader f;
int i;
try {
//Pfad muss evtl. angepasst werden
f = new FileReader("src/io/Studenten.csv");
while ((i = f.read()) != -1) {
char c=(char) i;
if ( c==';')
c=' ';
System.out.print(c);
}
f.close();
} catch (IOException e) {
System.out.println("Error while reading in the file");
}
}
}
16.3.3 BufferedReader
Dieser Filter dient zur Pufferung von Eingaben und kann verwendet werden, um die Performance beim
Lesen von externen Dateien zu erhöhen. Da nicht jedes Byte einzeln gelesen wird, verringert sich die
Anzahl der Zugriffe auf den externen Datenträger, und die Lesegeschwindigkeit erhöht sich. Zusätzlich
stellt BufferedReader die Methode readLine zur Verfügung, die eine komplette Textzeile liest und als
String an den Aufrufer zurückgibt:
/*
* Package: io
* Class:
LineCounter.java
* Method:
*/
package io;
import java.io.File;
import java.io.IOException;
107
public class LineCounter {
public int countLines(String file){
int lines=0;
try{
java.io.BufferedReader bufferedReader = new
java.io.BufferedReader(new java.io.FileReader( file));
String line="";
while((line = bufferedReader.readLine()) != null)
{
lines++;
}
File f=new File(file);
System.out.println("Die Datei "+f.getAbsolutePath()+" enthält
"+lines+" Zeilen!");
}
catch (IOException e) {
System.out.println("Can't read File");
}
return lines;
}
public static void main(String[] args)
{
//Pfad muss evtl. angepasst werden
LineCounter lineCounter=new LineCounter();
lineCounter.countLines("src/io/LineCounter.java");
}
}
Character-Streams:
16.3.4 FileWriter
:
Ein FileWriter-Objket schreibt eine Zeichenfolge in eine Datei.
Beispiel:
/*
* Package: io
* Class:
CharacterFileWriter.java
* Method:
*/
package io;
import java.io.FileWriter;
import java.io.IOException;
/**
* Das Programm erstellt eine CSV-Datei und fügt Studentendaten in sie ein
*
*
108
*/
public class CharacterFileWriter
{
public static void main(String[] args)
{
try {
String NEW_LINE="\r\n";
String firstLine = "Vorname;Nachname;MatNr.;Semesteranzahl"+NEW_LINE;
String student1 = "Markus1;Mustermann1;346572;5"+NEW_LINE;
String student2 = "Markus2;Mustermann2;23452132;3"+NEW_LINE;
String student3 = "Markus3;Mustermann3;2209365;4"+NEW_LINE;
FileWriter f1;
//Pfad muss evtl. angepasst werden
f1 = new FileWriter("src/io/Studenten.csv");
f1.write(firstLine);
f1.write(student1);
f1.write(student2);
f1.write(student3);
f1.close();
} catch (IOException e) {
System.out.println("Error while writing in the file");
}
}
}
}
16.3.5 BufferedWriter
Diese Klasse hat die Aufgabe, Stream-Ausgaben zu puffern. Dazu enthält sie einen internen Puffer, in
dem die Ausgaben von write zwischengespeichert werden. Erst wenn der Puffer voll ist oder die
Methode flush aufgerufen wird, werden alle gepufferten Ausgaben in den echten Stream geschrieben.
Das Puffern der Ausgabe ist immer dann nützlich, wenn die Ausgabe in eine Datei geschrieben werden
soll. Durch die Verringerung der write-Aufrufe reduziert sich die Anzahl der Zugriffe auf das externe
Gerät, und die Performance wird erhöht.
/*
* Package: io
* Class:
CharacterFileWriter.java
* Method:
*/
109
package io;
/**
* Das Programm fügt 15 Zeilen in eine Datei mittels eines BufferedReaders
ein.
*
*/
import java.io.BufferedWriter;
import java.io.FileWriter;
public class BufferedWriterApp {
public static void main(String args[]) {
if(!(args.length>0)){
System.out.println("Das Programm erwartet einen
Dateinamen als Eingabeparameter");
System.exit(0);
}
try {
int LINES=15;
FileWriter fw = new FileWriter(args[0]);
BufferedWriter bw = new BufferedWriter(fw);
for(int i = 0; i < LINES; i++) {
bw.write("Line " + i + "\r\n");
}
bw.close();
}
catch(Exception e) {
System.out.println("Exception: " + e);
}
}
}
110
17 Applets
Ein Applet ist ein Java-Programm, das in einer HTML-Seite eingebettet ist und von einem Javafähigen Web-Browser oder sog. AppletViewer ausgeführt wird.
Ein Browser ist Java-fähig, wenn für ihn in der Regel eine abgespeckte Version der JVM als Plug-In
installiert ist. In der Regel befinden sich die HTML-Seite und das Applet auf einem Web-Server. Wenn
eine http-Anfrage eines Clients an den Web-Server gestellt wird, packt der Server die Seite und das
Applet zusammen und sendet sie an den Client. Der Web-Browser des Clients lädt dann die Seite und
das Applet und fängt mit der Darstellung der Web-Seite und der Ausführung des Applets an.
Ein Applet unterscheidet sich von einer Applikation in der Art der Ausführung und in seinem
Lebenszyklus. Während eine Applikation gestartet wird, wenn ihre main-Methode aufgerufen wird,
und beendet wird, wenn die letzte Anweisung ihrer main-Methode ausgeführt wurde, hat ein Applet
einen längeren Lebenszyklus als eine Applikation.
Applets werden entweder von javax.swing.JApplet oder von ihrer Oberklasse
abgeleitet.
Der Lebenszyklus eines Applets ist durch die Methoden init(), start(), stop() und destroy() bestimmt.
Dies sind die Methoden, die überschrieben werden sollen, wenn ein Java-Programm als Applet laufen
soll.
java.applet.Applet
init() wird genau einmal unmittelbar nach Aufruf des Konstruktors automatisch aufgerufen. Sie soll so
überschrieben werden, dass sie Initialisierungsarbeiten vornimmt, z.B. Instanzvariablen initialisieren,
Objekte erzeugen, die das Applet benötigt, Bilder und Schriften laden, Parameter einlesen etc.
start() wird unmittelbar nach dem Aufruf von init() aufgerufen und immer wieder, wenn das Applet
wieder sichtbar wird. Dies ist genau der Fall, wenn der Benutzer zur HTML-Seite des Applets zurück
kehrt, nachdem er andere Seiten besucht hat.
Dies bewirkt, dass das Applet wieder aktiviert wird.
111
stop() wird immer aufgerufen, wenn der Benutzer die HTML-Seite des Applets verlässt und andere
Seiten besucht. Dies ist genau der Fall, wenn das Applet unsichtbar wird.
Diese Methode wird typischerweise überschrieben, um alle Aktionen zu beenden, die nicht mehr
benutzt werden, wenn das Applet unsichtbar wird, und dient dazu, die CPU nicht unnötig zu belasten.
So können gestartete Animationen, Music, Threads usw. gestoppt werden.
destroy() wird nur einmal aufgerufen, wenn der Benutzer das Browser-Fenster schließt,
um laufende Threads zu beenden oder belegten Speicherplatz wieder freizugeben, kurz gesagt, um
hinter dem Applet "aufzuräumen".
Ein Applet wird während seiner Lebensdauer genau einmal geladen, instanziiert und initialisiert
(init()), einmal oder mehrere Male gestartet (start()) oder gestoppt (stopp()) und genau einmal
„vernichtet“ (destroy()).
Alle genannten Methoden sollen entsprechend überschrieben werden.
Aus Sicherheitsgründen darf ein Applet in der Regel weder auf Dateien des lokalen Rechners zugreifen
noch externe Programme auf diesem starten.
Die Kontrolle der Sicherheit eines Applets ist im Browser implementiert, d.h. es ist auch vom Browser
abhängig, welchen Sicherheitsbeschränkungen ein Applet unterliegt. Browser verhindern
standardmäßig Folgendes:
•
•
•
Ausführen eines Programms auf dem Rechner des Benutzers außerhalb der JVM
Dateizugriff auf dem Rechner des Benutzers (weder Lese- noch Schreibrechte)
Aufbau einer Netz-Verbindung zu einem anderen System als zu dem, von dem das Applet
stammt.
Diese Einschränkungen verhindern, dass ein Applet die Datensicherheit verletzt oder das System des
Benutzers beschädigt. Die Beschränkung, dass ein Applet keine neuen Verbindungen aufbauen kann,
verhindert die Kommunikation mit anderen Programmen, die auf nicht vertrauenswürdigen Rechnern
liegen.
Um ein Applet zu schreiben, muss zunächst eine Klasse der folgenden Form erstellt werden:
import java.applet.*;
public class HelloWorld extends Applet {
...
}
Dabei muss die Applet-Klasse public sein und eine Subklasse der Klasse java.applet.Applet
sein.
112
public class TickerApplet extends JApplet implements Runnable {
// Die x-Koordinate der aktuellen Textposition
private int xPos;
// Die y-Koordinate der Textposition
private int yPos;
// Breite des Text-Panners
private int pannerWidth;
private Thread thread;
// Die Laufschrift
private String text;
public TickerApplet() {
}
@Override
public void destroy() {
thread = null;
}
@Override
public void init() {
// Die Schrift soll in der Mitte des Applets dargestellt weden
yPos = getSize().height / 2;
// Das Applet erwartet einen Textparameter namens text
text = getParameter("text");
if (text == null)
text = "Das Applet erwartet einen Textparameter namens Text";
pannerWidth = getSize().width;
xPos = Math.min(xPos, pannerWidth);
}
@Override
public void start() {
thread = new Thread(this);
thread.start();
System.out.println("start");
}
@Override
public void stop() {
thread.stop();// Die Methode Thread.stop() ist deprecated ( wurde
verworfen) und sollte in
// einer richtigen Anwendung nicht verwendet
werden!
// Hinweise wie ein Thread gestoppt werden soll unter:
//
http://java.sun.com/j2se/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
thread = null;
System.out.println("stop");
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.drawString(text, xPos, yPos);
113
}
@Override
public void run() {
while (true) {
repaint();
xPos -= 7;
if (xPos+text.length() < 0)
xPos = pannerWidth;
try { // Thread erfordert Ausnahme-Handler (try-catch-Klausel)
Thread.sleep(120);
} catch (InterruptedException e) {
}
}
}
}
<html>
<head>
<title>Mein erstes Applet</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body>
<h1>Ein Laufschriftapplet</h1>
<p>
<applet code="applets.TickerApplett.class" width="400" height="200">
<param name="text" value="Das ist mein erstes Applet!">
</applet>
</p>
</body>
</html>
Java-Programm als Applet und Applikation
Ein Java-Programm kann so implementiert werden, so dass es sowohl als Applikation als auch als
Applet läuft. Hierzu muss das Programm gewisse Anforderungen erfüllen. So muss die Hauptklasse
des Programms eine Unterklasse von Applet (JApplet) sein und die main-Methode implementieren.
Die main-Methode soll so implementiert sein, dass sie eine Instanz ihrer Klasse erstellt und die init()
und start()-Methode. Die Parameter, die ein Applet von der HTML-Seite erhält, können bei einer
Applikation durch die Kommandozeile übergeben werden.
Beispiel:
114
Methoden zum Zeichnen von Graphikprimitiven:
Die Methode public void paint(Graphics g) selbst. Sie wird immer dann aufgerufen, wenn ein oder die
grafische Oberfläche einer Applikation neu gezeichnet werden muss. Dies ist immer beim ersten
Aufruf des Applets bzw. Programms der Fall, aber auch jedes Mal dann, wenn das Fenster verschoben
oder zwischenzeitlich von einem anderen Fenster überlagert wurde. Der paint()-Methode wird eine
Instanz der Graphics-Klasse weitergegeben, die sie für das Zeichnen verschiedener Formen und Bilder
verwenden kann. Man kann das Java-System jederzeit dazu veranlassen, ein Applet unabhängig von
solchen oben beschriebenen Standardereignissen nachzuzeichnen. Dies erfolgt jedoch nicht mit einem
direkten Aufruf von paint(), wie man sich vielleicht zuerst denkt. Es muss die repaint()-Methode
verwendet werden.
Die Methode public void repaint() kann jederzeit aufgerufen werden, wann auch immer das Applet
oder die grafische Oberfläche einer Applikation neu gezeichnet werden muss. Unabhängig von einem
Standardereignis. Sie ist der Auslöser, der Java veranlasst, die paint()-Methode so bald wie möglich
aufzurufen und das Applet neu zu zeichnen. Die Aussage »so bald wie möglich« weist bereits darauf
hin, dass die paint()-Methode u.U. mit einer gewissen Verzögerung ausgeführt werden kann, wenn
andere Ereignisse zuerst zu Ende geführt werden müssen. Die Verzögerung ist jedoch meist sehr
gering.
Die Methode public void update(Graphics g) wird von repaint() aufgerufen. Die Standard-update()Methode löscht den vollständigen Zeichenbereich des Applets bzw. der grafischen Oberfläche einer
Applikation (oft ruft das den unangenehmen Flimmereffekt bei schnellen Bildsequenzen hervor) und
ruft anschließend die paint()-Methode auf, welche dann das Applet vollständig neu zeichnet. Zum
Löschen des Inhaltes des Zeichenbereiches setzt die update-Methode die Farbe jedes Pixels gleich der
Hintergrundfarbe. Es ist also genau genommen so, dass nicht die repaint()-Methode direkt die paint()Methode aufruft, sondern nur indirekt über die update()-Methode.
Graphics-Klasse:
java.awt.Graphics ist eine abstrakte Klasse die zahlreiche Methoden zum Zeichnen von
Graphikprimitiven zur Verfügung stellt.
Ein Objekt der Klasse Graphics verfügt über Methoden zum Zeichnen von Linien, Kreisen und
Ellipsen, Bögen, Rechtecke, Polygonen und Bildern. Darüber hinaus könne Methoden eines GraphikObjektes zum Auslesen und Festlegen von Farben
zum Zeichnen von Text in verschiedenen Schriften und Schriftstilen
Die Koordinaten im Applet-Fenster werden in der Form (x,y) angegeben. x ist die horizontale
Koordinate, y die vertikale. Der Nullpunkt (0,0) befindet sich oben links in der Ecke des
Zeichenbereichs. Die Maßeinheit entspricht einem Bildschirmpixel und ist damit geräteabhängig.
115
package applets;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
public class Painter extends Applet{
public void paint(Graphics g) {
// Erzeugen von Farbobjekten
Color farbe1 = new Color(255, 100, 92);
Color farbe2 = new Color(155, 192, 192);
Color farbe3 = new Color(55, 192, 192);
Color farbe4 = new Color(5, 12, 120);
Color farbe5 = new Color(255, 192, 192);
g.setColor(farbe1);
g.fillRect(5, 5, 150, 250);
// Zeichne eine Ellipse
116
g.setColor(farbe2);
g.fillOval(50, 15, 250, 150);
// Zeichne einen Kreis
g.setColor(farbe3);
g.fillOval(250, 150, 150, 150);
// Zeichne einen gefüllten Bogen
g.setColor(farbe4);
g.fillArc(50, 15, 100, 100, 90, 90);
// Zeichne einen weiteren gefüllten Bogen in einer anderen Farbe
g.setColor(farbe5);
g.fillArc(150, 150, 50, 50, 120, 180);
// direkte Verwendung einer Farbkonstanten
g.setColor(Color.green);
g.drawRect(40,40,100,200); }
}
package applets;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
/**
* @author baker
*
*/
public class DrawColoredStrings extends Applet{
public void paint(Graphics g) {
Font schrift1 = new Font("TimesRoman", Font.PLAIN, 18);
Font schrift2 = new Font("TimesRoman", Font.BOLD, 14);
117
Font schrift3 = new Font("TimesRoman", Font.ITALIC, 22);
Font schrift4 = new Font("TimesRoman", Font.BOLD + Font.ITALIC, 24);
Font schrift5 = new Font("Arial", Font.PLAIN, 10);
Font schrift6 = new Font("Courier", Font.BOLD, 12);
Font schrift7 = new Font("Arial", Font.ITALIC, 34);
Color farbe1 = new Color(255, 100, 92);
g.setFont(schrift1);
g.drawString("Normaler (plain) Font - TimesRoman", 10, 25);
g.setFont(schrift2);
g.drawString("Fetter (bold) Font - TimesRoman", 10, 50);
g.setFont(schrift3);
g.drawString("Kursiver (italic) Font - TimesRoman", 10, 75);
g.setFont(schrift4);
g.drawString("Fett und kursiv (bold italic) - TimesRoman", 10, 100);
g.setFont(schrift5);
g.drawString("Normaler (plain) Font - Arial", 10, 125);
g.setFont(schrift6);
g.drawString("Fetter Font - Courier", 10, 150);
g.setColor(farbe1);
g.setFont(schrift7);
g.drawString("Farbig, groß und kursiv - Arial", 10, 200);
}
}
118
18 AWT/Swing
Sind Java-API zur Erstellung von graphischen Benutzeroberflächen (GUI) wie Fenster, Buttons,
Textfelder, Registerkarten, etc. und zur Zeichnung von graphischen Primitiven.
AWT ist stark abhängig vom darunterliegenden System und mittlerweile veraltet.
Swing ist Erweiterung des AWT und plattformunabhängig
Beide besitzen Komponenten (Buttons, Textfelder, Scrollbars, Dialogfelder) und Container wie Frames
und Panels. Container dienen der Aufnahme von Komponenten. Zur Ausrichtung von Komponenten
innerhalb Containern werden sog. LayoutManager verwendet.
119
Herunterladen