Inhalt |
|
|
|
|
|
|
|
|
|
|
|
|
Die vordefinierten Klassen von Java sind in Pakete organisiert. Die Gesamtheit dieser Pakete bildet das API (Application Programming Interface). Es enthält Hunderte von Klassen für die verschiedensten Zwecke.
Es folgt eine Übersicht über wichtige Pakete, die in Java 1.1 definiert sind.
java.swing |
Dies ist das Toolkit für graphische Benutzeroberflächen. Es enthält Klassen zur Grafikausgabe und zur Erzeugung von graphischen Benutzeroberflächen (GUI). |
java.beans |
Die Beans-Technologie soll fertige Elemente zur Programmierung, insbesondere von graphischen Benutzeroberflächen, liefern. Der Zweck ist, visuelle Programmierung zu ermöglichen. |
java.io |
Klassen zur Ein- und Ausgabe von Dateien, vom Netz oder von anderen Quellen. |
java.lang |
Dieses Paket wird automatisch geladen. Es enthält wichtige Dinge, wie die String-Klasse. |
java.math |
Java unterstützt den Umgang mit langen Zahlen in diesem Paket in Form von großen Dezimalzahlen und großen Ganzzahlen. |
java.net |
Java unterstützt mit diesem Paket die Kommunikation über das Internet. |
java.rmi |
Es gibt in diesem Paket eine Klasse, die es erlaubt Methoden von Klassen an entfernten Rechnern über das Netz oder von anderen parallel laufenden Programmen aufzurufen. |
java.security |
Da Java von vornherein eine Sicherheitsphilosophie verfolgt, unterstützt es in diesem Paket Verschlüsselung. |
java.text |
Da Java schon Unicode benutzt, bietet dieses Paket Schnittstellen für die Entwicklung internationalisierter Programme. |
java.util |
Enthält einige Datenstrukturen, wie wachsende Arrays oder auch Stapelspeicher, sowie andere nützliche Dinge. |
Wir werden später noch viele Beispiele für die Verwendung von Klassen aus der API verwenden. Hier begnügen wir uns mit einem einfachen Beispiel.
import java.util.Random; public class Test { public static void main(String args[]) { Random r = new Random(); int count = 0; while (true) { int w = r.nextInt(6) + 1; count++; if (w == 6) break; } System.out.println(count + " Würfe bis zur ersten 6."); } }
Der import-Befehl muss vor der ersten Klassendefinition stehen. Die Klasse Random steht im Paket java.util, und muss beim Importieren vollständig spezifiziert werden. Alternativ können wir alle Klassen aus java.util mit
import java.util.*;
importieren. Schließlich ist es noch möglich, überhaupt nichts zu importieren. Dann muss aber die Klasse vollständig angegeben werden.
java.util.Random r=new java.util.Random();
Wir benutzen die Methode nextInt(n) von Random, die eine zufällige int-Zahl zwischen 0 und n-1 produziert. Sobald eine 6 erscheint, brechen wir die Schleife ab.
Neben dem Default-Paket lädt jedes Programm das Paket java.lang, das der Compiler an einem vordefinierten Platz findet. Um diese Details muss sich der Programmierer nicht kümmern, da das aktuelle Directory und das Directory, wo sich lang befindet, im normalen Klassenpfad enthalten sind. Dieser Pfad kann durch Setzen der Environment-Variablen CLASSPATH geändert werden.
Java kann auch mit komprimierten Paketen umgehen. So sind etwa in Java 1.1 alle Klassen lang.* in eine Datei classes.zip gepackt. Eine solche Datei muss ebenfalls im Klassenpfad sein, damit sie gefunden wird. Man spricht von archivierten Klassen. Insbesondere beim Laden über das Internet ist eine solche Komprimierung nützlich. Java verwendet den Komprimierer jar, der ganz in Java geschrieben ist, und Archive mit der Endung .jar erzeugt.
In DOS wird der CLASSPATH z.B. folgendermaßen gesetzt
set CLASSPATH=d:\mydir
Wird nun ein Java-Programm kompiliert oder ausgeführt, so wird dieser Pfad zusätzlich zu dem nromalen Pfad nach Klassen durchsucht.
Strings sind in java.lang definiert. Die String-Klasse hat in Java einen Sonderstatus. Z.B. wurden für sie eigene Konstanten eingeführt, nämlich "...". Man kann daher schreiben
String s = "Dies ist eine String-Konstante";
Außerdem gibt es den speziellen Operator + für Strings. Dieser Operator kann sogar Strings mit elementaren Datentypen addieren, die dann in Strings umgewandelt werden.
String s = "Hallo"; s = s + '!'; // s ist nun "Hallo!"
Hier wurde ein char-Wert an einen String gehängt. Wir haben bei der Ausgabe schon mehrfach double- oder int-Werte mit Strings verkettet.
In der Tat kann + jedes Objekt verarbeiten, das eine sinnvolle toString() Methode hat. Wir überschreiben die toString() Methode von Objekten mit der Ausgabe des Winkelwerts.
class Winkel { double Alpha; public Winkel(double alpha) { Alpha = alpha; } /** * Überschreibe toString() Methode */ @Override public String toString() { return "" + Alpha; } } public class Test { static public void main(String args[]) { String s = "Wert "; Winkel a = new Winkel(Math.PI); s = s + a; System.out.println(s); // Druckt: Wert 3.141592653589793 } }
Das Beispiel zeigt auch, wie man einen double-Wert zu einem String umwandeln kann. Man addiert ihn einfach zu "".
Natürlich hat die String-Klasse auch eine Reihe von Methoden. Am besten informiert man sich darüber in der Java-Dokumentation.
Strings lassen sich nicht nur mit Hilfe von ="..." initialisieren, sondern auch über den Inhalt eines Arrays von char-Werten oder von byte-Werten. Dies wird über Konstruktoren erreicht. Dazu ein Beispiel:
public class Test
{
static public void main (String args[])
{
char c[]={'H','a','l','l','o'};
String s=new String(c);
// char-Array nach String umwandeln
System.out.println(s);
}
}
Natürlich ist auch der umgekehrte Weg möglich.
public class Test
{
static public void main (String args[])
{
String s="Hallo";
char c[]=s.toCharArray(); // Umwandeln in char-Array
for (int i=0; i<c.length; i++)
System.out.print(c[i]);
System.out.println();
}
}
Hier wird der String Buchstabe für Buchstabe ausgegeben. Der String bietet außerdem Methoden zum Bilden von Teilstrings und zum Suchen.
Wir haben Strings bisher, außer in Konstanten, schon in dem args-Parameter von main kennengelernt. Dieser Parameter enthält ein Array von Strings.
Dazu folgendes Beispiel, das ein Wort aus der Kommandozeile entgegennimmt und rückwärts wieder ausgibt.
public class Test { static public void main (String args[]) { if (args.length==1) { char c[]=args[0].toCharArray(); for (int i=c.length-1; i>=0; i--) System.out.print(c[i]); System.out.println(); } } }
Das Programm funktioniert folgendermaßen:
java Test hallo ollah
Das Paket, in dem unsere Programme bisher die Klassen ablegten, war das default-Paket. Die Klassen wurden im aktuellen Directory vom Compiler abgelegt, und dort vom Interpreter gefunden.
Man kann auch sein eigenes Programm in Pakete organisieren. Dazu muss man die Klassendateien in Unterverzeichnisse abspeichern, aber vom Hauptverzeichnis aus kompilieren. Dies sieht so aus
javac MyPackage/Test.java
wenn Test.java eine Datei im Verzeichnis MyPackage ist. Außerdem muss die Datei Test.java gleich zu Beginn das Kommando
package MyPackage;
enthalten, also noch vor der ersten Klassendefinition. Drittens müssen alle Dateien, die Test.java verwenden wollen (außer den Dateien in MyPackage), die Klasse importieren.
import MyPackage.Test;
oder auch
import MyPackage.*;
Diese Anweisung importiert gleich alle Klassen im Paket MyPackage. Die import-Anweisungen müssen vor den Klassen-Definitionen stehen, aber nach der package-Anweisung. Insbesondere müssen Klassen aus den vordefinierten Paketen importiert werden (außer aus java.lang).
Alternativ kann man die Klasse auch mit vollem Namen ansprechen.
MyPackage.Test t=new MyPackage.Test();
Bei der Verwendung der IDE von Eclipse genügt es, eine neue Klasse in einem neuen Projekt zu definieren
Dieses Beispiel ist zwar numerischen Natur, aber vielleicht auch für Nicht-Mathematiker verständlich und von Interesse. Wir verwenden hier eine eigenes Paket, dass ich rene.util genannt habe. Die Namenskonvention von Sun verlangt eine eindeutigere Spezifikation des Herstellers als rene, aber für unsere Zwecke genügt das.
Ein Ringpuffer ist ein Puffer, dessen Elemente wir uns im Kreis angeordnet vorstellen. Es ist daher ohne Verschieben von Elementen möglich, das unterste Element zu entfernen oder oben ein Element hinzuzufügen. In Wirklichkeit verwenden wir ein Array einer festen Größe. Das Array braucht natürlich nicht vollständig gefüllt zu sein, d.h. es wird nur ein Teil zur Speicherung unseres Puffers benutzt. Der Puffer läuft von einem Start-Index zu einem End-Index, der durchaus vor dem Start-Index liegen kann.
Als Beispiel nehmen wir einen Puffer der Länge 5. Wir speichern nacheinander die Werte A, B, C, ... auf dem Puffer ab. Der Puffer sieht also Schritt für Schritt so aus.
X X X X X A X X X X A B X X X A B C X X A B C D X A B C D E
X bezeichnet jeweils unwichtige Array-Inhalte. An diesem Punkt ist der Puffer voll. Nun muss man das unterste Element entfernen, um F speichern zu können. Dies geht in zwei Schritten (pull and push)
X B C D E F B C D E
Als Beispiel führen wir nun noch zweimal pull aus. Dabei wird B und C entfernt.
F X C D E F X X D E
Ein erneutes push von G führt nun z.B. auf
F G X D E
Die Implementation dieses Puffers erfolgt in der Datei rene\util\RingBuffer.java mit folgendem Code.
package rene.util; class RingBufferOverflowException extends Exception { } class RingBufferEmptyException extends Exception { } /** * Ring Puffer zur Speicherung von Daten bis zu einer gewissen Größe, im Stil * first-in-first-out. */ public class RingBuffer { private int Size, // Maximale Größe First, // Erstes Element Last, // Letztes Element Length; // Gespeicherte Elemente private double X[]; /** * Initialisierung. * * @param size Puffergröße */ public RingBuffer(int size) { Size = size; X = new double[Size]; clear(); } /** * Initialisierung mit Default-Größe. */ public RingBuffer() { this(32); } /** * Löschen. */ public void clear () { Length = 0; } /** * Neues Element auf dem Puffer ablegen. * * @param x Neuer Wert */ public void push (double x) throws RingBufferOverflowException { if (Length == 0) { First = Last = 0; X[0] = x; Length = 1; } else if (Length < Size) // Nicht voll { Last++; if (Last >= Size) Last = 0; X[Last] = x; Length++; } else throw new RingBufferOverflowException(); } /** * Entferne das erste Elment des Puffers. * * @return Erstes Element */ public double pull () throws RingBufferEmptyException { if (Length == 0) throw new RingBufferEmptyException(); double x = X[First]; First++; if (First >= Size) First = 0; Length--; if (First == Last) Length = 0; return x; } /** * Test, if the buffer is empty. */ public boolean isEmpty () { return Length == 0; } /** * Test, if the buffer is full. */ public boolean isFull () { return Length >= Size; } /** * @return Anzahl der Elemente im Buffer. */ public int length () { return Length; } /** * Zugriff auf Elemente mit Index. * * @param i * @return Element Nummer i */ public double get (int i) throws RingBufferEmptyException, RingBufferOverflowException { if (Length == 0) throw new RingBufferEmptyException(); if (i >= Length) throw new RingBufferOverflowException(); int k = First + i; if (k >= Size) k -= Size; return X[k]; } }
Dies ist schon recht umfangreich. Zunächst sorgt der package-Befehl am Anfang dafür, dass die Datei auch in das richtige Package eingeordnet wird. Die Tatsache, dass sie im richtigen Unterverzeichnis liegt ist zwar notwendig, genügt aber noch nicht.
Man beachte, dass die Datei Kommentare eines bestimmten Formates enthält. Dieses Format kann vom Programm javadoc benutzt werden, um HTML-Seiten zu erstellen, die die Klasse sehr gut dokumentieren. Man kann dann sogar HTML-Code verwenden. Z.B. wird die Tatsache, dass diese Klasse nicht multitasking-fähig ist, mit fetter Schrift hervorgehoben.
Interessant ist, was passiert, wenn vom leeren Puffer gelesen, oder auf den vollen Puffer geschrieben wird. In diesem Fall werfen wir eine Exception. Dazu verwenden wir zwei spezielle Exceptions, nämlich RingBufferOverflowException und RingBufferEmptyException.
Zur Implementation des Puffers ist noch zu sagen, dass wir außer dem Anfangs- und Endindex noch die Länge speichern. Dadurch kann sehr leicht festegestellt werden, ob der Puffer leer oder voll ist. Etwas kompliziert ist die Verwaltung der Indizes deswegen, weil ständig getestet werden muss, ob man sich links aus dem Array herausbewegt.
Zum Testen erweitern wir die Klasse zu einer Klasse, die gleitende Durchschnitte berechnen kann. Ziel ist folgende Ausgabe.
1 1.0 2 1.5 3 2.0 4 3.0 5 4.0 5 4.666666666666667 5 5.0 5 5.0
Dabei ist die linke Zahlenkolonne vorgegeben. Die rechte Zahlenkolonne ist jeweils gerade der Durchschnitt der letzten drei Zahlen (oder weniger, falls nur weniger vorhanden sind). Z.B ist der Mittelwert von 3, 2 und 1 gleich 2, der Mittelwert von 5, 5 und 4 gleich 4.66...
Die Implementation ist nicht schwierig, wenn ein Ringpuffer zur Verfügung steht.
package rene.util; /** * A class to compute gliding averages. */ public class GlidingAverage extends RingBuffer { /** * @param size The size of the average buffer. */ public GlidingAverage(int size) { super(size); } /** * Push an element to the gliding average buffer. * * @param x The value to be pushed */ synchronized public void push (double x) { try { if (isFull()) pull(); super.push(x); } catch (Exception e) { System.out.println(e); } } /** * @return The gliding average */ synchronized public double computeAverage () { try { int n = length(); double sum = 0; for (int i = 0; i < n; i++) { sum += get(i); } return sum / n; } catch (Exception e) { System.out.println(e); } return 0; } }
Diese Klasse ist im Default-Package, und wird im aktuellen Verzeichnis gespeichert (in dem sich das Unterverzeichnis rene befindet). Ob man die Kommentare in Englisch oder Deutsch hält, ist Geschmackssache.
Wir fangen den Exceptions ab, die eigentlich nicht auftreten dürften. Dies ist nützlich, um das Programm zu debuggen.
Das Haupt- und Testprogramm sieht nun folgendermaßen aus, und steht wieder in einer anderen Datei.
public class AverageTest { public static void main (String args[]) { GlidingAverage ga=new GlidingAverage(3); for (int i=1; i<=5; i++) { ga.push(i); System.out.println(i+" "+ga.computeAverage()); } for (int i=0; i<3; i++) { ga.push(5); System.out.println(5+" "+ga.computeAverage()); } } }
Wir speichern die Zahlen 1 bis 5 und dann noch dreimal die 5, und drucken jeweils den Durchschnitt der letzten drei Zahlen. Die Ausgabe ist oben wiedergegeben.