Titel | Inhalt | Suchen | Index | API | Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung |
<< | < | > | >> | Kapitel 13 - Strukturierung von Java-Programmen |
Jede Klasse in Java ist Bestandteil eines Pakets. Der vollständige Name einer Klasse besteht aus dem Namen des Pakets, gefolgt von einem Punkt, dem sich der eigentliche Name der Klasse anschließt. Der Name des Pakets selbst kann ebenfalls einen oder mehrere Punkte enthalten.
Damit eine Klasse verwendet werden kann, muß angegeben werden, in welchem Paket sie liegt. Hierzu gibt es zwei unterschiedliche Möglichkeiten:
java.util.Date d = new java.util.Date();
import java.util.*; ... Date d = new Date();
Die Verwendung voll qualifizierter Namen hat den Nachteil, daß die Klassennamen sehr lang und unhandlich werden. Bequemer ist daher die Anwendung der zweiten Variante, bei der die benötigten Klassen mit Hilfe einer import-Anweisung dem Compiler bekanntgemacht werden.
Die import-Anweisung gibt es in zwei unterschiedlichen Ausprägungen:
import java.util.Date; ... Date d = new Date(); java.util.Vector = new java.util.Vector();
import java.util.*; ... Date d = new Date(); Vector v = new Vector();
Im Gegensatz zu ähnlichen Konstrukten in anderen Sprachen ist die Verwendung der zweiten Variante der import-Anweisung nicht zwangsläufig ineffizienter als die der ersten. In der Sprachspezifikation wird sie als type import on demand bezeichnet, was bedeutet, daß die Klasse erst dann in den angegebenen Paketen gesucht wird, wenn das Programm sie wirklich benötigt. Keinesfalls muß der Compiler beim Parsen der import-Anweisung zwangsläufig alle Klassendateien des angegebenen Pakets in den Hauptspeicher laden oder ähnlich zeit- und speicheraufwendige Dinge machen. Es schadet also im allgemeinen nichts, die zweite Variante der import-Anweisung zu verwenden.
Allerdings kann es zur Vermeidung von Verwechslungen mitunter nötig sein, die erste Variante zu verwenden. Enthalten nämlich zwei oder mehr Pakete, die mit der *-Notation importiert wurden, Klassen mit demselben Namen, würde der Compiler die Übersetzung mit einer Fehlermeldung »mehrdeutige Referenz auf Klasse...« abbrechen. In diesem Fall muß im Programm entweder der qualifizierte Klassenname verwendet oder die gewünschte Variante der Klasse mit einer expliziten import-Anweisung eingebunden werden. |
|
In vielen verschiedenen Beispielen in diesem Buch werden Klassennamen (wie beispielsweise String, Thread oder Object) verwendet, ohne daß eine zugehörige import-Anweisung zu erkennen wäre. In diesem Fall entstammen die Klassen dem Paket java.lang.
Dieses Paket wurde von den Entwicklern der Sprache als so wichtig
angesehen, daß es bei jedem Compilerlauf automatisch importiert
wird. Man kann sich das so vorstellen, als wenn am Anfang jeder Quelldatei
implizit die folgende Anweisung stehen würde:
import java.lang.*;
Ein expliziter Import von java.lang ist daher niemals nötig. Alle anderen Pakete müssen jedoch vor ihrer Verwendung importiert werden, wenn auf die Anwendung voll qualifizierter Klassennamen verzichtet werden soll.
Während beim Wechsel der Java-Versionen die Sprachspezifikation relativ stabil geblieben ist, hat sich der Umfang der Laufzeitbibliothek um ein Vielfaches erhöht. Dies zeigt sich unter anderem an der gestiegenen Anzahl an vordefinierten Paketen, die mit dem JDK ausgeliefert werden (siehe Tabelle 13.1). Sie stieg von 8 Standardpaketen im JDK 1.0 auf 22 im JDK 1.1, über 50 im JDK 1.2 und schließlich auf über 70 im JDK 1.3.
Paket | Bedeutung |
java.applet | Applets |
java.awt | Abstract Windowing Toolkit |
java.awt.color | Unterstützung für unterschiedliche Farbsysteme |
java.awt.datatransfer | AWT Clipboard-Funktionen |
java.awt.dnd | Drag-and-Drop |
java.awt.font | Erweiterte Unterstützung für Schriftarten |
java.awt.geom | 2D-Bildverarbeitung |
java.awt.im | Eingabe von fernöstlichen Schriftzeichen |
java.awt.image | AWT-Bildverarbeitung |
java.awt.image.renderable | Darstellungsunabhängige Bildverarbeitung |
java.awt.print | Druckausgabe |
java.beans | Java Beans |
java.beans.beancontext | Java Beans-Unterstützung |
java.io | Bildschirm- und Datei-I/O |
java.lang | Elementare Sprachunterstützung |
java.lang.reflect | Reflection-API |
java.lang.ref | Referenz-Objekte |
java.math | Fließkomma-Arithmetik |
java.net | Netzwerkunterstützung |
java.rmi | Remote Method Invocation (RMI) |
java.rmi.dgc | RMI Distributed Garbage Collection |
java.rmi.registry | RMI Service Registry |
java.rmi.server | RMI-Support |
java.security | Security-Dienste |
java.security.acl | Access Control Lists |
java.security.cert | Zertifikate |
java.security.interfaces | DSA-Interfaces |
java.security.spec | Sicherheitsspezifikationen |
java.sql | Datenbankzugriff (JDBC) |
java.text | Internationalisierung |
java.util | Diverse Utilities und Datenstrukturen |
java.util.jar | Zugriff auf JAR-Files |
java.util.mime | Multipurpose Internet Mail Extensions (MIME) |
java.util.zip | Zugriff auf ZIP-Files |
Tabelle 13.1: Die vordefinierten Pakete des JDK
Neben den Standardpaketen gibt es seit der Version 1.2 des JDK eine Reihe von Standarderweiterungen, deren Paketname mit javax. beginnt. Sie stellen erweiterte Funktionalitäten in einem oder mehreren .jar-Dateien zur Verfügung und werden typischerweise im Unterverzeichnis lib\ext des JDK installiert. Im Gegensatz zu den Standardpaketen des JDK sind sie nicht unbedingt Bestandteil jedes Java-Entwicklungssystems und müssen nicht auf allen Plattformen zur Verfügung stehen. Sie stellen häufig gebrauchte Erweiterungen zur Verfügung, deren Umfang die reinen Kernbibliotheken um ein Vielfaches übertrifft. |
|
Paketnamen bestehen in Java aus mehreren Komponenten, die jeweils durch einen Punkt voneinander getrennt sind. Neben der Aufgabe, die Paketnamen visuell zu strukturieren, hat die Unterteilung aber noch eine andere, sehr viel wichtigere Bedeutung.
Jeder Teil eines mehrstufigen Paketnamens bezeichnet nämlich ein Unterverzeichnis auf dem Weg zu der gewünschten Klassendatei. Soll beispielsweise eine Klasse aus dem Paket java.awt.image eingebunden werden, sucht es der Java-Compiler im Unterverzeichnis java\awt\image. Soll dagegen eine Klasse aus dem Paket com.sun.image.codec.jpeg geladen werden, wird es im Unterverzeichnis com\sun\image\codec\jpeg gesucht. Interessant ist in diesem Zusammenhang natürlich die Frage, in welchem Verzeichnis der Compiler mit der Suche beginnt. Bei der Antwort darauf muß zwischen dem JDK 1.2 bzw. 1.3 und ihren Vorgängerversionen 1.0 und 1.1 unterschieden werden.
Wurde keine Umgebungsvariable CLASSPATH angegeben und der Schalter -classpath beim Compiler-Aufruf nicht verwendet, so suchen der Java-Compiler und die übrigen Tools in einem systemspezifischen Installationsverzeichnis (z.B. c:\java1.1.7\lib beim JDK 1.1.7) und zusätzlich im aktuellen Verzeichnis nach den Klassendateien.
Anders als im JDK 1.0 ist es bei einer Standardinstallation des JDK
1.1 unter Windows 95 nicht erforderlich, den CLASSPATH
explizit zu setzen. Alle Tools generieren den CLASSPATH
implizit aus der Position des bin-Verzeichnisses
(z.B. c:\java1.1.7\bin) nach folgendem
Schema:
.;[bin]\..\classes;[bin]\..\lib\classes.zip
[bin] steht hier für den Pfad des bin-Verzeichnisses. Nur wenn die Klassendateien in einem anderen Verzeichnis liegen oder Klassendateien in weiteren Verzeichnissen eingebunden werden sollen, muß die CLASSPATH-Variable manuell geändert werden.
Existiert zum Zeitpunkt des Compiler-Aufrufs die Umgebungsvariable CLASSPATH, beginnt der Compiler die Suche nach den eingebundenen Klassen in allen im CLASSPATH angegebenen Verzeichnissen. Die einzelnen Verzeichnisse werden durch ein Semikolon (bzw. einen Doppelpunkt unter UNIX) voneinander getrennt.
Beim Aufruf des Compilers kann der Schalter -classpath, gefolgt von einer Liste von Verzeichnisnamen, übergeben werden. Er hat dieselbe Bedeutung wie die Umgebungsvariable CLASSPATH und definiert die Verzeichnisse, in denen der Compiler nach den .class-Dateien sucht. Der Compiler-Schalter hat dabei Vorrang gegenüber einer möglichen Umgebungsvariablen.
Die Verwendung der CLASSPATH-Variable hat immer wieder zu Schwierigkeiten und Mißverständnissen geführt. Es ist insbesondere häufig passiert, daß durch falsches Setzen der Umgebungsvariable die Systemklassen selbst nicht mehr gefunden werden konnten und auf diese Weise das gesamte Laufzeitsystem unbenutzbar wurde.
Seit dem JDK 1.2 wurde daher die Bedeutung der CLASSPATH-Umgebungsvariable dahingehend verändert, daß sie nur noch zur Suche der benutzerspezifischen Klassen verwendet wird. Alle Standardpakete und Standarderweiterungen (beide zusammen werden seit dem JDK 1.2 Bootstrap Classes genannt) werden dagegen unabhängig vom CLASSPATH mit Hilfe der auf das Installationsverzeichnis verweisenden Systemeigenschaft sun.boot.class.path gefunden. Sie wird bei der JDK-Installation automatisch gesetzt und sollte später nicht mehr verändert werden. Der CLASSPATH braucht also nur noch dann explizit gesetzt zu werden, wenn benutzerspezifische Klassen vorhanden sind, die nicht im aktuellen Verzeichnis liegen (letzteres wird ebenfalls automatisch durchsucht).
Falls Sie zuvor eine ältere Versionen des Java Development Kit auf Ihrem Rechner installiert hatten, überprüfen Sie bitte nach der Installation die Umgebungsvariablen. Sorgen Sie dafür, daß nicht ein veralteter CLASSPATH auf Verzeichnisse oder Dateien verweist, aus denen das Laufzeitsystem versehentlich unbrauchbare Klassendateien laden würde. |
|
Im Installationsverzeichnis von JDKs der Versionen 1.0 und 1.1 findet man meist eine Datei classes.zip anstelle der erwähnten Unterverzeichnisse mit den Klassendateien. Aus Gründen der Performance beim Übersetzen haben sich die Entwickler entschlossen, alle Standardklassendateien in diesem Archiv abzulegen, um einen schnelleren Lesezugriff auf sie zu ermöglichen.
Die Datei classes.zip sollte nicht ausgepackt werden, denn der Compiler verwendet sie in archivierter Form. Obwohl beim Einpacken der Klassen auf die Komprimierung verzichtet wurde, bietet diese Form der Archivierung den Vorteil, daß viele kleine Dateien in einer einzigen großen zusammengefaßt werden. Zeitaufwendige Verzeichniszugriffe und -wechsel können so entfallen, wenn der Compiler nach Klassennamen suchen muß oder den Inhalt einer Klassendatei lesen will. |
|
Um dem Compiler diese Art der Speicherung der Klassendateien bekanntzumachen,
muß in der CLASSPATH-Umgebungsvariable
nicht nur das Verzeichnis, sondern auch der Name der .zip-Datei
angegeben werden, z.B.:
CLASSPATH=.;c:\java\LIB\CLASSES.ZIP
Seit dem JDK 1.2 gibt es die Datei classes.zip nicht mehr. Die Klassenbibliotheken liegen nun als .jar-Dateien (z.B. rt.jar ) vor und befinden sich im Unterverzeichnis jre\lib der JDK-Installation. Wie zuvor erwähnt, werden sie unabhängig vom Inhalt der CLASSPATH-Umgebungsvariable gefunden. Weitere Informationen zu jar-Dateien finden Sie in Kapitel 49. |
|
Die Entwickler von Java haben sich einen Mechanismus ausgedacht, um auch bei sehr großen Projekten, an denen möglicherweise viele Entwickler beteiligt sind, Namenskollisionen zwischen den beteiligten Klassen und Paketen zu vermeiden. Auch die Verwendung einer großen Anzahl unterschiedlicher Klassenbibliotheken von verschiedenen Herstellern sollte möglich sein, ohne daß Namensüberschneidungen dies schon im Keim ersticken.
Um diese Probleme zu lösen, hat sich das Java-Team eine Vorgehensweise zur Vergabe von Paketnamen überlegt, die an das Domain-Namen-System bei Internet-Adressen angelehnt ist. Danach sollte jeder Anbieter seine Pakete entsprechend dem eigenen Domain-Namen benennen, dabei allerdings die Namensbestandteile in umgekehrter Reihenfolge verwenden.
So sollten beispielsweise die Klassen der Firma Sun, deren Domain-Name sun.com ist, in einem Paket com.sun oder in darunter befindlichen Subpaketen liegen. Da die Domain-Namen weltweit eindeutig sind, werden Namenskollisionen zwischen Paketen unterschiedlicher Hersteller auf diese Weise von vornherein vermieden. Beispiele für derartige Paketnamen liefert die Standardinstallation gleich mit. So stellt das JDK 1.2 diverse Pakete com.sun.* zur Verfügung. Sie gehören nicht zum Standard-Sprachumfang eines Java-Entwicklungssystems, sondern werden von SUN als eigenständige Erweiterung mit dem JDK ausgeliefert.
Unterhalb des Basispakets können Unterpakete beliebig geschachtelt werden. Die Namensvergabe liegt dabei in der Entscheidung des Unternehmens. Gibt es beispielsweise die Abteilungen is, se und tx in einem Unternehmen mit der Domain tl.de, kann es sinnvoll sein, diese Abteilungsnamen auch als Unterprojekte zu verwenden. Die von diesen Abteilungen erstellten Klassen würden dann in den Paketen de.tl.is, de.tl.se und de.tl.tx liegen. |
|
Der Vollständigkeit halber sollte man anmerken, daß sich das hier beschriebene System in der Praxis noch nicht komplett durchgesetzt hat und insbesondere die Klassen der java.*- und javax.*-Hierarchie Ausnahmen bilden. Es wird mit der zunehmenden Anzahl allgemein verfügbarer Pakete jedoch an Bedeutung gewinnen. Manche Entwickler verwenden ein leicht abgewandeltes Schema, bei dem nur die Top-Level-Domain ausgelassen wird. Die Paketnamen beginnen in diesem Fall nicht mit org oder com, sondern direkt mit dem zweiten Teil des Domain-Namens (oder einem ähnlichen herstellerspezifischen Kürzel). Dadurch werden sie etwas kürzer und sind leichter zu handhaben.
In der Praxis wird man neben der Klassenbibliothek des JDK häufig zusätzliche Pakete von Drittanbietern verwenden wollen. Um den Klassenpfad nicht unnötig lang werden zu lassen, empfiehlt es sich, die Pakete in einem gemeinsamen Verzeichnis abzulegen. Falls sich alle Entwickler von Libraries an das oben besprochene Schema zur Vergabe von Paketnamen halten, kann es keine Überschneidungen geben.
Als Beispiel wollen wir einige Java-Klassen des Autors (zu finden als Datei gkjava.zip unter http://www.gkrueger.com oder auf der CD-ROM zum Buch im Verzeichnis \misc) und die Utilities der »ACME-Labs« von Jef Poskanzer (http://www.acme.com) installieren:
set CLASSPATH==.;c:\classes
Alle Libraries, die sich an diese Konventionen halten, können in der beschriebenen Weise installiert werden. Probleme gibt es nur, wenn ein Anbieter seine Klassendateien nicht in Paketen ablegt. In diesem Fall müßten die Klassendateien in das aktuelle Verzeichnis oder nach c:\classes kopiert werden. Das würde bei Klassendateien von mehr als einem Anbieter natürlich schnell zum Chaos führen, läßt sich aber nicht so einfach ändern. Es bieten sich zwei Handlungsalternativen an:
Beide Varianten sind unbefriedigend, und es bleibt zu hoffen, daß die Anbieter von Java-Klassenbibliotheken sich verstärkt an die Namenskonventionen des Java-Teams halten werden.
Bisher wurde nur gezeigt, wie man Klassen aus fremden Paketen verwendet, nicht aber, wie man selbst Pakete erstellt. Glücklicherweise ist das aber keine Aufgabe für Spezialisten, sondern sehr einfach mit Bordmitteln realisierbar.
Um eine Klasse einem ganz bestimmten Paket zuzuordnen, muß lediglich am Anfang des Quelltextes eine geeignete package-Anweisung verwendet werden. Diese besteht (analog zur import-Anweisung) aus dem Schlüsselwort package und dem Namen des Pakets, dem die nachfolgende Klasse zugeordnet werden soll. Die package-Anweisung muß als erste Anweisung in einer Quelldatei stehen, so daß der Compiler sie noch vor den import-Anweisungen findet.
Der Aufbau und die Bedeutung der Paketnamen in der package-Anweisung entspricht exakt dem der import-Anweisung. Der Compiler löst ebenso wie beim import den dort angegebenen hierarchischen Namen in eine Kette von Unterverzeichnissen auf, an deren Ende die Quelldatei steht. Neben der Quelldatei wird auch die Klassendatei in diesem Unterverzeichnis erstellt.
Da der Java-Compiler eingebundene Quelldateien, die noch nicht übersetzt sind, während der Übersetzung einer anderen Klasse automatisch mit übersetzt, ist das Erstellen eines neuen Pakets sehr einfach. Wir wollen uns ein Beispiel ansehen, bei dem zwei Pakete demo und demo.tools angelegt und die darin enthaltenen Klassen A, B und C in einer Klasse PackageDemo verwendet werden. Am einfachsten ist es, wie nachfolgend beschrieben vorzugehen.
Wir gehen davon aus, daß der CLASSPATH das aktuelle Verzeichnis enthält (also beispielsweise den Inhalt ".;c:\classes" hat), und legen im aktuellen Verzeichnis die Unterverzeichnisse demo und demo\tools an.
001 package demo; 002 003 public class A 004 { 005 public void hello() 006 { 007 System.out.println("Hier ist A"); 008 } 009 } |
demo\A.java |
Sie enthält die Anweisung package demo;, um anzuzeigen, daß die Klasse A zum Paket demo gehört.
001 package demo; 002 003 public class B 004 { 005 public void hello() 006 { 007 System.out.println("Hier ist B"); 008 } 009 } |
demo\B.java |
Auch diese Quelldatei enthält die Anweisung package demo;, um anzuzeigen, daß die Klasse zum Paket demo gehört.
001 package demo.tools; 002 003 public class C 004 { 005 public void hello() 006 { 007 System.out.println("Hier ist C"); 008 } 009 } |
demo\tools\C.java |
Diese Quelldatei enthält die Anweisung package demo.tools;, um anzuzeigen, daß die Klasse zum Paket demo.tools gehört.
001 import demo.*; 002 import demo.tools.*; 003 004 public class PackageDemo 005 { 006 public static void main(String[] args) 007 { 008 (new A()).hello(); 009 (new B()).hello(); 010 (new C()).hello(); 011 } 012 } |
PackageDemo.java |
Ohne vorher die Klassen A, B
oder C separat übersetzen
zu müssen, kann nun einfach PackageDemo
kompiliert werden. Der Compiler erkennt die Verwendung von A,
B und C,
findet die Paketverzeichnisse demo und
demo\tools und erkennt, daß die
Quellen noch nicht übersetzt wurden. Er erzeugt dann aus den
.java-Dateien
die zugehörigen .class-Dateien
(in demselben Verzeichnis wie die Quelldateien), bindet sie ein und
übersetzt schließlich die Klasse PackageDemo.
Die Ausgabe des Programms ist:
Hier ist A
Hier ist B
Hier ist C
Würde nur dann ein eigenes Paket erzeugt werden, wenn die Quelldatei eine package-Anweisung enthält, müßte man sich fragen, zu welchem Paket die Klassen gehören, in deren Quelldatei die package-Anweisung fehlt (was ja erlaubt ist). Die Antwort auf diese Frage lautet, daß es in Java ein Default-Paket gibt, das genau dann verwendet wird, wenn keine andere Zuordnung getroffen wurde.
Das Default-Paket ist ein Zugeständnis an kleinere Programme oder einfache Programmierprojekte, bei denen es sich nicht lohnt, eigene Pakete anzulegen. Ohne Teile des Projektes in Unterverzeichnissen abzulegen und durch import- und package-Anweisungen unnötigen Aufwand zu treiben, ist es auf diese Weise möglich, Quelldateien einfach im aktuellen Verzeichnis abzulegen, dort zu kompilieren und automatisch einzubinden. Klassen des Default-Pakets können ohne explizite import-Anweisung verwendet werden.
Ein Java-Compiler braucht laut Spezifikation nur ein einziges Default-Paket zur Verfügung zu stellen. Typischerweise wird dieses Konzept aber so realisiert, daß jedes unterschiedliche Verzeichnis die Rolle eines Default-Pakets übernimmt. Auf diese Weise lassen sich beliebig viele Default-Pakete erzeugen, indem bei Bedarf einfach ein neues Unterverzeichnis angelegt wird und die Quelldateien eines Java-Projektes dort abgelegt werden. |
|
Es gibt noch eine wichtige Besonderheit bei der Deklaration von Klassen, die von anderen Klassen verwendet werden sollen. Damit eine Klasse A eine andere Klasse B einbinden darf, muß nämlich eine der beiden folgenden Bedingungen erfüllt sein:
Wenn also nur Default-Pakete verwendet werden, spielt es keine Rolle, ob eine Klasse vom Typ public ist, denn alle Klassen liegen in demselben Paket. Werden aber Klassen aus externen Paketen eingebunden, so gelingt das nur, wenn die einzubindende Klasse vom Typ public ist. Andernfalls verweigert der Compiler deren Einbindung und bricht die Übersetzung mit einer Fehlermeldung ab. |
|
Titel | Inhalt | Suchen | Index | API | Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0 |
<< | < | > | >> | © 2000 Guido Krüger, http://www.gkrueger.com |