java module imports feature imagejava module imports feature image
HappyCoders Glasses

Module importieren in Java: Module Import Declarations

Sven Woltmann
Sven Woltmann
Aktualisiert: 4. Dezember 2024

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.javaCode-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¹.

¹ Zum aktuellen Zeitpunkt gelten diese Aussagen nur, wenn Preview-Features mit --enable-preview --source <Java-Version> aktiviert sind.

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.javaCode-Sprache: Klartext (plaintext)

... oder wie folgt mit Java 24:

javac --enable-preview --source 24 Ambiguous.javaCode-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 matchCode-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“.

Mehrdeutige Klassennamen kann es übrigens auch – wenn auch selten – geben, wenn du nur ein Modul importierst. Beispielsweise enthält das Modul java.desktop sowohl das Interface javax.swing.text.Element als auch die Klasse javax.swing.text.html.parser.Element.

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 Modul java.xml.
  • Das Modul java.sql exportiert die Pakete java.sql und javax.sql.
  • Das Modul java.xml exportiert die Pakete javax.xml und org.w3c.dom, jeweils mit zahlreichen Unterpaketen.

Die folgende Grafik zeigt die Module, ihre Abhängigkeiten und die exportierten Pakete:

java module import declarations

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.

Beachte, dass in Java 23 ein Import des Moduls java.se (ein Agregator-Modul mit Abhängigkeiten auf alle Module der Java Standard Edition „Java SE“) nicht die Klassen des java.base-Moduls verfügbar macht. Dies wird in Java 24 geändert.

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.baseCode-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.