Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 5. Auflage |
<< | < | > | >> | API | 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, muss 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, dass 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 v = 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, dass die Klasse erst dann in den angegebenen Paketen gesucht wird, wenn das Programm sie wirklich benötigt. Keinesfalls muss der Compiler beim Parsen der import-Anweisung zwangsläufig alle Klassendateien des angegebenen Pakets in den Hauptspeicher laden oder ähnlich zeit- und speicheraufwändige 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 muss im Programm entweder der qualifizierte Klassenname verwendet oder die gewünschte Variante der Klasse mit einer expliziten import-Anweisung eingebunden werden. |
|
Seit der J2SE 5.0 gibt es unter dem Stichwort static import eine weitere Variante der import-Anweisung. Weitere Informationen dazu finden Sie in Abschnitt 9.4. |
|
In vielen verschiedenen Beispielen in diesem Buch werden Klassennamen (wie beispielsweise String, Thread oder Object) verwendet, ohne dass 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, dass 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 und Tabelle 13.2). Sie stieg von 8 Standardpaketen im JDK 1.0 auf 22 im JDK 1.1, 50 im JDK 1.2, 70 im JDK 1.3, 130 im JDK 1.4 und schließlich knapp 200 seit der Java 5 Standard Edition.
Paket | Bedeutung |
java.applet | Applets |
java.awt | Das Abstract Windowing Toolkit inkl. diverser Unterpakete |
java.beans | Java Beans |
java.io | Bildschirm- und Datei-I/O |
java.lang | Elementare Sprachunterstützung |
java.lang.ref | Referenz-Objekte |
java.lang.reflect | Reflection-API |
java.math | Fließkomma-Arithmetik |
java.net | Netzwerkunterstützung |
java.nio | Das seit dem JDK 1.4 vorhandene New I/O Package |
java.rmi | Remote Method Invocation (RMI) |
java.security | Security-Dienste |
java.sql | Datenbankzugriff (JDBC) |
java.text | Internationalisierung |
java.util | Diverse Utilities, Collection-Klassen und Datenstrukturen |
Tabelle 13.1: Wichtige Standard-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: |
|
Paket | Bedeutung |
javax.accessibility | Unterstützung für Braille-Zeilen und ähnliche Ein-/Ausgabegeräte |
javax.crypto | Kryptografische Erweiterungen |
javax.imageio | Lesen und Schreiben von Bilddateien |
javax.naming | Zugriff auf Namens-Services |
javax.print | Unterstützung zum Drucken |
javax.security.auth | Authentifizierung und Autorisierung |
javax.sound | Das Sound-API |
javax.swing | Das SWING-Toolkit |
javax.xml | Zugriff auf XML-Dateien |
Tabelle 13.2: Wichtige Standarderweiterungen des JDK
Des weiteren sind einige Pakete im JDK enthalten, deren Inhalt von Dritten hergestellt wurde. Beispiele dafür sind die diversen Pakete unterhalb der org.omg-Hierarchie, mit deren Hilfe CORBA-Support zur Verfügung gestellt wird. Oder die Paket-Hierarchien org.xml und org.w3c, die Unterstützung zum Zugriff auf XML-Dateien zur Verfügungv stellen.
Paketnamen bestehen in Java aus mehreren Komponenten, die durch Punkte 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 muss zwischen den JDKs 1.2, 1.3 und 1.4 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, muss 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 Missverständnissen geführt. Es ist insbesondere häufig passiert, dass 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, dass 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, dass 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, dass viele kleine Dateien in einer einzigen großen zusammengefasst werden. Zeitaufwändige Verzeichniszugriffe und -wechsel können so entfallen, wenn der Compiler nach Klassennamen suchen muss oder den Inhalt einer Klassendatei lesen will. |
|
Um dem Compiler diese Art der Speicherung der Klassendateien bekanntzumachen,
muss 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 51. |
|
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 dass 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, dass 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 DVD 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üssten 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, dass 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, muss 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 muss als erste Anweisung in einer Quelldatei stehen, so dass 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, dass 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, dass 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, dass 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, dass 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, dass 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üsste 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, dass 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, dass 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, muss 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 | DOC | Handbuch der Java-Programmierung, 5. Auflage, Addison Wesley, Version 5.0.2 |
<< | < | > | >> | API | © 1998, 2007 Guido Krüger & Thomas Stark, http://www.javabuch.de |