Warum sollte man in Java *-Importe vermeiden und was ist das überhaupt?

5 Antworten

Ein Wildcard-Import erlaubt es dir, dem Compiler ein komplettes Package als Suchbereich für deine verwendeten Typen zu übergeben.

Beispiel:

import java.util.*;

public class Main {
  public static void main(String[] args) {
    List<Integer> numbers = new ArrayList<Integer>();
  }
}

In diesem Fall weiß der Compiler, dass er, um einen unbekannten Typ wie List aufzulösen, in dem Package java.util suchen soll. Bei einem spezifischen Import:

import java.util.List;

wäre der Suchaufwand natürlich geringer, doch der fällt zeitlich zu gering aus, als dass er beim Kompilieren tatsächlich auffallen würde.

Einige Vor- und Nachteile der beiden Wege werden aufgrund der Features von IDEs wie Eclipse oder IntelliJ schon lange ausgeglichen. Beispielsweise ist die Länge der Import-Liste nicht mehr so wichtig, da es Ein-/Ausklappmechanismen im Code Editor gibt. Die Herkunft und die Definition eines Typs kann man ebenfalls schnell herausfinden, da die IDE Typnamen in der Regel verlinkt, sodass man via Klick direkt zur Implementation springen kann.

Wobei ein konkreter Import dir natürlich immer noch am deutlichsten sagt, welcher konkrete Typ von Anfang an zur Nutzung angedacht war. Das ist gerade in den Fällen relevant, in denen es sonst zu Namenskonflikten kommt.

Auf dem herkömmlichen Weg würde man einfach einen zusätzlichen spezifischen Import ergänzen:

import java.awt.*;
import java.util.*;
import java.util.List; // use always List from java.util

und wenn man beide Namensvertreter nutzen will, müsste für einen eben der volle Packagename genutzt werden:

import java.awt.*;

// main:
List awtList;
java.util.List someList;

Problematischer wird es allerdings, wenn ein Namenskonflikt erst im Nachgang entsteht. Nimm nur einmal an, du hast eine Anwendung mit einer externen Bibliothek, dessen Package du via Wildcard einbindest:

import someExternPackage.*;
// other imports ...

Nun wird diese Bibliothek vielleicht irgendwann einmal aktualisiert (für ein Sicherheitspatch, o.ä.) und für diese Version ist eine Klasse hinzugekommen, die zufälligerweise denselben Namen wie eine von dir genutzte Klasse aus einem anderen eingebundenen Package hat. In dem Fall müssten die Import-Anweisungen nochmal angepasst werden, damit der Compiler wieder weiß, welchen Typ er denn nun auflösen soll.

Vor allem bei größeren Projekten kann das einen ziemlichen Aufwand bedeuten, den Anwendungscode wieder kompilierbar zu machen.

Was das überhaupt ist:

Z.B.

import java.util.*;

Also wenn du nicht nur einzelne Klassen importierst, sondern die ganze Bibliothek.

Warum man das nicht machen sollte? Gute Frage. Ich hab mir tatsächlich vor einiger Zeit angewöhnt, wenn möglich die *-Imports zu nehmen, damit der Header nicht mit zig import-Anweisungen vollgemüllt wird. Es kann ab und zu vorkommen, dass es in zwei Bibliotheken den gleichen Klassennamen gibt. Wenn du z.B. zusätzlich noch java.awt.* importieren würdest, hättest du zweimal List und müsstest halt beim instanziieren jedes mal angeben, welche List du jetzt meinst. Wenn ich mich noch richtig erinnere, gab es aber auch einen Workaround, womit du sagen kannst, dass du mit List immer java.util.List meinst. Ich glaube, man braucht dafür nur die entsprechende Klasse nochmal importieren.

Weil heutige Java-Programmierer zum Over-Engineering in Form von "Ravioli-Code" neigen:

Hast vielleicht selber schon in größeren Projekten gesehen, dass kleinere Probleme häufig mit Unmengen an Code beworfen werden, Interfaces und Concrete-Klassen getrennt werden und dabei viele Wrapperklassen und hohe Anzahl an Abstraktionsebenen gebaut werden.

Alles muss stets perfekt und in alle möglichen und unmöglichen Richtungen für Eventualitäten skalierbar sein, die niemals eintreten.

