Importdeklarationen in Java
Typimportdeklarationen
Damit der maximal explizierte Name eines Eintrags nicht immer ausgeschrieben werden muß, gibt es in Java die Möglichkeit, einfache Typnamen aus Paketen in eine Übersetzungseinheit einzuführen (zu „importieren“).
Die Bekanntgabe eines Namens für einen Bereich des Quelltextes wird beim Programmieren auch als Deklaration des Namens bezeichnet, so daß die Importdeklaration ihren Namen zu Recht trägt: Sie deklariert einen Namen für den Bereich der sie enthaltenden Quelldatei.
Importdeklarationen müssen (JLS7 7.3) am Anfang der Quelldatei stehen. Eine Importdeklaration beginnt mit dem Schlüsselwort »import«, ihm folgt ein vollständig qualifizierter Name und ein Semikolon. Dadurch wird dann der eigentliche Name (der einfache Name am Ende) des vollständig qualifizierten Namens als Abkürzung für den vollständig qualifizierten Namen deklariert.
Die folgende Importdeklaration deklariert beispielsweise den Typnamen »Integer« für die Verwendung in der Übersetzungseinheit »Main.java«. Statt »java.lang.Integer« braucht dann nach der Importdeklaration nur noch »Integer« geschrieben werden. Die Importdeklaration definiert »Integer« also als Abkürzung für »java.lang.Integer«.
Main.java
import java.lang.Integer;
import java.lang.Double; public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( Integer.MAX_VALUE );
java.lang.System.out.println( Double.MAX_VALUE ); }}java.lang.System.out
2147483647
1.7976931348623157E308
⚠ Eine Importdeklaration ist niemals nötig, um eine Klasse zu verwenden. Sie stellt nur eine Möglichkeit zur Abkürzung folgender Programmteile dar. Insbesondere falsch ist die wiederholt gehörte Vorstellung, daß mit »import« „Bibliotheken importiert“ werden.
Syntax der Typimportdeklaration
- Typimportdeklaration (Syntaxdiagramm) ::=
.------. .---------. .-.
----->( import )----->| Typname |----->( ; )----->
'------' '---------' '-'
»import« ist ein Schlüsselwort.
Das Semikolon am Ende der Importdeklaration ist eine lexikalische Einheit.
Bedarfsimportdeklarationen
Falls alle Typen aus einem Paket mit ihren einfachen Namen erreichbar sein sollen, so kann anstelle eines einfachen Typnamens in der Importdeklaration ein Stern »*« geschrieben werden.
Main.java
import java.lang.*; public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( Integer.MAX_VALUE );
java.lang.System.out.println( Double.MAX_VALUE ); }}java.lang.System.out
2147483647
1.7976931348623157E308
Mit einer Bedarfsimportdeklaration wie »import java.lang.*;« wird dem Compiler erlaubt, die Typen des angegebenen Pakets nach Bedarf zu importieren. Dies bedeutet, daß der Name nur dann importiert wird, wenn es noch keinen passenden Namen im aktuellen Gültigkeitsbereich gibt. Beispielsweise würde in »new Main()« innerhalb einer Deklaration der Klasse »Main« der Name »Main« für die gerade deklarierte Klasse »Main« stehen. Daher wird dann nicht versucht, den Typnamen »Main« zu importieren. Würde man statt dessen eine Typimportdeklaration »import java.lang.Main;« schreiben, so würde dies zu einer Fehlermeldung führen, da es eine Klasse »java.lang.Main« nicht gibt. Wenn es aber eine Klasse »java.lang.Main« geben würde, würde »Main« dann für diese Klasse stehen und könnte danach nicht mehr mit »class Main« deklariert werden. Bei Verwendung von »new Integer()« nach der oben angegebenen Bedarfsimportdeklaration wird »Integer« in der Regel nicht im aktuellen Gültigkeitsbereich gefunden und daher dann importiert.
Wir unterscheiden nun also zwischen einer Typimportdeklaration ohne Stern (JLS7:7.5.1:“single-type-import declaration”) und einer Bedarfsimportdeklaration mit einem Stern (JLS7:7.5.2:“type-import-on-demand declaration”).
Syntax der Bedarfsimportdeklaration
- Bedarfsimportdeklaration (Syntaxdiagramm, Syntax vereinfacht) ::=
.------. .-----------. .-. .-. .-.
----->( import )----->| Paketname |----->( . )----->( * )----->( ; )----->
'------' '-----------' '-' '-' '-'
Es ist nicht möglich, etwa durch »import java.*;«, mehrere Pakete mit einem Stern »*« zu importieren, die alle mit »java.« beginnen. Der Stern »*« kann nur für Typen stehen, nicht für Pakete oder Teile von Paketnamen.
Mehrfache Bedarfsimportdeklarationen
Bedarfsimportdeklarationen können allerdings die Lesbarkeit von Quelltext vermindern, weil es bei ihrer Verwendung nicht mehr an den Importdeklarationszeilen erkennbar ist, welcher Typ zu welchem Paket gehört. Das folgende Programm zeigt Bedarfsimportdeklarationen für Typen aus zwei Paketen. Ein Leser kann nun dem Quelltext nicht mehr entnehmen, aus welchem Paket die einzelnen Typen (»String« oder »Integer«) stammen.
Main.java
import java.lang.*;
import java.util.*; public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( Integer.MAX_VALUE ); }}java.lang.System.out
2147483647
Mehrdeutigkeiten bei Bedarfsimportdeklarationen
Falls die Typen mehrerer Pakete mit Bedarfsimportdeklarationen importiert wurden, ist der einfache Name eines Typs nicht mehr eindeutig, falls er in mehreren dieser Pakete vorkommt. Dann muß der Paketname weiterhin verwendet werden, um sich auf einen Typ in einem bestimmten Paket zu beziehen. (Einen Typ »List« gibt es beispielsweise sowohl in dem Paket »java.awt« als auch in dem Paket »java.util«.) Selbst nachdem die Importdeklaration »import java.awt.*« und die Importdeklaration »import java.util.*« verwendet wurde, muß also weiterhin der vollständig qualifizierte Name »java.awt.List« oder der vollständig qualifizierte Name »java.util.List« verwendet werden, um sich eindeutig auf einen dieser beiden Typen zu beziehen, da der Bezeichner »List« alleine dann nicht eindeutig wäre.
Main.java
import java.awt.*; public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( new List() ); }}java.lang.System.out
java.awt.List[list0,0,0,0x0,invalid,selected=null]
Main.java
import java.util.*;
import java.awt.*; public final class Main
{ public static void main( final java.lang.String[] args )
{ final List list = null; }}- Konsole
Main.java:5: error: reference to List is ambiguous
{ final List list = null; }}
^
both class java.awt.List in java.awt and interface java.util.List in java.util match
1 error
Selbst wenn ein Typname eindeutig ist, weil er nur in einem der verwendeten Pakete vorkommt, kann es passieren, daß sich dies in einer späteren Version der Programmiersprache ändert, weil die Pakete erweitert wurden. Dann könnte es sein, daß ein Programm plötzlich nicht mehr übersetzt werden kann und überarbeitet werden muß, wenn es solche Bedarfsimportdeklarationen verwendet. Die Verwendbarkeit des Quelltextes ist dadurch als potentiell eingeschränkt.
Ein Autor kennt auch nicht immer alle Namen eines importierten Namensraums, so daß es durch den Import von Namen, derer sich der Programmierer nicht bewußt ist, zu Problemen kommen kann.
Wenn kein Bedarf an einem Import besteht, wird bei einem Bedarfsimports nicht importiert. Wenn also schon eine Klasse »String« bekannt ist, dann wird »java.lang.String« nach »import java.lang.*;« nicht importiert. Dies kann bedeuten, daß ein Programm unbemerkt sein Verhalten ändern könnte oder zu Fehlermeldungen führen könnte, wenn (versehentlich) eine andere Klasse »String« zugänglich gemacht wurde. Bei Verwendung einer Typimportdeklaration ohne Stern wäre dies aber nicht der Fall, da nach »import java.lang.String;« der einfache Name »String« immer »java.lang.String« bedeutet. Nach einer Bedarfsimportdeklaration ist es ohne weitere Nachforschungen also nicht ersichtlich, auf welche Klasse sich ein einfacher Klassenname bezieht und das Hinzufügen einer weiteren Klasse kann die Bedeutung oder Verwendbarkeit einer Quelldatei verändern.
Da Bedarfsimportdeklarationen zu den beschriebenen Problemen mit der Lesbarkeit, der Verwendbarkeit und der Interpretation von Quelltext führen können, sollten sie in Programmen, die wartbar sein sollen, vermieden werden.
Kombinierte Importdeklarationen
Das folgende Programmbeispiel zeigt, wie mit einer Typimportdeklaration klargestellt wird, welche Bedeutung der unqualifizierte Bezeichner »List« haben soll. Da der Name »List« aus »java.util.List« mit der Typimportdeklaration »import java.util.List;« auf jeden Fall importiert wird, werden die Bedarfsimportdeklarationen zur Auflösung dieses Namens nicht herangezogen.
In demselben Programm könnte dann auch noch »java.awt.List« als voll qualifizierter Name verwendet werden. Obwohl es so möglich ist, Mehrdeutigkeiten aufzulösen, wird von der Verwendung von Bedarfsimportdeklarationen für große Programme, die lange wartbar sein sollen, doch weiterhin abgeraten.
Main.java
import java.util.*;
import java.awt.*;
import java.awt.List; public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( new List() ); }}java.lang.System.out
java.awt.List[list0,0,0,0x0,invalid,selected=null]
Vorgegebene Importe
Weiter oben wurden die Typen des Pakets »java.lang« durch eine Bedarfsimportdeklaration mit einem Stern zum Bedarfsimport bereitgestellt. Speziell diese Importdeklaration »import java.lang.*« ist aber als niemals nötig, weil sie stets bereits vorgegeben ist. Daher kann diese Übersetzungseinheit auch kürzer geschrieben werden.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( Integer.MAX_VALUE ); }}java.lang.System.out
2147483647
Da es sich bei diesem vorgegebenen Import um einen Bedarfsimport handelt, besteht allerdings das schon angesprochene Problem mit der Mehrdeutigkeit der Bedeutung eines einfachen Namens: Der einfache Name »String« muß nicht »java.lang.String« bedeuten; er kann sich auch auf eine andere Klasse mit dem eigentlichen Namen »String« beziehen; welche Klasse dies ist kann außerhalb des Programms festgelegt werden; dies gefährdet die Sicherheit, Eindeutigkeit und Funktionsfähigkeit eines Programms. Wenn ein sicherer Bezug auf »java.lang.String« hergestellt werden soll, ist entweder eine Typimportdeklaration »import java.lang.String;« ohne Stern zu verwenden oder der maximal qualifizierte Klassenname »java.lang.String«.
Vorteile von Typimportdeklarationen
Durch die Auflistung von Typimportdeklarationen am Anfang eines Programms wird schnell erkennbar, welche Typen ein Programm benötigt.
Es ist möglich, Typnamen durch eine einzige Änderung an einer Stelle (nämlich der Typimportdeklaration) aus einem anderen Paket zu importieren.
Nachteile von Typimportdeklarationen
Beim Übersetzen eines Programms wird im allgemeinen nicht geprüft, ob die angegebenen Typen auch wirklich benötigt werden, so daß die Auflistung von Typimportdeklaration am Anfang eines Quelltextes über die tatsächlich verwendeten Typen täuschen könnte. Wenn ein Name aus einem Programm entfernt wird, aber vergessen wird, die zugehörige Importdeklaration zu entfernen, dann enthält die Auflistung der Typimportdeklarationen unnötige Bestandteile.
Eine durchgehende Verwendung maximal qualifizierter Namen erlaubt es dem Leser eines Programmstücks, sofort zu erkennen, zu welchem Paket ein Typ gehört, ohne daß erst oben in einer Liste von Importdeklarationen nachgesehen werden muß. Wenn Teile des Quelltextes, der Typimportdeklarationen verwendet, herauskopiert werden, sind diese Teile ohne die Typimportdeklarationen nicht immer verständlich (es ist nicht immer klar, welche Typen gemeint sind).
Programmstücke mit voll qualifizierten Namen können auf einfache Weise zwischen verschiedenen Quelldateien ausgetauscht werden, ohne daß sie ihre Bedeutung möglicherweise ändern, weil in der anderen Quelldatei andere Importdeklarationen stehen, beziehungsweise, ohne daß es nötig wird dann auch noch die zugehörigen Importdeklarationen zu finden und ebenfalls zu kopieren.
Statische Importe
Auch statische Einträge (wie statische Methoden) einer Klasse können importiert werden.
Das folgende Programmbeispiel zeigt den Import der Methode "random" der Klasse "java.lang.Math". Danach kann im Quelltext dann einfach "random" verwendet werden, um diese Methode anzugeben. Es wird damit auch möglich, den Eintrag "out" der Klasse "java.lang.System" einfach nur mit "out" zu bezeichnen. Für solche einen „statischen Import“ muß dem Schlüsselwort "import" noch das Schlüsselwort "static" direkt folgen.
StaticImport.java
import static java.lang.Math.random;
import static java.lang.System.out; public class StaticImport
{ public static void main( String[] args )
{ out.println( random() ); }}
Durch die Verwendung eines Sternchens können auch alle Einträge einer Klasse importiert werden.
StaticImportAll.java
import static java.lang.Math.*;
import static java.lang.System.*; public class StaticImportAll
{ public static void main( String[] args )
{ out.println( random() ); }}
Die Verwendung der Sternchen führt aber wieder zu dem Problem der Verschlechterung der Lesbarkeit, da der Name "out" und der Name "random" nun nicht mehr durch Lesen des Quelltextes einer Klasse zugeordnet werden können. Daher sollten die Sternchen vermieden werden.
Übungsaufgaben
/ Programm beurteilen
Sagen Sie, ohne das Programm übersetzen zu lassen, voraus, ob beim Versuch. das folgende Programm zu übersetzen, eine Fehlermeldung ausgegeben werden wird.
Main.java
import java.util.List; public final class Main
{ public static void main( final java.lang.String[] args )
{ new java.awt.List(); }}
/ Namen finden
Ermitteln Sie, ohne das Programm zu starten, den vollständig qualifizierten Namen der in dem folgenden Programm verwendeten Klasse »Adler32«.
Main.java
import java.rmi.activation.*;
import java.nio.file.attribute.*;
import java.util.zip.*; public final class Main
{ public static void main( final java.lang.String[] args )
{ new Adler32(); }}