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.Dateundjava.sql.Datesind 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.utilbefindet sich untercom/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, nichtCom.Example. - Umgekehrte Domain-Reihenfolge: Ein Projekt unter
w3docs.comverwendetcom.w3docsals 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.javarelativ 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.
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.
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.