Advanced   Java   Services packages Back Next Up Home

Packages

Packages sind eine Sammlung von Klassen. Die Klassen müssen dabei nicht notwendig in einem Zusammenhang stehen, ebensowenig muß Vererbung eingesetz werden. Meist packt man jedoch Klassen zusammen, die insofern verwandt sind, als sie ein gemeinsames Thema haben.


Ein package liegt in einem Verzeichnis

Als erstes legen wir uns einen Ordner an. Da der Ordner ein Javapackage werden soll, können wir den Namen nicht beliebig wählen, sondern wir müssen uns an die Regeln für Bezeichner in Java halten. Es ist eine Konvention, daß ein Packagename mit einem kleinem Buchstaben beginnt und natürlich halten wir uns daran. Wir wählen hier den Namen "myPackage" für unser Verzeichnis. Diesen Ordner legen wir wiederum in ein Verzeichnis "myPackages". Das ist zwar nicht notwendig, aber das Prinzip package wird so (hoffentlich) deutlicher. Auch wird es dadurch einfacher, aus dem Package ein Javaarchiv zu machen, was wir hier später noch vorhaben. Wo wiederum der Ordner myPackages liegt, das ist völlig egal. Wir haben also die folgende Schachtelung:

package.jpg


Die package Anweisung

Im Verzeichnis myPackage legen wir nun einige Klassen an, wobei jede Klasse in einer gleichnamigen Datei steht. Im Gegensatz zu früher erhalten diese Klassen resp. Dateien nun eine zusätzliche Anweisung, die besagt, daß die Klassen zu einem Package gehören und dieses Package müssen wir nun "myPackage" nennen. Hier sind unsere Beispielklassen:

package myPackage;

public class Klasse1
{
   public Klasse1()
   {
      System.out.println("Klasse1 aus dem Paket myPackage");
   }
}

Die nächste Klasse unterscheidet sich nicht wirklich von unserer ersten:

package myPackage;

public class Klasse2
{
   public Klasse2()
   {
      System.out.println("Klasse2 aus dem Paket myPackage");
   }
}

Die dritte Klasse dagegen unterscheidet sich in einem kleinen, aber wesentlichen Punkt von den ersten beiden.

package myPackage ;

class PackagePrivate
{
   public PackagePrivate()
   {
      System.out.println("PackagePrivate aus dem Paket myPackage");
   }
}

Bei der letzten Klasse fehlt der AccessModifier public vor dem Schlüsselwort class. Die Klasse hat also nicht den Schutzzustand public. Diesen Zustand nennt man default oder packageprivate. Die letztere Bezeichnung deutet die Zugriffsmöglichkeiten genauer an. Diese Klasse ist nur innerhalb des packages "myPackage" bekannt, wie wir noch sehen werden. Und schließlich legen wir noch eine letzte Klasse in unser Verzeichnis.

public class NotInThePackage
{
   public NotInThePackage()
   {
      System.out.println("NotInThePackage gehört nicht zum Paket myPackage");
   }
}

Der Name sagt es schon, diese Klasse gehört nicht zu unserem Package, obwohl sie im Verzeichnis myPackage liegt. In der zugehörigen Datei fehlt nämlich die package-Anweisung.


Der CLASSPATH für ein package

Wir legen nun eine main-Datei in einem Verzeichnis an, von dem aus wir Javaprogramme starten können und versuchen ein Objekt vom Typ Klasse1 anzulegen.

public class PackageDemo1
{
   public static void main(String args[])
   {
      Klasse1 k1 = new Klasse1() ;
      // compile-error : cannot resolve symbol
   }
}

Wie zu erwarten bekommen wir eine Fehlermeldung (siehe Kommentar). Wir setzen bzw. erweitern nun den CLASSPATH um das Verzeichnis x:\path\to\myPackages\myPackage und werden gleich feststellen, daß dies nicht der richtige Pfad für unser package ist ! Wir ergänzen unser obiges Programm noch um ein Objekt vom Typ der Klasse NotInThepackage

