Java API

Inhalt

 

 

Pakete

 

Default-Paket und Klassenpfad

 

Strings

 

Eigene Pakete

 

Beispiel (Gleitender Durchschnitt)

 

Übungsaufgaben

Pakete

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.awt

Dies ist das abstract windowing toolkit. 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.

Beispiel

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() von Random, die eine zufällige int-Zahl produziert. Damit wir eine Zahl zwischen 0 und 5 erhalten, wenden wir den Modulo-Operator an, der den Rest zu 6 berechnet. Dies ergibt eine Zahl zwischen 0 und 5. Sobald eine 6 erscheint, brechen wir die Schleife ab.

Default-Paket und CLASSPATH

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

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.

class Winkel
{   double Alpha;
    public Winkel (double alpha)
    {   Alpha=alpha;
    }
    public String toString ()
    {   return ""+Alpha; 
            // einfache Umwandlung von double zu String
    }
}

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:

D:\java\rene\kurs>java Test hallo
ollah

Eigene Pakete

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();

Beispiel (Gleitender Durchschnitt)

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;

/**
This class implements a ring (filo) buffer. Pushing and pulling
is most effective.
<p><b>This class is not thread save!</b>
*/

public class RingBuffer
{   private int Size, // Maximale Größe
        First, // Erstes Element
        Last, // Letztes Element
        Length; // Gespeicherte Elemente
    private double X[];

    /**
    Initialize with specific size
    @param size Size of the buffer.
    */  
    public RingBuffer (int size)
    {   Size=size;
        X=new double[Size];
        clear();
    }

    /**
    Initialize with default size (32).
    */
    public RingBuffer ()
    {   this(32);
    }

    /**
    Clear the ring buffer.
    */
    public void clear ()
    {   Length=0;
    }

    /**
    Push a new stack element on top of the stack or throw
    RingBufferOverflowException.
    @param x Value to be pushed.
    */
    public void push (double x)
        throws RingBufferOverflowException
    {   if (Length==0)
        {   First=Last=0; X[0]=x; Length=1;
        }
        else if (Length<Size) // not full
        {   Last++;
            if (Last>=Size) Last=0;
            X[Last]=x;
            Length++;
        }
        else throw new RingBufferOverflowException();
    }

    /**
    Pull the first element from the buffer or throw 
    RingBufferEmptyException.
    @return First buffer 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 Number of available elements.
    */
    public int length ()
    {   return Length;
    }
    
    /**
    Get a value by its number. Empty buffers or too large indices
    will throw exceptions.
    @param i Number of element to retrieve
    @return Element number 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. Was genau mit thread safety gemeint ist, werden wir erst später klären können. Vorläufig genügt es zu sagen, dass die Verwaltung des Puffers durcheinander kommt, wenn mehrere Prozesse gleichzeitig darauf zugreifen. Dies kann jedoch jetzt noch nicht vorkommen.

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. Diese beiden Exceptions sind in gleichnamigen Dateien im Paket rene.util definiert, z.B.

package rene.util;

public class RingBufferOverflowException extends Exception
{
}

in der Datei rene\util\RingBufferOverflowException.java. Nur dadurch, dass sie in eigenen Dateien definiert und public sind, können sie aus anderen Paketen heraus benutzt werden.

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.

import 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). Allerdings muss das Paket java.util importiert werden.

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.

Übungsaufgaben

  1. Informieren Sie sich in der Dokumentation über die Klasse BitSet. Schreiben Sie ein Primzahlsieb für die Primzahlen bis 1000000. Dazu initialisieren Sie alle Bits mit true und streichen dann die Vielfachen aller Zahlen (Bit auf false setzen), die bisher noch nicht gestrichen wurden (die Primzahlen). Zählen Sie die Primzahlen bis 1000000 auf diese Weise.
  2. Machen Sie das Sieb aus 1. effektiver, indem Sie nur die nötigen Vielfachen streichen und nur Bits für ungerade Zahlen setzen.
  3. Schreiben Sie ein Unterprogramm, das testet, ob ein String ein Palindrom ist (das ist ein Wort, das vorwärts wie rückwärts gleich lautet). Nutzen Sie dazu die Methode charAt() von String.
  4. Informieren Sie sich in der Dokumentation über die Klasse StringBuffer, die einen String darstellt, der dynamisch wachsen kann. Schreiben Sie ein Programm, das in einem String jedes Vorkommen von Doppelbuchstaben streicht, indem Sie den String in einen dynamisch wachsenden Stringbuffer kopieren.

Lösung.

Aufgaben ohne Lösung

  1. Nützen Sie die Vector-Klasse, um die ersten 100000 Primzahlen abzuspeichern.
  2. Schreiben Sie Programm, das aus einem String den Teil zwischen den beiden Klammern ( und ) ausschneidet, bzw. den leeren String "", wenn diese Klammern im String nicht vorkommen. Falls Sie dabei die Methode indexOf() von String verwenden, beachten Sie Sonderfälle wie "a)(avc)".
  3. Schreiben Sie 2., indem Sie ein char-Array von dem String anlegen.
  4. Schreiben Sie ein Unterprogramm, das einen String umkehrt. Benutzen Sie dazu ein char-Array oder den StringBuffer (aber nicht seine Methode reverse()!).

Zurück zum Java-Kurs