Javaschubla.de - Java als erste Programmiersprache

OO09 Packages

Wir haben schon Dateien aus den vordefinierten Packages java.lang, java.io und java.util benutzt. (java.lang.String, java.lang.System, java.lang.Integer und andere Wrapperklassen für primitive Typen, java.lang.Math, java.io.FileReader, FileWriter, BufferedReader, BufferedWriter, IOException, java.util.Scanner). Um sie verwenden zu können, haben wir z.B.

import java.io.*;

an den Anfang der java-Datei geschrieben. Damit werden alle Klassen und Interfaces aus diesem Package importiert und wir können sie ohne Packageangabe benutzen - sonst hätten wir z.B. statt

BufferedReader br = new BufferedReader(fr);

schreiben müssen

java.io.BufferedReader br = new java.io.BufferedReader(fr);

Stattdessen hätten wir auch nur die Klassen importieren können, die wir benötigen, z.B. statt

import java.io.*;

hätten wir im WriteFile-Beispiel

import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.IOException;

schreiben können. Das ist auch empfehlenswert, weil das Laden von Klassen Zeit und Speicher benötigt. IDEs (integrierte Entwicklungsumgebungen wie z.B. Eclipse) können das meist automatisch für einen erledigen (bei Eclipse Rechtsklick -> "organize imports").

Nun wollen wir selbst Packages anlegen. Die Packagestruktur entspricht dabei der Ordnerstruktur.

  1. Leg einen Ordner namens "test" an.
  2. Leg darin zwei Ordner an, nenn den einen "eins" und den anderen "zwei".
  3. Leg im Ordner "eins" eine Datei namens "Start.java" an. Ihr Inhalt soll sein:
    package test.eins;
    import test.zwei.*;
    
    class Start
    {
      public static void main(String[] args)
      {
        Greeter.hallo();
      }
    }
    
    Kompiliere sie noch nicht. Das geht noch nicht, denn die Klasse test.zwei.Greeter mit ihrer Methode hallo() existiert ja noch nicht.
  4. Leg im Ordner "zwei" eine Datei namens "Greeter.java" an. Ihr Inhalt soll sein:
    package test.zwei;
    
    public class Greeter
    {
      public static void hallo()
      {
        System.out.println("Hallo!");
      }
     
      public static void tschüß()
      {
        System.out.println("Tschüß!");
      }
    }
    
  5. Geh auf der MS-DOS-Eingabeaufforderung oder der Konsole in den Ordner oberhalb von test.
  6. Kompiliere Greeter mit
    javac test\zwei\Greeter.java
    

    (Das würde auch im Ordner zwei mit javac Greeter.java funktionieren.)

  7. Kompiliere dann Start.java mit
    javac test\eins\Start.java
    

    (Das würde nicht im Ordner eins mit javac Start.java funktionieren, er würde dann nicht das Package test.zwei finden können. Es sei denn, man hat nicht nur . (das aktuelle Verzeichnis), sondern auch den Ordner oberhalb von test zu seinem Classpath hinzugefügt.)

  8. Führe dann Start aus mit
    java test.eins.Start
    

    (Auch das würde nicht im Package eins mit java Start funktionieren.)

An dieser Stelle lohnt es sich, sich mit IDEs wie Eclipse zu beschäftigen, wo man auf Knopfdruck kompilieren und ausführen kann.

Vielleicht ist dir aufgefallen, dass die Klasse Greeter public ist und ihre Methoden auch. Das ist notwendig, wenn man auf Klassen und Methoden aus anderen Packages zugreifen will. Eine Klasse, ein Attribut oder eine Methode, die keinen Zugriffsmodifizierer (private, protected oder public) vor sich stehen hat, hat default Zugriff ("Access"), das ist Package-Zugriff. Die anderen Klassen aus demselben Package können auf diese Klassen, Attribute und Methoden zugreifen - so war es möglich, dass die Klasse AngestelltenVerwaltung auf die Attribute von Angestellter zugreifen konnte, bevor diese private gemacht wurden, und auf ihre Methoden zugreifen kann. Das lag daran, dass AngestelltenVerwaltung und Angestellter im selben Ordner, also im selben Package waren. Genauer gesagt waren sie im default Package, denn sie hatten keine Zeile "package ...;" am Anfang der Datei.