public class PackageDemo2
{
   public static void main(String args[])
   {
      Klasse1 k1 = new Klasse1() ;
      // bad class file: c:\myPackages\myPackage\Klasse1.class
      // class file contains wrong class: myPackage.Klasse1
      // Please remove or make sure it appears in the
      // correct subdirectory of the classpath.

      NotInThePackage nitp = new NotInThePackage();
      // läßt sich compilieren !!
   }
}

Der obige Versuch endet mit der im Kommentar erwähnten Fehlermeldung. Dagegen läßt sich der Konstruktoraufruf der Klasse, die keine package-Anweisung enthält problemlos compilieren.

Im dritten Anlauf werden wir nun die richtigen Voraussetzungen schaffen, um unser package anzusprechen. Wir sind beim Setzen des CLASSPATH einen Schritt zu weit gegangen. Der CLASSPATH muß nämlich auf das Verzeichnis über dem package-Verzeichnis gesetzt werden, in unserem Fall muß er also folgendermaßen lauten:

x:\path\to\myPackages

package2.jpg

Wir löschen unseren vorhergehenden Eintrag im CLASSPATH und tragen den richtigen Pfad ein. Jetzt müssen wir noch in unserem Beispiel eine import-Anweisung schreiben, analog zu etwa import java.util.*; und alles funktioniert problemlos.

import myPackage.* ;

public class PackageDemo3
{
   public static void main(String args[])
   {
      Klasse1 k1 = new Klasse1() ;

      //NotInThePackage nitp = new NotInThePackage();
      // cannot access myPackage.NotInThePackage
      // bad class file: c:\myPackages\myPackage\NotInThePackage.class
      // class file contains wrong class: NotInThePackage
      // Please remove or make sure it appears in the
      // correct subdirectory of the classpath.
   }
}

Da wir den vorigen Pfad gelöscht haben, ist nun unsere Klasse NotInThePackage nicht mehr ansprechbar. Die Fehlermeldung des compilers finden Sie im Kommentar.


Der default-Accessmodifier

Wir haben unsere Klasse PackagePrivate noch nicht eingesetzt. In unserer main-Datei versuchen wir ein Objekt von diesem Typ anzulegen.

import myPackage.* ;

public class PackageDemo4
{
   public static void main(String args[])
   {
      PackagePrivate pp = new PackagePrivate() ;
      //myPackage.PackagePrivate is not public in myPackage;
      // cannot be accessed from outside package
   }
}

Der Versuch scheitert. Die Fehlermeldung des Compilers finden Sie oben im Kommentar. Die Klasse ist außerhalb des Packages nicht bekannt. Auch ein public-Konstruktor kann daran nichts ändern. Dagegen können wir die Klasse ohne weiteres innerhalb des packages verwenden. Wir ergänzen als Beispiel in der Klasse2 den Konstruktor um ein Objekt vom Typ PackagePrivate.

package myPackage ;

public class Klasse2
{
   public Klasse2()
   {
      PackagePrivate pp = new PackagePrivate();
      // Verwendung innerhalb des packages

      System.out.println("Klasse2 aus dem Paket myPackage");
   }
}

Im obigen Fall ist die ganze Klasse packageprivate. Man kann aber feiner modellieren und eine Klasse public machen, aber darin eine Methode mit default-Schutzstatus schreiben. Wir ändern unsere Klasse1 dahingehend ab.

package myPackage;

public class Klasse1
{
   public Klasse1()
   {
      System.out.println("Klasse1 aus dem Paket myPackage");
   }

   // default - Schutzzustand
   void packagePrivateMethod()
   {
      System.out.println("I'm packageprivate");
   }
}

Wir verwenden nun ohne Probleme die neue Methode in der Klasse2.

package myPackage ;

public class Klasse2
{
   public Klasse2()
   {
      PackagePrivate pp = new PackagePrivate();

      Klasse1 k1 = new Klasse1();
      k1.packagePrivateMethod();  // OK

      System.out.println("Klasse2 aus dem Paket myPackage");
   }
}

Auißerhalb des Packages jedoch können wir auf diese Methode nicht zugreifen. Dazu versuchen wir einen Methodenaufruf in der main-Klasse.