Da will man nichts, wirklich nichts dem Zufall überlassen.

Das ist ein Problem der Menschen, die Java benutzen.

Deshalb gilt "import java.util.*" als Pfui, weil es nicht in die Welt des klinisch reinen Codes passt.

Technisch könntest du lediglich auf dieses Problem stoßen: Du möchtest mit "List" nicht das List-Interface aus java.util.List sondern das gleichnamige GUI-Element java.awt.List.

Wenn du jetzt folgendes machst...

import java.util.*
import java.awt.List

...hast du ein Problem: Beide List-Klassen werden importiert und das gibt einen Konflikt, der den Code mehrdeutig macht und da wird der Compiler motzen.

Um das zu beheben, musst du den Wildcard-Import wegmachen und den Kram einzeln importieren. Das kann dann etwas nervig sein.

Ansonsten ist es technisch überhaupt kein Fehler, Wildcards-Imports zu verwenden, solange solche Konflikte nich passieren. Das ist gültiges Java.

Sofern du eine IDE wie IntelliJ oder Eclipse verwendest, musst du dir im Alltag keine Gedanken hierdrum machen: Du gibst irgendwo "List" ein und das wird rot hinterlegt, weil zunächst von einer eigenen Implementierung im selben Namespace ausgegangen wird, die es nicht gibt. Im Kontextmenü hast du die Wahl zwischen Import einer vorhandenen Implementierung und dem Erstellen einer eigenen Klasse namens "List".

Je nachdem, was du davon auswählst, passt die IDE die Imports automatisch an (und legt dir die Datei für die List-Klasse im eigenen Namespace an, falls du diese Option nimmst) und du musst dich darum überhaupt gar nicht kümmern.

Du musst also überhaupt gar keine import-Statements schreiben, weil die IDE das für dich erledigt.

Wenn der Import-Block durch häufiges Bearbeiten des Codes irgendwann mal kraus wird oder ungenutzte Imports haben, kann man in der IDE ein Funktion "optimize imports" auslösen, das die imports bereinigt, bsser anordnet und unbenutztes rausschmeißt.

Sollte oben beschriebener Konflikt doch mal auftreten und die IDE das nicht automatisch bereinigen, einfach den betroffenen Wildcard-Import rauslöschen und die nun rot markierten Klassen neu importieren.

Woher ich das weiß:Hobby – Ich beschäftige mich schon mehrere Jahre damit.

Ist eher ne Fausregel. Man soll halt nicht zuviel reinladen. Nur das was man wirklich braucht.

Gleiches gilt ja z.b. auch für globale Variablen oder so. Kann man machen aber SOLLTE man vermeiden wenn man kann.

Ansonsten bläht sich das alles eben endlos auf und du hast unter umständen extreme ladezeiten etc.

Vorallem in Java was ja von haus aus schon recht rechenintensiv ist.


regex9  24.07.2025, 14:25

Es ist wohl ein weitverbreitetes Missverständnis, doch Wildcard-Imports beeinflussen die Ladezeit der Anwendung nicht. Die JVM lädt immer nur die Klassen, die tatsächlich benutzt werden. Der Unterschied zwischen spezifischen Imports und Wildcard-Imports liegt im Kompilierungsprozess: Bei einem Wildcard-Import wird dem Compiler einfach gesagt, dass er beim Auflösen der Symbole/Typnamen das komplette Package für die Suche berücksichtigen muss. Der Overhead, der dadurch entsteht, ist jedoch zu minimal, als das er erwähnenswert wäre.

ichweisnetwas  24.07.2025, 14:31
@regex9

ging ja um normale imports.

Oder macht java das generell so?

Ich programmiere nicht in Java. Aber ich vermute mal bei normalen imports läd er einfach die ganze lib rein?

regex9  24.07.2025, 14:39
@ichweisnetwas

Ja, richtig. Das Java-Importsystem unterstützt zwei Schreibweisen. Wenn du z.B. die Klasse java.util.List importieren möchtest, kannst du explizit

import java.util.List

schreiben oder einen Wildcard-Import (auf den bezieht sich der FS) nutzen:

import java.util.*
ichweisnetwas  24.07.2025, 14:40
@regex9

kk. man lernt nie aus.