Bei den beiden Klassen Greeter und Start haben wir angegeben, dass sie im Package test.zwei und test.eins liegen. Könnten sie das nicht einfach von selbst wissen, wo sie doch im entsprechenden Ordner liegen? Nein, denn z.B. Greeter könnte nicht wissen, ob ihr Package nun das default Package, das Package zwei, das Package test.zwei, das Package java.test.zwei oder das Package Eigene Dateien.java.test.zwei sein soll (unter der Annahme, dass sie in C:\Eigene Dateien\java\test\zwei liegt).

Statt in Start das Package test.zwei zu importieren, hätten wir auch den vollständigen Klassennamen (voll qualifizierter Klassenname, fully qualified class name) verwenden können:

package test.eins;

class Start
{
  public static void main(String[] args)
  {
    test.zwei.Greeter.hallo();
  }
}

Oder wir hätten die Klasse statt das ganze Package importieren können:

package test.eins;
import test.zwei.Greeter;

class Start
{
  public static void main(String[] args)
  {
    Greeter.hallo();
  }
}

Unterpackages

Wenn sich eine Klasse in einem Unterpackage eines anderen Packages befindet, verhält sie sich zu den Klassen in dem übergeordneten Package nicht anders als zu Klassen in irgendeinem anderen Package. Beispiel: Eine Klasse im Package java.awt und eine Klasse im Package java.awt.event sind sich genau so fremd oder bekannt wie eine Klasse im Package java.util und eine Klasse im Package javax.swing. Sie können voneinander die public Attribute und Methoden sehen, aber nicht die mit Default-Zugriff (Package-Zugriff). Wenn eine Klasse in java.awt eine aus java.awt.event benutzen will, muss sie java.awt.event.* (oder java.awt.event.KlassenName) importieren.

Wenn man in seinem Programm mit

import java.awt.*;

alle Klassen und Interfaces aus dem Package java.awt importiert, hat man keine Klassen oder Interfaces aus dem Package java.awt.event import, man muss also noch

import java.awt.event.*;

hinzufügen, wenn man die Klassen und Interfaces aus dem Package verwenden will, ohne immer den vollen Packagenamen vor jeden der Klassennamen zu schreiben ("fully qualified class name").

Konvention

Ein Wort zum Stil: Packagenamen schreibt man komplett klein. Also noch nicht einmal in camelCase. Beispiel: java.awt.datatransfer, java.beans.beancontext. (Ausnahme: CORBA und die meisten Packages in org.omg.)

Statische Imports

Seit JDK 5.0 gibt es eine ganze neue Art von Import: Man kann nicht nur Klassen und Interfaces importieren, sondern auch statische Konstanten, Variablen und Methoden aus einer Klasse. Dann kann man sie nicht nur ohne Angabe des Packages verwenden, sondern auch ohne Angabe der Klasse!

package test.eins;
import static test.zwei.Greeter.hallo;

class Start
{
  public static void main(String[] args)
  {
    hallo();
  }
}

Mit import static test.zwei.Greeter.*; hätten wir alle statischen Konstanten, Variablen und Methoden aus der Klasse test.zwei.Greeter importiert.

Das können wir natürlich auch mit vordefinierten Packages machen:

import static java.lang.Math.*;

class MatheImport
{
  public static void main(String[] args)
  {
    System.out.println( sqrt(2) );
    System.out.println( pow(2,3) );
    System.out.println( abs(-5) );
  }
}

In der nächsten Lektion kommen wir zur Königsdisziplin der Objektorientierung, der Vererbung.