import myPackage.* ;

public class PackageDemo5
{
   public static void main(String args[])
   {
      Klasse1 k1 = new Klasse1() ;
      k1.packagePrivateMethod();
      //packagePrivateMethod() is not public in myPackage.Klasse1;
      //cannot be accessed from outside package
   }
}

Wie man sieht hat das Weglassen des Schlüsselwortes public gravierende Folgen. Es handelt sich wirklich um einen anderen Zugriffsmodus. Verwendet man kein explizites Package, so befinden sich alle Klassen, die man schreibt im gleichen Defaultpackage und der Unterschied zwischen public class Xxx und einfach nur class Xxx ist nicht sichtbar.


Der Accessmodifier protected

Auch die Wirkung des Accessmodifiers protected läßt sich genau erst im Zusammenhang mit packages klären. Im Unterschied zum default-Accessmodifier läßt sich protected nicht für Klassen in einem Package anwenden. protected ist nur anwendbar auf Members einer Klasse (die natürlich selbst wieder Klassen sein können, siehe das Kapitel über nested classes). Um die Wirkung von protected zu demonstrieren legen wir in inserem Package eine neue Klasse an, die eine protected-Methode enthält.

package myPackage ;

public class Klasse3
{
   public Klasse3()
   {
      System.out.println("Klasse3 aus dem Paket myPackage");
   }

   protected void protectedMethod()
   {
      System.out.println("protectedMethod() aus dem Paket myPackage");
   }
}

Als erstes versuchen wir einen Zugriff innerhalb des Packages und stellen fest, daß dieser zulässig ist.

package myPackage ;

public class UsingProtected
{
   public UsingProtected()
   {
      Klasse3 k3 = new Klasse3();
      // Aufruf einer protected-Methode im gleichen Package
      k3.protectedMethod() ;
   }

}

Innerhalb des Packages gibt es also keinen Unterschied zum default-Zustand. Nun versuchen wir den gleichen Zugriff außerhalb des Packages.

import myPackage.* ;

public class UsingProtectedOutside1
{
   public UsingProtectedOutside1()
   {
      Klasse3 k3 = new Klasse3();
      k3.protectedMethod() ;
      //has protected access in myPackage.Klasse3
   }
}

Jetzt weigert sich der Compiler die Klasse zu compilieren. Die Fehlermeldung ist oben im Kommentar eingefügt. Da die Klasse Klasse3 public ist, ist sie außerhalb des Packages bekannt und wir können ein Objekt anlegen und auf alle public-Members zugreifen. Und wir können auch von dieser Klasse erben, da sie ja nicht final ist. Das nächste Beispiel zeigt, daß eine Klasse, die von Klasse3 erbt, die protected-Methode verwenden kann.

import myPackage.* ;

public class UsingProtectedOutside2 extends Klasse3
{
   public UsingProtectedOutside2()
   {
      protectedMethod() ;  // OK

      //Klasse3 k3 = new Klasse3();
      //k3.protectedMethod() ;
      // has protected access
   }
}

Damit haben wir den Wirkung von protected erklärt. protected ist als ein schwächerer Schutzzustand als default, weil man außerhalb des Packages noch auf protected-Members zugreifen kann, wenn man Vererbung einsetzt.


Unterpackages

Wir legen nun im Verzeichnis myPackage ein Unterverzeichnis mySubPackage an und legen dort eine Klasse mit dem Namen Klasse3 an.

package myPackage.mySubPackage;

public class Klasse3
{
   public Klasse3()
   {
      System.out.println("Klasse3 aus dem UnterPaket mySubPackage");
   }
}

Hier muß nun in der package-Anweisung Bezug genommen werden auf das darüberliegende package, sonst ist die Klasse nicht ansprechbar. Hat man die package-Anweisung richtig geschrieben, so kann man die Klasse problemlos ansprechen, vorausgesetzt, man ergänzt eine zweite import-Anweisung. Der CLASSPATH braucht hier nicht mehr geändert werden.

