Mehrere Klassen, Packages und der Classpath
Ein echtes Programm besteht nicht aus einer Datei, sondern aus Dutzenden oder Hunderten. In diesem Kapitel wachsen wir Schritt für Schritt dorthin: erst zwei Klassen, dann Packages, dann eine saubere Ordnerstruktur und am Ende eine einzige Datei, die man weitergeben kann.
Wir arbeiten weiter mit Notepad und Terminal. Neue Sprachmittel tauchen nur
am Rande auf – tippe den Code einfach ab. Was package, import und
public genau bedeuten, besprechen wir in späteren Kapiteln.
Was du am Ende kannst
Abschnitt betitelt „Was du am Ende kannst“- erklären, warum
java de.drv.Hallokeine Pfadangabe ist - Quelltext und übersetzte Klassen in getrennten Ordnern halten
- mit dem Classpath festlegen, wo Java nach Klassen sucht
- ein ausführbares JAR bauen und starten
Teil 1: Zwei Klassen
Abschnitt betitelt „Teil 1: Zwei Klassen“Lege den Ordner C:\dev\workspace\kapitel03\teil1 an und schreibe zwei Dateien.
Die erste Klasse erzeugt einen Begrüßungstext:
public class Begruessung {
public static String fuer(String name) { return "Hallo " + name + "!"; }
}Die zweite enthält wie gewohnt die main-Methode und benutzt die erste:
public class Hallo {
public static void main(String[] args) { System.out.println(Begruessung.fuer("Welt")); System.out.println(Begruessung.fuer("DRV")); }
}Übersetze nur Hallo.java und schau dir das Verzeichnis an:
cd C:\dev\workspace\kapitel03\teil1javac Hallo.javadirjava HalloHallo Welt!Hallo DRV!Obwohl du nur Hallo.java angegeben hast, liegt auch eine Begruessung.class
im Ordner. Der Compiler hat bemerkt, dass Hallo die Klasse Begruessung
braucht, im aktuellen Verzeichnis nach Begruessung.java gesucht und sie gleich
mitübersetzt.
Teil 2: Packages
Abschnitt betitelt „Teil 2: Packages“Solange es nur ein paar Klassen gibt, ist ein einzelner Ordner kein Problem. In einem großen Projekt gibt es aber schnell zwei Klassen mit demselben Namen – und irgendeine Ordnung braucht man auch. Dafür gibt es Packages: Jede Klasse gehört zu einem Package, und der Package-Name wird Teil ihres Namens.
Namenskonvention
Abschnitt betitelt „Namenskonvention“Package-Namen werden kleingeschrieben und beginnen üblicherweise mit dem
umgekehrten Domain-Namen der Organisation. Aus drv.de wird de.drv.
Darunter wird weiter unterteilt, etwa de.drv.util für Hilfsklassen. So sind
Namen weltweit eindeutig: Eine Klasse de.drv.Hallo kann nicht mit einer
Klasse com.example.Hallo verwechselt werden.
Erster Versuch
Abschnitt betitelt „Erster Versuch“Lege den Ordner C:\dev\workspace\kapitel03\projekt an. Darin schreibst du beide
Klassen noch einmal – diesmal mit Package-Angabe in der ersten Zeile:
package de.drv.util;
public class Begruessung {
public static String fuer(String name) { return "Hallo " + name + "!"; }
}package de.drv;
import de.drv.util.Begruessung;
public class Hallo {
public static void main(String[] args) { System.out.println(Begruessung.fuer("Welt")); System.out.println(Begruessung.fuer("DRV")); }
}Die Zeile mit import sagt dem Compiler: Wenn in dieser Datei Begruessung
steht, ist de.drv.util.Begruessung gemeint.
Jetzt übersetzen wir wie gewohnt:
cd C:\dev\workspace\kapitel03\projektjavac Hallo.javaHallo.java:3: Fehler: Package de.drv.util ist nicht vorhandenimport de.drv.util.Begruessung; ^…3 FehlerDer Compiler findet Begruessung nicht mehr, obwohl die Datei direkt daneben
liegt. Geben wir beide Dateien ausdrücklich an:
javac Hallo.java Begruessung.javadirDas klappt, beide .class-Dateien sind da. Also starten:
java HalloFehler: Hauptklasse Hallo konnte nicht gefunden oder geladen werdenUrsache: java.lang.NoClassDefFoundError: Hallo (wrong name: de/drv/Hallo)Die Klasse heißt nicht mehr Hallo, sondern de.drv.Hallo – das sagt uns
Java sogar. Also mit vollem Namen:
java de.drv.HalloFehler: Hauptklasse de.drv.Hallo konnte nicht gefunden oder geladen werdenUrsache: java.lang.ClassNotFoundException: de.drv.HalloAuch das geht nicht. Warum?
Package = Ordner
Abschnitt betitelt „Package = Ordner“Wenn Java eine Klasse de.drv.Hallo laden soll, sucht es nicht irgendwo
nach einer Datei Hallo.class. Es übersetzt den Namen in einen Pfad und schaut
genau dort nach:
| Klassenname | Wo Java sucht |
|---|---|
Hallo |
Hallo.class |
de.drv.Hallo |
de\drv\Hallo.class |
de.drv.util.Begruessung |
de\drv\util\Begruessung.class |
Jeder Teil des Package-Namens ist ein Ordner. Genauso sucht der Compiler
nach Quelltexten – deshalb hat er vorhin Begruessung.java nicht gefunden: Er
hat in de\drv\util\ nachgesehen.
Bringen wir die Dateien also an die richtige Stelle und räumen die alten
.class-Dateien weg:
mkdir de\drv\utilMove-Item Hallo.java de\drvMove-Item Begruessung.java de\drv\utilRemove-Item *.classJetzt reicht es wieder, nur die Hauptklasse anzugeben – den Rest findet der Compiler selbst:
javac de\drv\Hallo.javatree /FOrdnerprojekt
Ordnerde
Ordnerdrv
- Hallo.class
- Hallo.java
Ordnerutil
- Begruessung.class
- Begruessung.java
java de.drv.HalloHallo Welt!Hallo DRV!Teil 3: Quelltext und Bytecode trennen
Abschnitt betitelt „Teil 3: Quelltext und Bytecode trennen“Unser Projekt hat zwei Klassen – und die .class-Dateien liegen schon wild
zwischen den Quelltexten. Bei hundert Klassen will das niemand mehr sehen.
Außerdem gehören nur die Quelltexte ins Archiv bzw. zur Abgabe, die
.class-Dateien lassen sich jederzeit neu erzeugen.
Deshalb trennt man üblicherweise:
src– die Quelltexte, die du schreibstout– alles, was der Compiler erzeugt
Verschiebe den Ordner de nach src und lösche die .class-Dateien:
mkdir srcMove-Item de srcGet-ChildItem src -Recurse -Filter *.class | Remove-ItemMit der Option -d (destination) sagst du dem Compiler, wohin er
schreiben soll. Die Package-Ordner legt er dort selbst an:
javac -d out src\de\drv\Hallo.java src\de\drv\util\Begruessung.javatree /FOrdnerprojekt
Ordnerout
Ordnerde
Ordnerdrv
- Hallo.class
Ordnerutil
- Begruessung.class
Ordnersrc
Ordnerde
Ordnerdrv
- Hallo.java
Ordnerutil
- Begruessung.java
Diesmal mussten wir beide Dateien angeben: Der Compiler sucht fehlende
Quelltexte nur ab dem aktuellen Verzeichnis – und dort liegt jetzt kein de
mehr, sondern src.
Der Classpath
Abschnitt betitelt „Der Classpath“Starten wir das Programm:
java de.drv.HalloFehler: Hauptklasse de.drv.Hallo konnte nicht gefunden oder geladen werdenUrsache: java.lang.ClassNotFoundException: de.drv.HalloJava sucht de\drv\Hallo.class – aber ausgehend von welchem Ordner?
Bisher war das immer das aktuelle Verzeichnis. Jetzt liegen die Klassen in
out. Das müssen wir Java sagen, mit dem Classpath:
java -cp out de.drv.HalloHallo Welt!Hallo DRV!Der Aufruf hat jetzt zwei Teile, die man auseinanderhalten muss:
| Teil | Bedeutung | Art |
|---|---|---|
-cp out |
Wo wird gesucht? | Pfad (Ordner oder JAR-Datei) |
de.drv.Hallo |
Was wird gestartet? | Klassenname |
Ohne -cp ist der Classpath einfach . – das aktuelle Verzeichnis. Deshalb
hat bisher alles ohne diese Angabe funktioniert. -cp ist die Kurzform von
--class-path; beides bedeutet dasselbe.
Der Classpath kann auch mehrere Einträge enthalten, unter Windows getrennt
durch Semikolon: -cp out;lib\eine-bibliothek.jar. So kommen später fremde
Bibliotheken ins Spiel.
Teil 4: Ein ausführbares JAR
Abschnitt betitelt „Teil 4: Ein ausführbares JAR“Unser Programm besteht jetzt aus einem Ordner voller .class-Dateien. Um es
weiterzugeben, packt man es in eine einzige Datei: ein JAR (Java
Archive). Ein JAR ist technisch einfach ein ZIP-Archiv mit einer festgelegten
Struktur.
Das Werkzeug dafür heißt jar und gehört zum JDK:
jar --create --file hallo.jar --main-class de.drv.Hallo -C out .| Option | Bedeutung |
|---|---|
--create |
ein neues Archiv anlegen |
--file hallo.jar |
so soll die Datei heißen |
--main-class de.drv.Hallo |
diese Klasse wird beim Start ausgeführt |
-C out . |
wechsle in den Ordner out und nimm von dort alles (.) |
Was steckt drin?
jar --list --file hallo.jarMETA-INF/META-INF/MANIFEST.MFde/de/drv/de/drv/Hallo.classde/drv/util/de/drv/util/Begruessung.classDie Package-Ordner sind auch im Archiv erhalten. Neu ist die Datei
META-INF/MANIFEST.MF – darin steht unter anderem, welche Klasse die
Hauptklasse ist. Deshalb genügt zum Starten jetzt:
java -jar hallo.jarHallo Welt!Hallo DRV!Diese eine Datei läuft auf jedem Rechner mit Java – egal ob Windows, Linux oder macOS.
Wenn es nicht klappt
Abschnitt betitelt „Wenn es nicht klappt“| Fehlermeldung | Ursache | Lösung |
|---|---|---|
Fehler: Package de.drv.util ist nicht vorhanden |
Der Compiler findet die Quelldatei nicht. | Ordner prüfen: de\drv\util\Begruessung.java; ggf. alle Dateien angeben oder -sourcepath setzen |
Ursache: java.lang.ClassNotFoundException: de.drv.Hallo |
Die Klasse liegt nicht im Classpath. | Aus dem Projektordner starten, -cp out angeben |
Ursache: java.lang.NoClassDefFoundError: Hallo (wrong name: de/drv/Hallo) |
Klasse ohne Package-Namen aufgerufen, oder aus dem falschen Ordner gestartet | Vollen Namen verwenden: java -cp out de.drv.Hallo |
Ursache: java.lang.ClassNotFoundException: de\drv\Hallo |
Pfad statt Klassenname angegeben | Punkte statt Backslashes |
Fehler: Hauptklasse de.drv.Hallo konnte nicht gefunden … bei java -jar |
Beim jar-Aufruf -C out . vergessen – im Archiv liegt dann out/de/drv/… |
JAR mit -C out . neu bauen |
Was jetzt noch fehlt
Abschnitt betitelt „Was jetzt noch fehlt“Schon bei zwei Klassen haben wir:
- Ordner passend zu jedem Package von Hand angelegt
- Dateien verschoben und
.class-Dateien gelöscht - lange
javac-Aufrufe mit jeder einzelnen Datei getippt - den Classpath beim Starten angegeben
- nach jeder Änderung neu übersetzt – und es manchmal vergessen
Stell dir das mit fünfzig Klassen vor. Genau diese Arbeit nimmt uns eine Entwicklungsumgebung (IDE) ab. Im nächsten Kapitel steigen wir um – und du wirst jeden dieser Schritte dort wiedererkennen.
Übungen
Abschnitt betitelt „Übungen“- Schreibe eine dritte Klasse
de.drv.util.Verabschiedungmit einer Methodefuer(String name), die"Auf Wiedersehen " + name + "!"zurückgibt. Rufe sie inHalloauf. Übersetze, starte und baue das JAR neu. Was musst du alles anpassen? - Wechsle mit
cd out\de\drvin den Ordner, in demHallo.classliegt, und versuche, das Programm von dort zu starten. Warum klappt es weder mitjava Hallonoch mitjava de.drv.Hallo? - Lösche
out\de\drv\util\Begruessung.classund starte mitjava -cp out de.drv.Hallo. Was passiert – und warum meldet sich der Fehler erst beim Ausführen und nicht schon beim Übersetzen? - Kopiere
hallo.jar, benenne die Kopie inhallo.zipum und öffne sie im Explorer. ÖffneMETA-INF\MANIFEST.MFin Notepad.