W3docs

Java Packages

Verwandte Java-Klassen in Packages gruppieren, Namenskonventionen einhalten und Projekte für bessere Wartbarkeit strukturieren.

Java hat keinen riesigen gemeinsamen Pool von Klassennamen. Jede Klasse lebt in einem Package — einem benannten Bereich, der sowohl als Organisationseinheit als auch als Namespace auf Java-Ebene dient. Zwei Klassen namens Logger können ohne Kollision nebeneinander existieren, solange sie in verschiedenen Packages liegen. Der Package-Name wird überall verwendet: in import-Anweisungen, in vollqualifizierten Namen, im Dateisystem und sogar in JAR-Manifests. Ein grundlegendes Verständnis von Packages ermöglicht es, die Projektstruktur anderer auf einen Blick zu erfassen.

Diese Seite erklärt, was ein Package ist, wie man es benennt, den Sonderfall des Default-Packages, wie Package-Namen auf Verzeichnisse abgebildet werden und wie man ein Package in einer eigenen Quelldatei deklariert.

Was ein Package tatsächlich ist

Ein Package erfüllt gleichzeitig drei Zwecke:

  • Ein Namespace. java.util.Date und java.sql.Date sind verschiedene Klassen; der Package-Name hält sie auseinander.
  • Eine Zugriffsgrenze. Ohne einen Modifier sind Member nur innerhalb desselben Packages sichtbar — die sogenannte „package-private"-Zugriffsebene. Das ist eine echte, strukturelle Form der Kapselung; siehe Access modifiers.
  • Ein Verzeichnis. Der Package-Name wird eins-zu-eins auf einen Ordnerpfad abgebildet. com.example.app.util befindet sich unter com/example/app/util/.

Derselbe Name wird an drei Stellen verwendet — Deklaration, Dateipfad und import — und alle müssen übereinstimmen.

Namenskonventionen

Javas Konvention ist das umgekehrte DNS-Namensschema basierend auf einer Domain, die man kontrolliert:

  • Alles Kleinbuchstaben: com.example, nicht Com.Example.
  • Umgekehrte Domain-Reihenfolge: Ein Projekt unter w3docs.com verwendet com.w3docs als Wurzel.
  • Projekt-, Modul- und Feature-Segmente folgen darauf: com.w3docs.learnjava.parser.
  • Java-reservierte Wörter als Segmente vermeiden (int, class, new). Falls die eigene Domain eines enthält, sollte man es abwandeln: com.example.int_ oder anders aufteilen.

Diese Konventionen sind mehr als Ästhetik. Die umgekehrte DNS-Regel sorgt dafür, dass JARs verschiedener Organisationen sicher in denselben Classpath gelegt werden können, ohne Namenskonflikte zu verursachen.

Das Default-Package

Eine .java-Datei ohne package-Deklaration gehört zum Default-Package (unbenanntes Package). Daraus ergeben sich zwei Konsequenzen:

  • Man kann aus dem Default-Package nicht in ein benanntes importieren. Alles darin ist effektiv vom echten Code abgeschnitten.
  • Build-Tools, IDEs und Modulsysteme behandeln das Default-Package als Sonderfall — viele verweigern schlichtweg die Kompilierung dagegen.

Es eignet sich für einzelne Hello.java-Dateien. Nichts Produktives sollte daraus veröffentlicht werden.

Wie Packages auf Verzeichnisse abgebildet werden

Wenn man package com.w3docs.learnjava.parser; am Anfang von Tokenizer.java deklariert, muss die Datei unter folgendem Pfad liegen:

com/w3docs/learnjava/parser/Tokenizer.java

relativ zum Source-Root. Der Compiler leitet das Package nicht aus dem Pfad ab — er liest die Deklaration und vertraut darauf. Aber die Laufzeitumgebung (und die meisten Tools) werden nicht zufrieden sein, wenn beides nicht übereinstimmt.

Dieser Source-Root ist der Ausgangspunkt des classpath: Die JVM muss wissen, wo der Package-Baum beginnt, sonst kann sie nichts finden.

Ein Package deklarieren

Die package-Anweisung weist eine Klasse einem Package zu. Sie muss die allererste Anweisung in der Datei sein — vor allen import-Anweisungen und vor der Klasse selbst. Nur Kommentare und Leerzeilen dürfen ihr vorangehen.

// File: com/w3docs/learnjava/parser/Tokenizer.java
package com.w3docs.learnjava.parser;

import java.util.List;

public class Tokenizer {
    public List<String> tokenize(String source) {
        // ...
        return List.of();
    }
}

Von überall sonst ist diese Klasse jetzt unter ihrem vollqualifizierten Namen bekannt: com.w3docs.learnjava.parser.Tokenizer. Code im selben Package kann einfach Tokenizer verwenden; Code in anderen Packages muss sie entweder importieren oder den vollständigen Namen ausschreiben.

Warnung
Eine Datei darf höchstens eine package-Anweisung enthalten, und nur eine öffentliche Top-Level-Klasse — deren Name mit dem Dateinamen übereinstimmen muss. Steht package irgendwo anders als ganz oben, lehnt der Compiler die Datei ab.

Ein praktisches Beispiel: zwei Loggers

Das überzeugendste Argument für Packages ist die Kollision, die sie verhindern. Das folgende Programm verwendet zwei Klassen, die im Geiste Logger heißen — den JDK-eigenen java.util.logging.Logger über seinen vollqualifizierten Namen — und zeigt, wie das Package einer Klasse Teil ihrer Laufzeitidentität wird.

java— editable, runs on the server

Zwei Erkenntnisse aus dem Ausführen: Klassen kennen ihr eigenes Package zur Laufzeit (über Class.getName() und Class.getPackage()), und der vollqualifizierte Name ist das, was einen Typ eindeutig identifiziert — Logger allein ist mehrdeutig; java.util.logging.Logger nicht.

Was als Nächstes kommt

Ein Package zu benennen ist eine Sache; seine Typen in den eigenen Code zu importieren eine andere. Das nächste Kapitel behandelt die import-Anweisung — Einzeltyp-Imports, Wildcard-Imports, statische Imports und wann welcher die richtige Wahl ist.

Übungen

Übung
Warum verwendet Javas Standardkonvention für Package-Namen die umgekehrte Domain-Reihenfolge, wie z. B. `com.example.app`?
Warum verwendet Javas Standardkonvention für Package-Namen die umgekehrte Domain-Reihenfolge, wie z. B. `com.example.app`?
Was this page helpful?