import myPackage.* ;
import myPackage.mySubPackage.* ;

public class PackageDemo6
{
   public static void main(String args[])
   {
      Klasse1 k1 = new Klasse1() ;   // benötigt erstes import
      Klasse3 k3 = new Klasse3() ;   // benötigt zweites import
   }
}

Namenskonflikte

Wir legen nun in unserem Unterpackage auch eine Klasse Klasse1 an und versuchen diese in unserer main-Klasse zu verwenden.

package myPackage.mySubPackage;

public class Klasse1
{
   public Klasse1()
   {
      System.out.println("Klasse1 aus dem UnterPaket mySubPackage");
   }
}

Der Versuch scheitert zunächst, da der Compiler nun nicht erkennen kann, welche Klasse wir meinen.

import myPackage.* ;
import myPackage.mySubPackage.* ;

public class PackageDemo7
{
   public static void main(String args[])
   {
      Klasse1 k1 = new Klasse1() ;
      // reference to Klasse1 is ambiguous,
      // both class myPackage.mySubPackage.Klasse1 in myPackage.mySubPackage
      // and class myPackage.Klasse1 in myPackage match Klasse1 k1 = new Klasse1() ;

      Klasse2 k2 = new Klasse2() ;  // OK
   }
}

Was tun, wenn wir etwa Klasse2 aus myPackage verwenden wollen und auch Klasse1 aus mySubPackage. Hier muß man sich die Mühe machen und bei der Klasse1 den vollständigen Packagenamen verwenden, dann wird es wieder eindeutig. Anders geht es nicht.

import myPackage.* ;
import myPackage.mySubPackage.* ;

public class PackageDemo8
{
   public static void main(String args[])
   {
      // vollständiger Name notwendig
      myPackage.mySubPackage.Klasse1 k1 = new myPackage.mySubPackage.Klasse1() ;

      Klasse2 k2 = new Klasse2() ;
   }
}

Es gibt eine bekannte Situation in der Klassenbibliothek, wo es zu einem Namenskonflikt kommt. So gibt es ein Interface List im Package java.util und eine Klasse List im Package java.awt .


Java-Archive (Packages archivieren)

Wo sind eigentlich die Klassen aus der Klassenbibliothek und wie findet der Compiler sie ? Die Klassen befindet sich in einer Datei mit dem namen rt.jar . Dabei steht rt für runtime und jar für JavaArchiv. Diese Datei befindet sich im Verzeichnis %JAVAHOME%\jre\lib , was dem Compiler natürlich bekannt ist. Wenn sie wollen, können sie eine Kopie dieser Datei anlegen und sie umbenennen in rt.zip (aber lassen Sie die Originaldatei unangetastet !!!) . Wenn sie rt.zip öffnen (nur öffnen, nicht entpacken !) werden sie erfahren, daß in ihr weit über 8000 class-Dateien enthalten sind. Nach einigem Suchen findet man auch eine Datei Manifest.mf in einem Verzeichnis META-INF (großgeschrieben!). Damit haben wir die Struktur eines Java-Archivs ergründet.


Anlegen eines eigenen Archivs

Im Verzeichnis %JAVAHOME%\bin gibt es eine kleine exe-Datei mit dem Namen jar.exe . Mit dieser Konsolanwendung kann man Archive erstellen. Wenn die Umgebungsvariable Path auf das bin-Verzeichnis zeigt, was eigentlich der Fall sein sollte, dann kann man das Utility von jedem Verzeichnis aus aufrufen. Man muß jar.exe "nur" sagen, welche Dateien man in das neu zu erstellende Archiv aufnehmen soll. Die Datei Manifest.mf wird dabei automatisch generiert. Für unser Archiv lautet der Aufruf

C:\>jar  cvf  myArchiv.jar  -C  myPackages  .

Dabei bedeutet

