Computerhaus Quickborn

Tutorial: Generics in Java nutzen​

: Lesen Sie, wie Generics in Java optimale Wirkung entfalten.THP Creative | shutterstock.com Mit Version 5 von Java wurden Generics eingeführt. Sie verbessern die Typsicherheit des Codes und machen ihn leichter lesbar. Das trägt auch dazu bei, Fehler zur Laufzeit wie ClassCastException zu verhindern. Dieser tritt auf, wenn Objekte in inkompatible Typen umgewandelt werden sollen. Dieses Tutorial beschäftigt sich eingehend mit dem Thema Generics in Java und vermittelt Ihnen: welchen Mehrwert Generics bringen, wie Sie diese im Java Collection Framework nutzen, wie Sie Generic Types in Java konkret einsetzen (inklusive Beispielen), und   was mit Blick auf Raw Types und Generics zu beachten ist. Was Generics bringen Generics werden gängigerweise innerhalb des Java Collection Frameworks genutzt mit: java.util.List, java.util.Set und java.util.Map. Darüber hinaus sind sie auch in anderen Teilen von Java zu finden, beispielsweise in: java.lang.Class, java.lang.Comparable und java.lang.ThreadLocal. Bevor Generics eingeführt wurden, mangelte es Java-Code regelmäßig an Type Safety. Hier ein Beispiel für Java-Code im Prä-Generics-Zeitalter: List integerList = new ArrayList(); integerList.add(1); integerList.add(2); integerList.add(3); for (Object element : integerList) { Integer num = (Integer) element; // Cast is necessary System.out.println(num); } Mit diesem Code sollen Integer-Objekte in einer Liste gespeichert werden. Dabei können Sie jederzeit weitere Typen hinzufügen, etwa einen String: integerList.add("Hello"); Würden Sie versuchen, den String in einen Integer umzuwandeln, würde dieser Code zur Laufzeit eine ClassCastException verursachen. Mit Hilfe von Generics lässt sich dieses Problem lösen und der Fehler zur Laufzeit vermeiden. Dazu nutzen wir sie, um die Typen von Objekten zu spezifizieren, die eine Liste enthalten darf. In diesem Fall können wir auf einen Class Cast verzichten, was dazu führt, dass der Code sicherer wird und leichter zu verstehen ist:    List integerList = new ArrayList(); integerList.add(1); integerList.add(2); integerList.add(3); for (Integer num : integerList) { System.out.println(num); } List bedeutet „eine Liste von Integer-Objekten“. Auf Grundlage dieser Anweisung stellt der Compiler sicher, dass ausschließlich Integer-Objekte zur Liste hinzugefügt werden können. Generics im Java Collections Framework Um eine Typprüfung während der Kompilierung zu ermöglichen und die Notwendigkeit einer expliziten Typumwandlung zu beseitigen, sind Generics im Java Collections Framework integriert. Wenn Sie eine Collection mit Generics nutzen, spezifizieren Sie den Typ der Elemente, die erstere enthalten soll. Der Java-Compiler nutzt diese Spezifikation, um sicherzustellen, dass nicht versehentlich ein inkompatibles Objekt in die Collection einfließt. Das reduziert Fehler und macht den Code besser lesbar. Um zu veranschaulichen, wie Generics im Java Collections Framework angewendet werden, werfen wir im Folgenden einen Blick auf drei Beispiele. Namenskonventionen für GenericsGeneric Types können Sie in jeder beliebigen Klasse deklarieren und dafür jeden beliebigen Namen verwenden. Vorzugsweise sollten Sie sich dabei aber auf eine Namenskonvention verlassen. In Java bestehen die Namen von Typparametern in der Regel aus einzelnen Großbuchstaben. Dabei steht:E für Element,K für Key,V für Value, undT für Type.Insofern sollten Sie davon absehen, bedeutungslose Benennungen wie „X“, „Y“ oder „Z“ zu verwenden. List und ArrayList Im zuletzt angeführten Code-Beispiel haben wir bereits eine einfachere Möglichkeit gesehen, um ArrayList zu nutzen. Dieses Konzept sehen wir uns nun genauer an, indem wir einen Blick darauf werfen, wie das List-Interface deklariert wird:   public interface List extends SequencedCollection { … } In diesem Code deklarieren wir unsere generische Variable als „E“. Diese kann durch jeden beliebigen Objekttyp ersetzt werden. Sehen wir uns nun an, wie die Variable E durch den gewünschten Typ für unsere List ersetzt wird. Im folgenden Code ersetzen wir die Variable durch : List list = new ArrayList(); list.add("Java"); list.add("Challengers"); // list.add(1); // This line would cause a compile-time error. Hier spezifiziert List, dass die Liste lediglich String-Objekte enthalten kann. Eine Integer hinzuzufügen, führt zu einem Kompilierungsfehler, wie Sie in der letzten Code-Zeile sehen. Set und HashSet Das Set-Interface ähnelt dem von List: public interface Set extends Collection { … } Wir ersetzen zudem durch , so dass wir nur einen Double-Wert in das entsprechende Set einfügen können: Set doubles = new HashSet(); doubles.add(1.5); doubles.add(2.5); // doubles.add("three"); // Compile-time error double sum = 0.0; for (double d : doubles) { sum += d; } Set gewährleistet, dass nur Double-Werte hinzugefügt werden können. Das verhindert Laufzeitfehler, die durch nicht korrektes Casting entstehen können. Map und HashMap Wir können so viele generische Typen deklarieren, wie wir möchten. Im folgenden ein Key-Value-Datenstrukturbeispiel in Form einer Map: public interface Map { … } Nun ersetzen wir den Key-Type K durch String und den Value-Type V durch Integer: Map map = new HashMap(); map.put("Duke", 30); map.put("Juggy", 25); // map.put(1, 100); // This line would cause a compile-time error Dieses Beispiel zeigt eine HashMap, die String-Keys Integer-Values zuordnet. Einen Key vom Typ Integer hinzuzufügen, ist nicht erlaubt und führt zu einem Kompilierungsfehler. Generic Types in Java nutzen – Beispiele Im nächsten Schritt sehen wir uns einige Beispiele an, um besser zu durchdringen, wie Generic Types in Java deklariert und verwendet werden. Generics mit beliebigen Objekten nutzen Generic Types lassen sich jeder Klasse deklarieren – es muss sich nicht um einen Collection-Typ handeln. Im folgenden Code-Beispiel deklarieren wir den Generic Type E, um jedes Element innerhalb der Box-Klasse zu manipulieren. Zu beachten ist dabei, dass der Generic Type nach dem Klassennamen deklariert wird. Erst dann können wir ihn als Attribut, Konstruktor, Methodenparameter und Methodenrückgabetyp nutzen: // Define a generic class Box with a generic type parameter E public class Box { // Variable to hold an object of type E private E content; public Box(E content) { this.content = content; } public E getContent() { return content; } public void setContent(E content) { this.content = content; } public static void main(String[] args) { // Create a Box to hold an Integer Box integerBox = new Box(123); System.out.println("Integer Box contains: " + integerBox.getContent()); // Create a Box to hold a String Box stringBox = new Box("Hello World"); stringBox.setContent("Java Challengers"); System.out.println("String Box contains: " + stringBox.getContent()); } } Der Output dieses Beispiels lautet: Integer Box contains: 123 String Box contains: Java Challengers Zu beachten ist dabei: Die Box-Klasse verwendet den Typparameter E als Platzhalter für das Objekt, das die Box enthalten wird. Dadurch lässt sich Box mit jedem Objekttyp verwenden. Der Konstruktor initialisiert eine neue Instanz der Box-Klasse mit dem bereitgestellten Inhalt. Der E-Typ stellt dabei sicher, dass der Konstruktor jeden Objekttyp akzeptieren kann, der bei der Erstellung der Instanz definiert wurde – und gewährleistet so die Typsicherheit. getContent gibt den aktuellen Inhalt von Box zurück. Durch die Rückgabe von E wird sichergestellt, dass er dem Generic Type entspricht, der bei der Erstellung der Instanz angegeben wurde –  und der richtige Typ bereitgestellt wird. Casting ist dazu nicht erforderlich. setContent aktualisiert den Inhalt von Box. Weil E als Parameter verwendet wird, ist sichergestellt, dass nur ein Objekt des richtigen Typs als neuer Inhalt festgelegt werden kann. Das gewährleistet umfassende Type Safety, solange die Instanz genutzt wird. In der main-Methode werden zwei Box-Objekte erstellt: integerBox enthält eine Integer, stringBox einen String. Jede Box-Instanz arbeitet mit ihrem spezifischen Datentyp. Dieses Beispiel veranschaulicht, wie Generics in Java grundlegend implementiert und verwendet werden. Es unterstreicht, wie Objekte beliebigen Typs „type-safe“ erstellt und bearbeitet werden können. Generics mit unterschiedlichen Datentypen nutzen Sie können so viele Typen als generisch deklarieren, wie Sie möchten. In der folgenden Pair-Klasse können wir so die generischen Werte hinzufügen: class Pair { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } } public class GenericsDemo { public static void main(String[] args) { Pair person = new Pair("Duke", 30); System.out.println("Name: " + person.getKey()); System.out.println("Age: " + person.getValue()); person.setValue(31); System.out.println("Updated Age: " + person.getValue()); } } Dieser Code erzeugt folgenden Output: Name: Duke Age: 30 Updated Age: 31 Zu beachten ist dabei: Die generische Klasse Pair weist zwei Typparameter auf, wodurch sie für jeden Datentyp vielseitig einsetzbar ist. Konstruktoren und Methoden in der Pair-Klasse verwenden diese Typparameter, was striktes Type Checking ermöglicht. Ein Pair-Objekt wird erstellt, um einen String (den Namen einer Person) und einen Integer (ihr Alter) zu speichern. Accessors (getKey und getValue) sowie Mutators (setKey und setValue) manipulieren und rufen die Pair-Daten ab. Die Pair-Klasse kann verwandte Informationen speichern und managen und ist dabei nicht an bestimmte Datentypen gebunden. Dieses Beispiel demonstriert, wie Generics zu wiederverwendbaren, typsicheren Komponenten mit unterschiedlichen Datentypen beitragen – und zur Wiederverwendbarkeit und besseren Wartbarkeit von Code. Generic Types innerhalb einer Methode deklarieren Sie können Generic Types auch direkt innerhalb einer Methode deklarieren. Das bewerkstelligen Sie, indem Sie den Generic Type vor dem Rückgabetyp der Methodensignatur deklarieren: public class GenericMethodDemo { // Declare generic type and print elements with the chosen type public static void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); } public static void main(String[] args) { // Using the generic method with an Integer array Integer[] intArray = {1, 2, 3, 4}; printArray(intArray); // Using the generic method with a String array String[] stringArray = {"Java", "Challengers"}; printArray(stringArray); } } Der Output sieht wie folgt aus: 1 2 3 4 Java Challengers Raw Types vs. Generics Bei einem Raw Type handelt es sich im Grunde um den Namen einer Generic Class (beziehungsweise eines Generic Interface), jedoch ohne Typargumente. Diese waren vor der Generics-Einführung in Java 5 weit verbreitet. Heutzutage kommen Sie vor allem zum Einsatz, um die Kompatibilität mit Legacy-Code oder die Interoperabilität mit nicht-generischen APIs zu gewährleisten. Es empfiehlt sich also, zu wissen, wie Raw Types im Code zu erkennen und zu verwenden sind. Ein gängiges Beispiel für den Raw-Type-Einsatz ist, eine List ohne Typparameter zu deklarieren: List rawList = new ArrayList(); In diesem Beispiel deklariert List rawList eine Liste ohne generischen Typparameter. rawList kann jeden Objekttyp enthalten, einschließlich Integer, String, Double und so weiter. Weil kein Typ spezifiziert ist, wird im Rahmen der Kompilierung nicht überprüft, welche Objekttypen der Liste hinzugefügt werden. Compiler-Warnung bei Raw Types Der Java-Compiler sendet Warnmeldungen bezüglich der Verwendung von Raw Types in Java. Diese werden generiert, um Entwickler auf potenzielle Risiken im Zusammenhang mit der Typsicherheit hinzuweisen, wenn Raw- statt Generic Types verwendet werden. Wie Sie Generics nutzen, überprüft der Compiler die Objekttypen in den Collections (wie List und Set), die Rückgabetypen von Methoden und die Parameter. Das geschieht, um sicherzustellen, dass diese mit den deklarierten Generic Types übereinstimmen. Das verhindert gängige Bugs wie ClassCastException zur Laufzeit. Wenn Sie einen Raw Type nutzen, kann der Compiler diese Prüfungen nicht durchführen. Das liegt daran, dass Raw Types nicht den Typ der Objekte spezifizieren, die sie enthalten. Infolgedessen gibt der Compiler Warnmeldungen aus, um darauf aufmerksam zu machen, dass die Type-Safety-Mechanismen der Generics umgangen werden. Beispiel für eine Compiler-Warnung Im Folgenden ein simples Beispiel, das veranschaulicht, wie die Compiler-Warnung aussieht, wenn Raw Types zum Einsatz kommen: List list = new ArrayList(); // Warning: Raw use of parameterized class 'List' list.add("hello"); list.add(1); Wenn Sie diesen Code kompilieren, gibt der Java-Compiler in der Regel eine Meldung aus, wie beispeislweise: Note: SomeFile.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. Wenn Sie mit der Flag -Xlint:unchecked kompilieren, erhalten Sie detailliertere Informationen darüber, wo und warum diese Warnmeldung generiert wurde: warning: [unchecked] unchecked call to add(E) as a member of the raw type List list.add("hello"); ^ where E is a type-variable: E extends Object declared in interface List Wenn Sie sicher sind, dass es keine Risiken birgt, Raw Types in Ihrem Code zu verwenden (oder Sie mit Legacy-Code arbeiten, der sich nicht so einfach für die Nutzung von Generics umgestalten lässt), können Sie @SuppressWarnings(„unchecked“) nutzen, um diese auszublenden. Das sollten Sie mit Bedacht nutzen, schließlich könnten dabei andere, echte Probleme unter den Tisch fallen. Raw Types verwenden – die Folgen Zwar sind Raw Types mit Blick auf die Abwärtskompatibilität nützlich. Allerdings hat das auch ganz wesentliche Nachteile. Insbesondere: geht die Type Safety verloren. erhöhen sich die Wartungskosten. Ein Beispiel für ein Type-Safety-Problem: Wenn Sie List (Raw Type) anstelle des generischen List verwenden, erlaubt es der Compiler, beliebige Objekttypen zur Liste hinzuzufügen – nicht nur Strings. Wenn Sie ein Element aus der Liste abrufen und versuchen, es in einen String umzuwandeln, dieses Element aber tatsächlich von einem anderen Typ ist, kann das in Laufzeitfehlern resultieren. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox! 

Tutorial: Generics in Java nutzen​ : Lesen Sie, wie Generics in Java optimale Wirkung entfalten.THP Creative | shutterstock.com Mit Version 5 von Java wurden Generics eingeführt. Sie verbessern die Typsicherheit des Codes und machen ihn leichter lesbar. Das trägt auch dazu bei, Fehler zur Laufzeit wie ClassCastException zu verhindern. Dieser tritt auf, wenn Objekte in inkompatible Typen umgewandelt werden sollen. Dieses Tutorial beschäftigt sich eingehend mit dem Thema Generics in Java und vermittelt Ihnen: welchen Mehrwert Generics bringen, wie Sie diese im Java Collection Framework nutzen, wie Sie Generic Types in Java konkret einsetzen (inklusive Beispielen), und   was mit Blick auf Raw Types und Generics zu beachten ist. Was Generics bringen Generics werden gängigerweise innerhalb des Java Collection Frameworks genutzt mit: java.util.List, java.util.Set und java.util.Map. Darüber hinaus sind sie auch in anderen Teilen von Java zu finden, beispielsweise in: java.lang.Class, java.lang.Comparable und java.lang.ThreadLocal. Bevor Generics eingeführt wurden, mangelte es Java-Code regelmäßig an Type Safety. Hier ein Beispiel für Java-Code im Prä-Generics-Zeitalter: List integerList = new ArrayList(); integerList.add(1); integerList.add(2); integerList.add(3); for (Object element : integerList) { Integer num = (Integer) element; // Cast is necessary System.out.println(num); } Mit diesem Code sollen Integer-Objekte in einer Liste gespeichert werden. Dabei können Sie jederzeit weitere Typen hinzufügen, etwa einen String: integerList.add("Hello"); Würden Sie versuchen, den String in einen Integer umzuwandeln, würde dieser Code zur Laufzeit eine ClassCastException verursachen. Mit Hilfe von Generics lässt sich dieses Problem lösen und der Fehler zur Laufzeit vermeiden. Dazu nutzen wir sie, um die Typen von Objekten zu spezifizieren, die eine Liste enthalten darf. In diesem Fall können wir auf einen Class Cast verzichten, was dazu führt, dass der Code sicherer wird und leichter zu verstehen ist:    List integerList = new ArrayList(); integerList.add(1); integerList.add(2); integerList.add(3); for (Integer num : integerList) { System.out.println(num); } List bedeutet „eine Liste von Integer-Objekten“. Auf Grundlage dieser Anweisung stellt der Compiler sicher, dass ausschließlich Integer-Objekte zur Liste hinzugefügt werden können. Generics im Java Collections Framework Um eine Typprüfung während der Kompilierung zu ermöglichen und die Notwendigkeit einer expliziten Typumwandlung zu beseitigen, sind Generics im Java Collections Framework integriert. Wenn Sie eine Collection mit Generics nutzen, spezifizieren Sie den Typ der Elemente, die erstere enthalten soll. Der Java-Compiler nutzt diese Spezifikation, um sicherzustellen, dass nicht versehentlich ein inkompatibles Objekt in die Collection einfließt. Das reduziert Fehler und macht den Code besser lesbar. Um zu veranschaulichen, wie Generics im Java Collections Framework angewendet werden, werfen wir im Folgenden einen Blick auf drei Beispiele. Namenskonventionen für GenericsGeneric Types können Sie in jeder beliebigen Klasse deklarieren und dafür jeden beliebigen Namen verwenden. Vorzugsweise sollten Sie sich dabei aber auf eine Namenskonvention verlassen. In Java bestehen die Namen von Typparametern in der Regel aus einzelnen Großbuchstaben. Dabei steht:E für Element,K für Key,V für Value, undT für Type.Insofern sollten Sie davon absehen, bedeutungslose Benennungen wie „X“, „Y“ oder „Z“ zu verwenden. List und ArrayList Im zuletzt angeführten Code-Beispiel haben wir bereits eine einfachere Möglichkeit gesehen, um ArrayList zu nutzen. Dieses Konzept sehen wir uns nun genauer an, indem wir einen Blick darauf werfen, wie das List-Interface deklariert wird:   public interface List extends SequencedCollection { … } In diesem Code deklarieren wir unsere generische Variable als „E“. Diese kann durch jeden beliebigen Objekttyp ersetzt werden. Sehen wir uns nun an, wie die Variable E durch den gewünschten Typ für unsere List ersetzt wird. Im folgenden Code ersetzen wir die Variable durch : List list = new ArrayList(); list.add("Java"); list.add("Challengers"); // list.add(1); // This line would cause a compile-time error. Hier spezifiziert List, dass die Liste lediglich String-Objekte enthalten kann. Eine Integer hinzuzufügen, führt zu einem Kompilierungsfehler, wie Sie in der letzten Code-Zeile sehen. Set und HashSet Das Set-Interface ähnelt dem von List: public interface Set extends Collection { … } Wir ersetzen zudem durch , so dass wir nur einen Double-Wert in das entsprechende Set einfügen können: Set doubles = new HashSet(); doubles.add(1.5); doubles.add(2.5); // doubles.add("three"); // Compile-time error double sum = 0.0; for (double d : doubles) { sum += d; } Set gewährleistet, dass nur Double-Werte hinzugefügt werden können. Das verhindert Laufzeitfehler, die durch nicht korrektes Casting entstehen können. Map und HashMap Wir können so viele generische Typen deklarieren, wie wir möchten. Im folgenden ein Key-Value-Datenstrukturbeispiel in Form einer Map: public interface Map { … } Nun ersetzen wir den Key-Type K durch String und den Value-Type V durch Integer: Map map = new HashMap(); map.put("Duke", 30); map.put("Juggy", 25); // map.put(1, 100); // This line would cause a compile-time error Dieses Beispiel zeigt eine HashMap, die String-Keys Integer-Values zuordnet. Einen Key vom Typ Integer hinzuzufügen, ist nicht erlaubt und führt zu einem Kompilierungsfehler. Generic Types in Java nutzen – Beispiele Im nächsten Schritt sehen wir uns einige Beispiele an, um besser zu durchdringen, wie Generic Types in Java deklariert und verwendet werden. Generics mit beliebigen Objekten nutzen Generic Types lassen sich jeder Klasse deklarieren – es muss sich nicht um einen Collection-Typ handeln. Im folgenden Code-Beispiel deklarieren wir den Generic Type E, um jedes Element innerhalb der Box-Klasse zu manipulieren. Zu beachten ist dabei, dass der Generic Type nach dem Klassennamen deklariert wird. Erst dann können wir ihn als Attribut, Konstruktor, Methodenparameter und Methodenrückgabetyp nutzen: // Define a generic class Box with a generic type parameter E public class Box { // Variable to hold an object of type E private E content; public Box(E content) { this.content = content; } public E getContent() { return content; } public void setContent(E content) { this.content = content; } public static void main(String[] args) { // Create a Box to hold an Integer Box integerBox = new Box(123); System.out.println("Integer Box contains: " + integerBox.getContent()); // Create a Box to hold a String Box stringBox = new Box("Hello World"); stringBox.setContent("Java Challengers"); System.out.println("String Box contains: " + stringBox.getContent()); } } Der Output dieses Beispiels lautet: Integer Box contains: 123 String Box contains: Java Challengers Zu beachten ist dabei: Die Box-Klasse verwendet den Typparameter E als Platzhalter für das Objekt, das die Box enthalten wird. Dadurch lässt sich Box mit jedem Objekttyp verwenden. Der Konstruktor initialisiert eine neue Instanz der Box-Klasse mit dem bereitgestellten Inhalt. Der E-Typ stellt dabei sicher, dass der Konstruktor jeden Objekttyp akzeptieren kann, der bei der Erstellung der Instanz definiert wurde – und gewährleistet so die Typsicherheit. getContent gibt den aktuellen Inhalt von Box zurück. Durch die Rückgabe von E wird sichergestellt, dass er dem Generic Type entspricht, der bei der Erstellung der Instanz angegeben wurde –  und der richtige Typ bereitgestellt wird. Casting ist dazu nicht erforderlich. setContent aktualisiert den Inhalt von Box. Weil E als Parameter verwendet wird, ist sichergestellt, dass nur ein Objekt des richtigen Typs als neuer Inhalt festgelegt werden kann. Das gewährleistet umfassende Type Safety, solange die Instanz genutzt wird. In der main-Methode werden zwei Box-Objekte erstellt: integerBox enthält eine Integer, stringBox einen String. Jede Box-Instanz arbeitet mit ihrem spezifischen Datentyp. Dieses Beispiel veranschaulicht, wie Generics in Java grundlegend implementiert und verwendet werden. Es unterstreicht, wie Objekte beliebigen Typs „type-safe“ erstellt und bearbeitet werden können. Generics mit unterschiedlichen Datentypen nutzen Sie können so viele Typen als generisch deklarieren, wie Sie möchten. In der folgenden Pair-Klasse können wir so die generischen Werte hinzufügen: class Pair { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } } public class GenericsDemo { public static void main(String[] args) { Pair person = new Pair("Duke", 30); System.out.println("Name: " + person.getKey()); System.out.println("Age: " + person.getValue()); person.setValue(31); System.out.println("Updated Age: " + person.getValue()); } } Dieser Code erzeugt folgenden Output: Name: Duke Age: 30 Updated Age: 31 Zu beachten ist dabei: Die generische Klasse Pair weist zwei Typparameter auf, wodurch sie für jeden Datentyp vielseitig einsetzbar ist. Konstruktoren und Methoden in der Pair-Klasse verwenden diese Typparameter, was striktes Type Checking ermöglicht. Ein Pair-Objekt wird erstellt, um einen String (den Namen einer Person) und einen Integer (ihr Alter) zu speichern. Accessors (getKey und getValue) sowie Mutators (setKey und setValue) manipulieren und rufen die Pair-Daten ab. Die Pair-Klasse kann verwandte Informationen speichern und managen und ist dabei nicht an bestimmte Datentypen gebunden. Dieses Beispiel demonstriert, wie Generics zu wiederverwendbaren, typsicheren Komponenten mit unterschiedlichen Datentypen beitragen – und zur Wiederverwendbarkeit und besseren Wartbarkeit von Code. Generic Types innerhalb einer Methode deklarieren Sie können Generic Types auch direkt innerhalb einer Methode deklarieren. Das bewerkstelligen Sie, indem Sie den Generic Type vor dem Rückgabetyp der Methodensignatur deklarieren: public class GenericMethodDemo { // Declare generic type and print elements with the chosen type public static void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); } public static void main(String[] args) { // Using the generic method with an Integer array Integer[] intArray = {1, 2, 3, 4}; printArray(intArray); // Using the generic method with a String array String[] stringArray = {"Java", "Challengers"}; printArray(stringArray); } } Der Output sieht wie folgt aus: 1 2 3 4 Java Challengers Raw Types vs. Generics Bei einem Raw Type handelt es sich im Grunde um den Namen einer Generic Class (beziehungsweise eines Generic Interface), jedoch ohne Typargumente. Diese waren vor der Generics-Einführung in Java 5 weit verbreitet. Heutzutage kommen Sie vor allem zum Einsatz, um die Kompatibilität mit Legacy-Code oder die Interoperabilität mit nicht-generischen APIs zu gewährleisten. Es empfiehlt sich also, zu wissen, wie Raw Types im Code zu erkennen und zu verwenden sind. Ein gängiges Beispiel für den Raw-Type-Einsatz ist, eine List ohne Typparameter zu deklarieren: List rawList = new ArrayList(); In diesem Beispiel deklariert List rawList eine Liste ohne generischen Typparameter. rawList kann jeden Objekttyp enthalten, einschließlich Integer, String, Double und so weiter. Weil kein Typ spezifiziert ist, wird im Rahmen der Kompilierung nicht überprüft, welche Objekttypen der Liste hinzugefügt werden. Compiler-Warnung bei Raw Types Der Java-Compiler sendet Warnmeldungen bezüglich der Verwendung von Raw Types in Java. Diese werden generiert, um Entwickler auf potenzielle Risiken im Zusammenhang mit der Typsicherheit hinzuweisen, wenn Raw- statt Generic Types verwendet werden. Wie Sie Generics nutzen, überprüft der Compiler die Objekttypen in den Collections (wie List und Set), die Rückgabetypen von Methoden und die Parameter. Das geschieht, um sicherzustellen, dass diese mit den deklarierten Generic Types übereinstimmen. Das verhindert gängige Bugs wie ClassCastException zur Laufzeit. Wenn Sie einen Raw Type nutzen, kann der Compiler diese Prüfungen nicht durchführen. Das liegt daran, dass Raw Types nicht den Typ der Objekte spezifizieren, die sie enthalten. Infolgedessen gibt der Compiler Warnmeldungen aus, um darauf aufmerksam zu machen, dass die Type-Safety-Mechanismen der Generics umgangen werden. Beispiel für eine Compiler-Warnung Im Folgenden ein simples Beispiel, das veranschaulicht, wie die Compiler-Warnung aussieht, wenn Raw Types zum Einsatz kommen: List list = new ArrayList(); // Warning: Raw use of parameterized class 'List' list.add("hello"); list.add(1); Wenn Sie diesen Code kompilieren, gibt der Java-Compiler in der Regel eine Meldung aus, wie beispeislweise: Note: SomeFile.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. Wenn Sie mit der Flag -Xlint:unchecked kompilieren, erhalten Sie detailliertere Informationen darüber, wo und warum diese Warnmeldung generiert wurde: warning: [unchecked] unchecked call to add(E) as a member of the raw type List list.add("hello"); ^ where E is a type-variable: E extends Object declared in interface List Wenn Sie sicher sind, dass es keine Risiken birgt, Raw Types in Ihrem Code zu verwenden (oder Sie mit Legacy-Code arbeiten, der sich nicht so einfach für die Nutzung von Generics umgestalten lässt), können Sie @SuppressWarnings(„unchecked“) nutzen, um diese auszublenden. Das sollten Sie mit Bedacht nutzen, schließlich könnten dabei andere, echte Probleme unter den Tisch fallen. Raw Types verwenden – die Folgen Zwar sind Raw Types mit Blick auf die Abwärtskompatibilität nützlich. Allerdings hat das auch ganz wesentliche Nachteile. Insbesondere: geht die Type Safety verloren. erhöhen sich die Wartungskosten. Ein Beispiel für ein Type-Safety-Problem: Wenn Sie List (Raw Type) anstelle des generischen List verwenden, erlaubt es der Compiler, beliebige Objekttypen zur Liste hinzuzufügen – nicht nur Strings. Wenn Sie ein Element aus der Liste abrufen und versuchen, es in einen String umzuwandeln, dieses Element aber tatsächlich von einem anderen Typ ist, kann das in Laufzeitfehlern resultieren. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!

Tutorial: Generics in Java nutzen​

: Lesen Sie, wie Generics in Java optimale Wirkung entfalten.THP Creative | shutterstock.com Mit Version 5 von Java wurden Generics eingeführt. Sie verbessern die Typsicherheit des Codes und machen ihn leichter lesbar. Das trägt auch dazu bei, Fehler zur Laufzeit wie ClassCastException zu verhindern. Dieser tritt auf, wenn Objekte in inkompatible Typen umgewandelt werden sollen. Dieses Tutorial beschäftigt sich eingehend mit dem Thema Generics in Java und vermittelt Ihnen: welchen Mehrwert Generics bringen, wie Sie diese im Java Collection Framework nutzen, wie Sie Generic Types in Java konkret einsetzen (inklusive Beispielen), und   was mit Blick auf Raw Types und Generics zu beachten ist. Was Generics bringen Generics werden gängigerweise innerhalb des Java Collection Frameworks genutzt mit: java.util.List, java.util.Set und java.util.Map. Darüber hinaus sind sie auch in anderen Teilen von Java zu finden, beispielsweise in: java.lang.Class, java.lang.Comparable und java.lang.ThreadLocal. Bevor Generics eingeführt wurden, mangelte es Java-Code regelmäßig an Type Safety. Hier ein Beispiel für Java-Code im Prä-Generics-Zeitalter: List integerList = new ArrayList(); integerList.add(1); integerList.add(2); integerList.add(3); for (Object element : integerList) { Integer num = (Integer) element; // Cast is necessary System.out.println(num); } Mit diesem Code sollen Integer-Objekte in einer Liste gespeichert werden. Dabei können Sie jederzeit weitere Typen hinzufügen, etwa einen String: integerList.add(“Hello”); Würden Sie versuchen, den String in einen Integer umzuwandeln, würde dieser Code zur Laufzeit eine ClassCastException verursachen. Mit Hilfe von Generics lässt sich dieses Problem lösen und der Fehler zur Laufzeit vermeiden. Dazu nutzen wir sie, um die Typen von Objekten zu spezifizieren, die eine Liste enthalten darf. In diesem Fall können wir auf einen Class Cast verzichten, was dazu führt, dass der Code sicherer wird und leichter zu verstehen ist:    List integerList = new ArrayList(); integerList.add(1); integerList.add(2); integerList.add(3); for (Integer num : integerList) { System.out.println(num); } List bedeutet „eine Liste von Integer-Objekten“. Auf Grundlage dieser Anweisung stellt der Compiler sicher, dass ausschließlich Integer-Objekte zur Liste hinzugefügt werden können. Generics im Java Collections Framework Um eine Typprüfung während der Kompilierung zu ermöglichen und die Notwendigkeit einer expliziten Typumwandlung zu beseitigen, sind Generics im Java Collections Framework integriert. Wenn Sie eine Collection mit Generics nutzen, spezifizieren Sie den Typ der Elemente, die erstere enthalten soll. Der Java-Compiler nutzt diese Spezifikation, um sicherzustellen, dass nicht versehentlich ein inkompatibles Objekt in die Collection einfließt. Das reduziert Fehler und macht den Code besser lesbar. Um zu veranschaulichen, wie Generics im Java Collections Framework angewendet werden, werfen wir im Folgenden einen Blick auf drei Beispiele. Namenskonventionen für GenericsGeneric Types können Sie in jeder beliebigen Klasse deklarieren und dafür jeden beliebigen Namen verwenden. Vorzugsweise sollten Sie sich dabei aber auf eine Namenskonvention verlassen. In Java bestehen die Namen von Typparametern in der Regel aus einzelnen Großbuchstaben. Dabei steht:E für Element,K für Key,V für Value, undT für Type.Insofern sollten Sie davon absehen, bedeutungslose Benennungen wie „X“, „Y“ oder „Z“ zu verwenden. List und ArrayList Im zuletzt angeführten Code-Beispiel haben wir bereits eine einfachere Möglichkeit gesehen, um ArrayList zu nutzen. Dieses Konzept sehen wir uns nun genauer an, indem wir einen Blick darauf werfen, wie das List-Interface deklariert wird:   public interface List extends SequencedCollection { … } In diesem Code deklarieren wir unsere generische Variable als „E“. Diese kann durch jeden beliebigen Objekttyp ersetzt werden. Sehen wir uns nun an, wie die Variable E durch den gewünschten Typ für unsere List ersetzt wird. Im folgenden Code ersetzen wir die Variable durch : List list = new ArrayList(); list.add(“Java”); list.add(“Challengers”); // list.add(1); // This line would cause a compile-time error. Hier spezifiziert List, dass die Liste lediglich String-Objekte enthalten kann. Eine Integer hinzuzufügen, führt zu einem Kompilierungsfehler, wie Sie in der letzten Code-Zeile sehen. Set und HashSet Das Set-Interface ähnelt dem von List: public interface Set extends Collection { … } Wir ersetzen zudem durch , so dass wir nur einen Double-Wert in das entsprechende Set einfügen können: Set doubles = new HashSet(); doubles.add(1.5); doubles.add(2.5); // doubles.add(“three”); // Compile-time error double sum = 0.0; for (double d : doubles) { sum += d; } Set gewährleistet, dass nur Double-Werte hinzugefügt werden können. Das verhindert Laufzeitfehler, die durch nicht korrektes Casting entstehen können. Map und HashMap Wir können so viele generische Typen deklarieren, wie wir möchten. Im folgenden ein Key-Value-Datenstrukturbeispiel in Form einer Map: public interface Map { … } Nun ersetzen wir den Key-Type K durch String und den Value-Type V durch Integer: Map map = new HashMap(); map.put(“Duke”, 30); map.put(“Juggy”, 25); // map.put(1, 100); // This line would cause a compile-time error Dieses Beispiel zeigt eine HashMap, die String-Keys Integer-Values zuordnet. Einen Key vom Typ Integer hinzuzufügen, ist nicht erlaubt und führt zu einem Kompilierungsfehler. Generic Types in Java nutzen – Beispiele Im nächsten Schritt sehen wir uns einige Beispiele an, um besser zu durchdringen, wie Generic Types in Java deklariert und verwendet werden. Generics mit beliebigen Objekten nutzen Generic Types lassen sich jeder Klasse deklarieren – es muss sich nicht um einen Collection-Typ handeln. Im folgenden Code-Beispiel deklarieren wir den Generic Type E, um jedes Element innerhalb der Box-Klasse zu manipulieren. Zu beachten ist dabei, dass der Generic Type nach dem Klassennamen deklariert wird. Erst dann können wir ihn als Attribut, Konstruktor, Methodenparameter und Methodenrückgabetyp nutzen: // Define a generic class Box with a generic type parameter E public class Box { // Variable to hold an object of type E private E content; public Box(E content) { this.content = content; } public E getContent() { return content; } public void setContent(E content) { this.content = content; } public static void main(String[] args) { // Create a Box to hold an Integer Box integerBox = new Box(123); System.out.println(“Integer Box contains: ” + integerBox.getContent()); // Create a Box to hold a String Box stringBox = new Box(“Hello World”); stringBox.setContent(“Java Challengers”); System.out.println(“String Box contains: ” + stringBox.getContent()); } } Der Output dieses Beispiels lautet: Integer Box contains: 123 String Box contains: Java Challengers Zu beachten ist dabei: Die Box-Klasse verwendet den Typparameter E als Platzhalter für das Objekt, das die Box enthalten wird. Dadurch lässt sich Box mit jedem Objekttyp verwenden. Der Konstruktor initialisiert eine neue Instanz der Box-Klasse mit dem bereitgestellten Inhalt. Der E-Typ stellt dabei sicher, dass der Konstruktor jeden Objekttyp akzeptieren kann, der bei der Erstellung der Instanz definiert wurde – und gewährleistet so die Typsicherheit. getContent gibt den aktuellen Inhalt von Box zurück. Durch die Rückgabe von E wird sichergestellt, dass er dem Generic Type entspricht, der bei der Erstellung der Instanz angegeben wurde –  und der richtige Typ bereitgestellt wird. Casting ist dazu nicht erforderlich. setContent aktualisiert den Inhalt von Box. Weil E als Parameter verwendet wird, ist sichergestellt, dass nur ein Objekt des richtigen Typs als neuer Inhalt festgelegt werden kann. Das gewährleistet umfassende Type Safety, solange die Instanz genutzt wird. In der main-Methode werden zwei Box-Objekte erstellt: integerBox enthält eine Integer, stringBox einen String. Jede Box-Instanz arbeitet mit ihrem spezifischen Datentyp. Dieses Beispiel veranschaulicht, wie Generics in Java grundlegend implementiert und verwendet werden. Es unterstreicht, wie Objekte beliebigen Typs „type-safe“ erstellt und bearbeitet werden können. Generics mit unterschiedlichen Datentypen nutzen Sie können so viele Typen als generisch deklarieren, wie Sie möchten. In der folgenden Pair-Klasse können wir so die generischen Werte hinzufügen: class Pair { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } } public class GenericsDemo { public static void main(String[] args) { Pair person = new Pair(“Duke”, 30); System.out.println(“Name: ” + person.getKey()); System.out.println(“Age: ” + person.getValue()); person.setValue(31); System.out.println(“Updated Age: ” + person.getValue()); } } Dieser Code erzeugt folgenden Output: Name: Duke Age: 30 Updated Age: 31 Zu beachten ist dabei: Die generische Klasse Pair weist zwei Typparameter auf, wodurch sie für jeden Datentyp vielseitig einsetzbar ist. Konstruktoren und Methoden in der Pair-Klasse verwenden diese Typparameter, was striktes Type Checking ermöglicht. Ein Pair-Objekt wird erstellt, um einen String (den Namen einer Person) und einen Integer (ihr Alter) zu speichern. Accessors (getKey und getValue) sowie Mutators (setKey und setValue) manipulieren und rufen die Pair-Daten ab. Die Pair-Klasse kann verwandte Informationen speichern und managen und ist dabei nicht an bestimmte Datentypen gebunden. Dieses Beispiel demonstriert, wie Generics zu wiederverwendbaren, typsicheren Komponenten mit unterschiedlichen Datentypen beitragen – und zur Wiederverwendbarkeit und besseren Wartbarkeit von Code. Generic Types innerhalb einer Methode deklarieren Sie können Generic Types auch direkt innerhalb einer Methode deklarieren. Das bewerkstelligen Sie, indem Sie den Generic Type vor dem Rückgabetyp der Methodensignatur deklarieren: public class GenericMethodDemo { // Declare generic type and print elements with the chosen type public static void printArray(T[] array) { for (T element : array) { System.out.print(element + ” “); } System.out.println(); } public static void main(String[] args) { // Using the generic method with an Integer array Integer[] intArray = {1, 2, 3, 4}; printArray(intArray); // Using the generic method with a String array String[] stringArray = {“Java”, “Challengers”}; printArray(stringArray); } } Der Output sieht wie folgt aus: 1 2 3 4 Java Challengers Raw Types vs. Generics Bei einem Raw Type handelt es sich im Grunde um den Namen einer Generic Class (beziehungsweise eines Generic Interface), jedoch ohne Typargumente. Diese waren vor der Generics-Einführung in Java 5 weit verbreitet. Heutzutage kommen Sie vor allem zum Einsatz, um die Kompatibilität mit Legacy-Code oder die Interoperabilität mit nicht-generischen APIs zu gewährleisten. Es empfiehlt sich also, zu wissen, wie Raw Types im Code zu erkennen und zu verwenden sind. Ein gängiges Beispiel für den Raw-Type-Einsatz ist, eine List ohne Typparameter zu deklarieren: List rawList = new ArrayList(); In diesem Beispiel deklariert List rawList eine Liste ohne generischen Typparameter. rawList kann jeden Objekttyp enthalten, einschließlich Integer, String, Double und so weiter. Weil kein Typ spezifiziert ist, wird im Rahmen der Kompilierung nicht überprüft, welche Objekttypen der Liste hinzugefügt werden. Compiler-Warnung bei Raw Types Der Java-Compiler sendet Warnmeldungen bezüglich der Verwendung von Raw Types in Java. Diese werden generiert, um Entwickler auf potenzielle Risiken im Zusammenhang mit der Typsicherheit hinzuweisen, wenn Raw- statt Generic Types verwendet werden. Wie Sie Generics nutzen, überprüft der Compiler die Objekttypen in den Collections (wie List und Set), die Rückgabetypen von Methoden und die Parameter. Das geschieht, um sicherzustellen, dass diese mit den deklarierten Generic Types übereinstimmen. Das verhindert gängige Bugs wie ClassCastException zur Laufzeit. Wenn Sie einen Raw Type nutzen, kann der Compiler diese Prüfungen nicht durchführen. Das liegt daran, dass Raw Types nicht den Typ der Objekte spezifizieren, die sie enthalten. Infolgedessen gibt der Compiler Warnmeldungen aus, um darauf aufmerksam zu machen, dass die Type-Safety-Mechanismen der Generics umgangen werden. Beispiel für eine Compiler-Warnung Im Folgenden ein simples Beispiel, das veranschaulicht, wie die Compiler-Warnung aussieht, wenn Raw Types zum Einsatz kommen: List list = new ArrayList(); // Warning: Raw use of parameterized class ‘List’ list.add(“hello”); list.add(1); Wenn Sie diesen Code kompilieren, gibt der Java-Compiler in der Regel eine Meldung aus, wie beispeislweise: Note: SomeFile.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. Wenn Sie mit der Flag -Xlint:unchecked kompilieren, erhalten Sie detailliertere Informationen darüber, wo und warum diese Warnmeldung generiert wurde: warning: [unchecked] unchecked call to add(E) as a member of the raw type List list.add(“hello”); ^ where E is a type-variable: E extends Object declared in interface List Wenn Sie sicher sind, dass es keine Risiken birgt, Raw Types in Ihrem Code zu verwenden (oder Sie mit Legacy-Code arbeiten, der sich nicht so einfach für die Nutzung von Generics umgestalten lässt), können Sie @SuppressWarnings(„unchecked“) nutzen, um diese auszublenden. Das sollten Sie mit Bedacht nutzen, schließlich könnten dabei andere, echte Probleme unter den Tisch fallen. Raw Types verwenden – die Folgen Zwar sind Raw Types mit Blick auf die Abwärtskompatibilität nützlich. Allerdings hat das auch ganz wesentliche Nachteile. Insbesondere: geht die Type Safety verloren. erhöhen sich die Wartungskosten. Ein Beispiel für ein Type-Safety-Problem: Wenn Sie List (Raw Type) anstelle des generischen List verwenden, erlaubt es der Compiler, beliebige Objekttypen zur Liste hinzuzufügen – nicht nur Strings. Wenn Sie ein Element aus der Liste abrufen und versuchen, es in einen String umzuwandeln, dieses Element aber tatsächlich von einem anderen Typ ist, kann das in Laufzeitfehlern resultieren. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox! 

Nach oben scrollen