Java Classpath
Classpath beim Kompilieren und Ausführen von Java-Programmen setzen, damit die JVM Klassen und Abhängigkeiten findet.
Der Compiler weiß, in welchen Paketen er suchen soll, weil es den Classpath gibt — die Menge der Orte, an denen Java nach Class-Dateien sucht. Wenn Sie java MyApp ausführen und eine ClassNotFoundException erhalten, liegt die Ursache fast immer am Classpath: Die JVM konnte eine .class-Datei nicht dort finden, wo sie es erwartete. Wenn Sie verstehen, wie der Classpath aufgebaut ist, wird aus „es läuft einfach nicht" ein präzises, lösbares Problem.
Was der Classpath ist
Der Classpath ist eine geordnete Liste von Orten, in denen die JVM nach .class-Dateien sucht. Jeder Ort ist eines von:
- Ein Verzeichnis — wird als Wurzel eines Paketbaums behandelt. Die JVM sucht darunter nach
com/example/Foo.class. - Eine JAR-Datei — wird durchsucht, als wäre ihr interner Verzeichnisbaum ein Verzeichnis.
- Ein Wildcard wie
lib/*— trifft auf jede.jar-Datei inlib/zu (nicht rekursiv und keine losen.class-Dateien).
Wenn Sie auf com.example.Foo verweisen, durchläuft die JVM den Classpath der Reihe nach und verwendet den ersten Treffer. Wenn zwei Orte dieselbe Klasse enthalten, gewinnt der frühere im Classpath — eine häufige Ursache für „Ich habe das JAR aktualisiert, aber der alte Code wird noch ausgeführt."
Den Classpath setzen
Es gibt drei Möglichkeiten, der JVM mitzuteilen, was sich im Classpath befindet, in der Reihenfolge der Präferenz:
# 1. -cp / -classpath flag (clearest, scoped to the one command):
java -cp out:lib/mylib.jar com.example.App
# 2. The CLASSPATH environment variable (set once, used by every invocation):
export CLASSPATH=out:lib/mylib.jar
java com.example.App
# 3. JAR manifest Class-Path entry (for executable JARs):
java -jar app.jar-cp überschreibt CLASSPATH. Das globale Setzen der Umgebungsvariable ist eine häufige Fehlerquelle — ein veralteter CLASSPATH aus einem längst vergessenen Projekt verursacht mysteriöses Verhalten. Bevorzugen Sie -cp für jeden Befehl.
Unter Windows ist das Trennzeichen ;. Unter macOS und Linux ist es :. Setzen Sie den Wert in Anführungszeichen, wenn er Leerzeichen enthält.
Der Standard-Classpath
Wenn Sie keinen setzen, verwendet die JVM das aktuelle Verzeichnis (.) als Classpath. Deshalb funktioniert javac Hello.java && java Hello für Dateien im Standard-Paket ohne weitere Konfiguration — Hello.class liegt genau dort.
Sobald Sie Ihren Code in ein Paket packen, müssen Sie entweder vom richtigen Ort aus ausführen oder -cp explizit übergeben:
# Source: src/com/example/App.java with `package com.example;`
javac -d out src/com/example/App.java
java -cp out com.example.App # must use the fully-qualified nameEin häufiger Fehler ist java -cp out com/example/App. Das Argument für java ist ein Klassenname, kein Pfad — verwenden Sie Punkte, keine Schrägstriche.
Der Classpath zur Compile-Zeit
javac hat seinen eigenen Classpath, der sich von dem zur Laufzeit unterscheidet:
javac -cp lib/dependency.jar -d out $(find src -name "*.java")-cp listet hier die Orte auf, an denen javac nach Typen sucht, auf die Ihre Quellen verweisen. Alles, was diese Quellen importieren, muss entweder in lib/dependency.jar oder auf dem impliziten Classpath vorhanden sein. Das -d-Flag des Compilers legt fest, wo die ausgegebenen .class-Dateien landen — typischerweise in einem parallelen out/-Baum.
Für die meisten Builds führen Sie javac und java nicht von Hand aus. Build-Tools — Maven, Gradle — stellen den Classpath aus deklarierten Abhängigkeiten zusammen. Der Sinn des manuellen Verständnisses ist, dass Sie debuggen können, was sie getan haben, wenn etwas schiefgeht.
JAR-Dateien im Classpath
Eine JAR-Datei ist eine ZIP-Datei mit Class-Dateien und Metadaten. Legen Sie eine in den Classpath, und die JVM behandelt ihren Inhalt als weiteren Paketbaum:
java -cp app.jar:lib/json.jar:lib/db.jar com.example.MainEin paar praktische Hinweise:
- Wildcards expandieren nur zu JARs:
-cp lib/*trifft auf jede.jar-Datei inlib/zu, nicht auf Unterverzeichnisse oder lose.class-Dateien. - Wildcards sind keine Shell-Globs. Sie werden von der JVM selbst behandelt. Die meisten Shells würden
lib/*zuerst expandieren; die JVM erwartet die wörtliche Zeichenkettelib/*. Setzen Sie sie in Anführungszeichen, um die Shell-Expansion zu verhindern:-cp "lib/*". - Die Reihenfolge ist bei Duplikaten entscheidend. Die erste JAR-Datei, die eine Klasse bereitstellt, gewinnt.
Ausführbare JARs
Wenn Sie eine Main-Class in der META-INF/MANIFEST.MF einer JAR-Datei festlegen, können Sie sie mit nur -jar ausführen:
java -jar app.jarZwei Besonderheiten bei -jar:
-cpwird ignoriert, wenn-jarverwendet wird. Die einzige Möglichkeit, Abhängigkeiten zum Classpath einer ausführbaren JAR hinzuzufügen, ist dasClass-Path:-Attribut im Manifest, das andere JARs auflistet.- Die
Main-Classder JAR ist Pflicht. Ohne sie weigert sich-jarzu starten.
Deshalb wurden Fat JARs — eine einzige JAR, die alle Abhängigkeiten enthält, erstellt mit dem Maven Shade Plugin oder dem Shadow Plugin von Gradle — zum Standard. Sie umgehen das gesamte Classpath-Problem bei ausführbaren JARs.
Classpath-Probleme debuggen
Zwei Fehler weisen direkt auf den Classpath hin, und sie bedeuten leicht unterschiedliche Dinge:
ClassNotFoundException— Code hat explizit eine Klasse über ihren Namen angefordert (oft viaClass.forName(...)oder Reflection), und der Loader konnte sie nirgendwo im Classpath finden.NoClassDefFoundError— die Klasse war beim Kompilieren vorhanden, fehlt aber zur Laufzeit oder kann nicht geladen werden. Die übliche Ursache ist eine Abhängigkeits-JAR, die im Compile-Classpath vorhanden, aber im Runtime-Classpath fehlt.
Wenn Sie auf einen der beiden Fehler stoßen, arbeiten Sie diese Checkliste durch:
- Drucken Sie den Classpath aus, den die JVM tatsächlich verwendet hat —
System.getProperty("java.class.path"), wie das folgende Beispiel zeigt. Der Classpath, den Sie zu übergeben glauben, und der tatsächlich wirksame sind oft unterschiedlich. - Überprüfen Sie das Trennzeichen.
:unter macOS/Linux,;unter Windows. Ein falsches Trennzeichen führt stillschweigend dazu, dass zwei Einträge zu einem ungültigen Pfad zusammengeführt werden. - Wildcards in Anführungszeichen.
-cp "lib/*"— ein nicht in Anführungszeichen gesetzteslib/*wird von der Shell expandiert, bevorjavaes überhaupt sieht. - Denken Sie daran, dass
-jar-cpignoriert. Wenn Sie mit-jarausführen, wird der Classpath der Kommandozeile vollständig verworfen.
Der Modulpfad (ein kurzer Exkurs)
Seit Java 9 hat die JVM auch einen Modulpfad (-p oder --module-path), parallel zum Classpath. Module sind eine strengere, deklarationsbasierte Verpackungseinheit, die auf Paketen aufbaut. Die meisten Anwendungs-Codes laufen noch auf dem Classpath; Module sind am deutlichsten auf JDK-Ebene sichtbar. Sie können sie ignorieren, während Sie die Grundlagen lernen, und später darauf zurückkommen, wenn ein Framework Sie dazu auffordert.
Ein durchgearbeitetes Beispiel
Dieses Programm zeigt den Classpath von innen — was die JVM tatsächlich geladen hat und woher. Es verwendet nur java.lang, daher läuft es überall.
java.class.path gibt den Anwendungs-Classpath aus; der Klassenlader von String gibt null aus, weil JDK-Kernklassen vom Bootstrap-Loader kommen, nicht von einem benutzersichtbaren Classpath-Eintrag. Die Klassenlader-Hierarchie ist der Motor, der den Classpath zum Laufen bringt — ihre Details sind ein Thema für ein fortgeschrittenes Kapitel.
Was als nächstes kommt
Damit sind Pakete und Importe abgeschlossen. Sie haben jetzt alle Bausteine — Benennung, Importieren, Deklarieren, Finden und die Standardbibliothek, die am anderen Ende jeder import-Zeile steht. Als nächstes kommt eine der prägenden Design-Entscheidungen von Java: geprüfte Ausnahmen und die try/catch/finally-Mechanismen zum Umgang mit Fehlern. Weiter zu Java-Ausnahmen.