jar Name der Datei jar.exe
c neues Archiv erstellen (create)
v Meldungen nach stderr schreiben (verbose)
f der Name einer jar-Datei wird angegeben (obligatorisch)
myArchiv.jar der Name des zu erstellenden Archivs mit Endung .jar (obligatorisch)
-C wechselt in das nachfolgend angegebene Verzeichnis (change)
myPackages Verzeichnis, in das gewechselt werden soll( immer im Zusammenhang mit -C)
. packt alle Dateien und Unterverzeichnise des vorher angegebenen Verzeichnisses
in das Archiv, aber nicht das angegebene Verzeichnis selbst.

Ein paar "Kleinigkeiten" sind noch wichtig:

Bei obigem Aufruf werden wirklich alle vorhandenen Dateien gepackt, also nicht nur die .class-Files. Man sollte also vorher die Quelldateien an einen anderen Ort transferieren oder löschen. Manchmal will man auch keine Pfadangaben im Java-Archiv. Man will einfach nur einige class-Files packen. Für diesen Fall gibt es den folgenden Aufruf:

C:\path\to\classfiles\jar  cvf  myArchiv.zip  *.class

Man wechselt also diesmal in das Verzeichnis, dessen class-Dateien man packen will und wählt mit *.class daraus nur die class-Files. Die so entstanden Datei enthält dann keine Pfadangeben, was zur Folge hat, daß die package-Anweisung in diesem Fall nicht verwendet werden darf. Der Name myArchiv.zip ist kein Schreibfehler. jar.exe ist es egal, wie sie die Datei nennen, sie könnten auch bla.bla schreiben. Mit diesem einfachen Trick können wir sofort etwa mit Winzip den Inhalt der Datei kontrollieren und sie danach in .jar umbenennen.


Ansprechen der Klassen im Archiv

Dieser Schritt ist der einfachste. Es gibt ab der Version JDK1.2 ein Standardverzeichnis, in dem der Compiler nach Archiven sucht.

Standardverzeichnis für eigene Archive:  %JAVA_HOME%\jre\lib\ext

Wir brauchen nur unser Archiv in dieses Verzeichnis legen und der Compiler findet unsere Klassen mit der üblichen import-Anweisung. Die Java-Archive in diesem Verzeichnis müssen aber echte Packages sein. Es reicht nicht, einfach nur class-Files zu packen und diese dann im ext-Verzeichnis abzulegen. Ohne vorherige package-Anweisung gibt es keine passende import-Anweisung. Wie sollte sie auch aussehen ?


Eine ausführbares Archiv erzeugen (Create an executable JAR file)

Ähnlich wie eine EXE-datei kann auch ein Java-Archiv durch einen Doppelklick gestartet werden. Dazu muß das Archiv eine Manifestdatei enthalten, die angibt welche Klasse die main()-Methode enthält. Man kann die Manifestdatei entweder von Hand schreiben oder sie von jar.exe selbst erzeugen lassen. Es gibt zwei Schalter mit denen man jar.exe dazu bringt eine ausführbate Datei zu erzeugen.


Manifestdatei selbst schreiben (Schalter m verwenden)

Der folgend Aufruf erzeugt ein startbares Archiv.

jar cvfm myArchiv.jar MANIFEST.MF -C myJar .

Die Reihenfolge ist hier wichtig. Zuerst muß der Name des zu erstellenden Archivs kommen, dann kommt der Name MANIFEST.MF. Der Rest ist wie oben. Die selbst erstellte Manifestdatei liegt dabei im Verzeichnis oberhalb von myJar!


Manifestdatei mit notwendigem Eintrag erzeugen lassen (Schalter e verwenden)

Der Schalter e ist einfacher, da man hier nur die Mainklasse angeben muß. Auch hier ist die Reihenfolge wichtig.

jar cvfe myArchiv.jar MyMainClass -C myJar .

Die Entwicklungsumgebung Eclipse verwenden

Am einfachsten ist es, man delegiert die Arbeit an Eclipse, siehe eclipse/index.html.


Der Inhalt der Manifestdatei

ist denkbar simpel...

Manifest-Version: 1.0
Created-By: 1.6.0 (Sun Microsystems Inc.)
Main-Class: MyMainClass

Übungen

static members

protected und public

Valid XHTML 1.0 Strict top Back Next Up Home