Aber gut ist wie gesagt nicht meine Sprache^^

Franky12345678  27.07.2025, 03:25

Das ist die Vergangenheit ;)

In der .jar ist erstmal nur das drin, was du selber an Code geschrieben hast bzw. was in dem src-Ordner deines Projektes drin ist.

Wenn du Zeugs importierst, wird lediglich festgelegt, welche Implementierung von "List" genutzt werden soll, wenn du nur "List" deklarierst. Du kannst auf Importe komplett verzichten, indem du die ausgeschriebene Variante nutzt:

Java.util.list myList = new Java.util.ArrayList();

Das kann die Java.util.List (=das List-Interface, was in 99% der Fälle gemeint ist) sein oder Java.awt.List (=das ist ein GUI-Element.)

Das ist nur eine Deklaration. Java.util.List wird nicht in die .jar reinkompiliert. Das wäre sinnlos (weil dieses Interface zur Standardlibrary von Java gehört, die in der JVM bereits vorhanden ist) und fatal (weil du die in der JVM enthaltene Implementierung overriden würdest, was fiese Nebenwirkungen haben kann).

Wenn du externe Libs verwendest, die nicht bei Java mit dabei sind, musst du zusehen, dass sie mit in die .jar reinkompiliert oder auf andere Weise zur Laufzeit verfügbar gemacht wird, weil es sonst beim Zugriff eine ClassNotFound-Exception gibt. Genau dafür gibt es Maven bzw. Gradle, was das sehr erleichtert, weil man nur festlegt, welche Libs man möchte. Diese Systeme sind in z.B. IntelliJ integriert.

Geht aber auch ohne. Dann muss man die .jars der externen Libs halt selber besorgen und reinfummeln. Ist aber mühsam und manchmal problematisch. Macht keiner mehr so wirklich.

Wegen langsamer Ausführung: Das war mal!

Java kann C-ähnliche Performance erreichen. In der .jar ist lediglich vorkompilierter Bytecode enthalten. Dieser wird zur Laufzeit vom JIT ein weiteres Mal in auf das System maßgeschneiderten Maschinencode kompiliert und auch optimiert, denn das System, auf dem er laufen soll, ist der JVM ja bekannt: jenes, auf dem die JVM selbst ausgeführt wird. Das Java-Programm ist dadurch beim Start etwas langsamer, wird aber immer schneller, je länger es läuft, weil während der Laufzeit weiter optimiert wird. Und der Code läuft direkt auf der CPU. Da ist kein Interpreter o.ä. zwischen. Die JVM managed diese Vorgänge lediglich.

Durch die Optimierung macht es keinen Sinn, eine for-Schleife durch eine while-Schleife zu ersetzen zwecks Performanceoptimierung. Sowas soll man in Java überhaupt nicht machen, weil es die Optimierung stört und dadurch kontraproduktiv sein kann.

In C sieht das etwas anders aus: Der Compiler kompiliert direkt in den Maschinencode des Zielsystems. Um unterschiedliche CPUs und Plattformen zu unterstützen, müssen entweder mehrere Executables erstellt werden oder eine, die mehrere CPU-Befehlssätze unterstützt oder nur einen grundlegenden Teil davon verwendet. Dadurch ist das Executable nicht immer so optimal wie der perfekt zugeschnittene Maschinencode aus der JVM. Dadurch kann das C-Programm in manchen Fällen langsamer ablaufen als ein gleichartiges in Java.

Allerdings hat Java den Nachteil, dass man nicht direkt auf die Speicheradressen zugreifen kann, wie bei C. Das ist wiederrum ein Performanceverlust, der die Vorteile des JIT-Kompilierens wieder kaputt macht.

Das tut sich am Ende daher nicht viel.

Imports mit stern erlauben dir mehrere klassen aufeinmal zu importieren.

Kann halt passieren dass man klassen importiert die man nicht benutzen will, was in manchen fällen dann dazu führen kann dass du zwei klassen mit dem gleichen namen importierst.

Wenn du z.B.

import java.sql.*;
import java.util.*;

schreibst, hast du zwei Date klassen...

Das ist nicht katastrophal, aber du bekommst dann halt einen fehler wenn du versuchst die Date klasse zu benutzen.

In der praxis ist das oft auch kein problem.