In diesem Artikel erfährst du:
- Wie können mit
import module
ganze Java-Module importiert werden? - Wie können dabei Mehrdeutigkeiten aufgelöst werden?
- Was sind transitive Modul-Importe, und wie funktionieren sie?
- Welche Module werden standardmäßig importiert?
Beginnen wir mit einem ganz kurzen Rückblick...
Java Imports
Seit Java 1.0 können wir mit dem import
-Statement einzelne Klassen („single-type-import declaration“) oder ganze Pakete („type-import-on-demand declaration“) importieren, z. B. wie folgt:
import java.util.*;
import java.util.stream.Stream;
Code-Sprache: Java (java)
Die Klassen des Pakets java.lang
werden seit jeher automatisch importert. Deshalb müssen wir für Klassen wie Object
, String
, Integer
, Exception
, usw. keine import
-Statements angeben.
Module Import Declarations
Mit import module
können wir seit Java 23 auch ganze Module importieren¹. Dadurch können wir alle Klassen, die sich innerhalb eines Moduls befinden und von diesem exportiert werden, direkt verwenden.
Im folgenden Beispiel importieren wir das Modul java.base
und können dadurch die Klassen List
, Map
, Stream
und Collectors
verwenden, ohne sie einzeln oder paketweise importieren zu müssen (String
und Character
liegen im Paket java.lang
und wurden daher schon immer automatisch importiert).
import module java.base;
public class ModuleImportTest {
public static Map<Character, List<String>> groupByFirstLetter(String... values) {
return Stream.of(values).collect(
Collectors.groupingBy(s -> Character.toUpperCase(s.charAt(0))));
}
}
Code-Sprache: Java (java)
Wenn du dieses Code-Beispiel in der Datei ModuleImportTest.java speicherst, kannst du die Klasse in Java 23 wie folgt kompilieren (in Java 24 musst du den Parameter --source 23
durch --source 24
ersetzen):
javac --enable-preview --source 23 ModuleImport.java
Code-Sprache: Klartext (plaintext)
Um import module
zu verwenden ist es – wie im vorangegangenen Beispiel gesehen – nicht nötig, dass sich die importierende Klasse selbst in einem Modul befindet.
Simple Source Files (auch bekannt als „Implizit deklarierte Klassen“) und JShell importieren automatisch das java.base
-Modul, d. h. Klassen wie List
und Map
kannst du dort ohne explizite Imports verwenden¹.
Mehrdeutige Klassennamen
Manchmal kommt es vor, dass ein Klassenname nicht eindeutig ist. In folgendem Beispiel gibt es eine List
-Klasse sowohl im importierten Modul java.base
(java.util.List
) als auch im Modul java.desktop
(java.awt.List
):
import module java.base; // ← Contains java.util.List
import module java.desktop; // ← Contains java.awt.List
public class Ambiguous {
List list; // ← Ambiguous reference to List
}
Code-Sprache: Java (java)
Wenn du die Datei unter Ambiguous.java abspeicherst und dann wie folgt mit Java 23 kompilierst:
javac --enable-preview --source 23 Ambiguous.java
Code-Sprache: Klartext (plaintext)
... oder wie folgt mit Java 24:
javac --enable-preview --source 24 Ambiguous.java
Code-Sprache: Klartext (plaintext)
... dann bricht der Compiler mit folgender Fehlermeldung ab:
Ambiguous.java:5: error: reference to List is ambiguous
List list;
^
both class java.awt.List in java.awt and interface java.util.List in java.util match
Code-Sprache: Klartext (plaintext)
Das bedeutet, dass der Compiler nicht weiß, welche der beiden List
-Klassen gemeint ist.
Mehrdeutige Klassennamen auflösen
Nehmen wir an, du möchtest java.util.List
verwenden (und nicht java.awt.List
). Dann hast du zwei Möglichkeiten, um diese Mehrdeutigkeit aufzulösen:
Option 1: Du importierst zusätzlich die Klasse java.util.List
direkt:
import module java.base;
import module java.desktop;
import java.util.List; // ← Ambiguity resolved by single-type-import declaration
public class Ambiguous {
List list;
}
Code-Sprache: Java (java)
Option 2: Du importierst zusätzlich das Paket java.util
(in der Java-Terminologie heißt das übrigens nicht „Package Import“ sondern „Type-Import-on-Demand Declaration“):
import module java.base;
import module java.desktop;
import java.util.*; // ← Ambiguity resolved by type-import-on-demand declaration
public class Ambiguous {
List list;
}
Code-Sprache: Java (java)
Die zweite Option ist erst seit Java 24 verfügbar. Wenn du das letzte Beispiel mit Java 23 kompilierst, führt dies ebenfalls zur Fehlermeldung „reference to List is ambiguous“.
Transitive Modul-Importe
Wenn ein mit import module
importiertes Modul ein drittes Modul transitiv importiert, dann sind auch alle Klassen der exportierten Pakete dieses dritten Moduls ohne explizite Imports nutzbar.
Ich möchte dir das am Beispiel der Module java.sql
und java.xml
erklären.
- Das Modul
java.sql
hat (u. a.) eine transitive Abhängigkeiten auf das Moduljava.xml
. - Das Modul
java.sql
exportiert die Paketejava.sql
undjavax.sql
. - Das Modul
java.xml
exportiert die Paketejavax.xml
undorg.w3c.dom
, jeweils mit zahlreichen Unterpaketen.
Die folgende Grafik zeigt die Module, ihre Abhängigkeiten und die exportierten Pakete:
In der Modul-Deklarationen des java.sql
-Moduls sieht das so aus:
module java.sql {
. . .
requires transitive java.xml;
exports java.sql;
exports javax.sql;
. . .
}
Code-Sprache: Java (java)
Und in der Modul-Deklaration von java.xml
so:
module java.xml {
exports javax.xml;
exports javax.xml.parsers;
. . .
}
Code-Sprache: Java (java)
Wenn wir nun ein Programm schreiben, das das Modul java.sql
importiert, dann benötigen wir beispielsweise keine expliziten Imports für die Klassen SAXParserFactory
und SAXParser
aus dem Paket javax.xml.parsers
des java.xml
-Moduls – und auch keinen expliziten Import dieses Moduls:
import module java.sql; // ← Transitively imports module java.xml
// and its exported packages, e.g. javax.xml.parsers
. . .
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
. . .
Code-Sprache: Java (java)
Denn aus der transitiven Abhängigkeit von Modul java.sql
auf Modul java.xml
und der Tatsache, dass java.xml
das Paket javax.xml.parsers
exportiert, folgt, dass das Programm auch ohne explizite Imports auf alle Klassen des Pakets javax.xml.parsers
zugreifen kann.
Automatischer java.base-Import in JShell
Sobald Module Import Declarations Produktionsreife erlangt haben wird JShell automatisch das Modul java.base
importieren. Aktuell kannst du dies bereits mit --enable-preview
aktivieren:
$ jshell --enable-preview
| Welcome to JShell -- Version 23.0.1
| For an introduction type: /help intro
jshell> /imports
| import java.base
Code-Sprache: Klartext (plaintext)
Wenn du JShell aktuell ohne --enable-preview
startest und das /imports
-Kommando eingibst, wirst du hingegen die folgenden zehn standardmäßige Paket-Importe sehen:
$ jshell --enable-preview
| Welcome to JShell -- Version 23.0.1
| For an introduction type: /help intro
jshell> /imports
| import java.io.*
| import java.math.*
| import java.net.*
| import java.nio.file.*
| import java.util.*
| import java.util.concurrent.*
| import java.util.function.*
| import java.util.prefs.*
| import java.util.regex.*
| import java.util.stream.*
Code-Sprache: Klartext (plaintext)
Fazit
Module Import Declarations können Java-Programme kürzer und leichter wartbar machen, indem nicht mehr einzelne Klassen und Pakete, sondern ganze Module importiert werden können. Andererseits können sie auch die ewige Diskussion darüber, ob man Klassen einzeln oder paketweise importieren soll, weiter ausufern lassen.
In Simple Source Files (bzw. implizit deklarierten Klassen) und JShell wird automatisch das java.base
-Modul importiert, so dass hier alle Klassen dieses Moduls ohne Imports direkt verwendet werden können.
Module Import Declarations befindet sich bis mindestens Java 24 noch in der Preview-Phase und müssen mit --enable-preview --source <Java-Version>
aktiviert werden.
Möchtest du immer auf dem neuesten Stand bleiben und informiert werden, sobald ein neuer Artikel auf HappyCoders.eu veröffentlicht werden? Dann klicke hier, um dich für den HappyCoders.eu-Newsletter anzumelden.