Javakurs für Anfänger Einheit 14: Generics Lorenz Schauer Lehrstuhl für Mobile und Verteilte Systeme Heutige Agenda Generische Klassen (Generics) Motivation Java Typ-Prüfung Warum also Generics? Generische Typen selbst definieren Generische Typen anwenden Zusammenfassung Ausblick Praxis: Generische Klasse BlackBox schreiben Lernziele Einführung in generische Klassen Das Konzept von Generics kennenlernen und verstehen Generische Typen schreiben und v.a. benutzen können 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 2 Motivation Generische Typen (Generics): Sind ein syntaktisches Mittel für die generische Programmierung Zählen zu den komplexesten Typen in Java Seit Java 1.5 fester Bestandteil Grundidee: Einführung von Variablen, die Typen (statt wie bisher Werte) speichern Diese Typ-Variablen repräsentieren zum Zeitpunkt der Implementierung unbekannte Typen. Erst bei der Verwendung der Klassen, Schnittstellen und Methoden werden diese Typ-Variablen durch konkrete Typen ersetzt. Damit kann typsichere Programmierung gewährleistet werden. Ein bekanntes Beispiel (hatten wir schon): Java.util.ArrayList<E> Anwendung: ArrayList<String> meine_liste1 = new ArrayList<String>(); ArrayList<Integer> meine_liste2 = new ArrayList<Integer>(); 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 3 Java Typ-Prüfung Die Typ-Prüfung passiert 2 Mal: Durch den Compiler (statisch) Führt erste Typ-Prüfung durch. Ist weniger „exakt“ als die dynamische Prüfung Durch die Laufzeitumgebung JVM (dynamisch) Hat absolute Typ-Intelligenz Prüft zur Laufzeit (als letzte Instanz), ob der Typ zum zugewiesenen Objekt passt Falls nicht, folgt Exception // Beispiel: Object o = Integer.valueOf(10); String s = (String) o; System.out.println(s); Anweisungen sind für den Compiler zunächst völlig in Ordnung. Statische Typprüfung: korrekt Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String Aber die JVM kann diese Anweisungen nicht at Test.main(Test.java:8) ausführen => ClassCastException Dynamische Typprüfung: falsch! 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 4 Warum also Generics? Einführung von Generics: Compiler bekommt mehr Informationen über die Typen So sollen bspw. ClassCastExceptions vermeiden werden. Beispiel bei ArrayList: Ohne parametrisierte Typen Mit parametrisierten Typen public static void main(String[] args) { ArrayList myList = new ArrayList(); myList.add("Hallo"); myList.add(new Hund("Bello","Haski",98)); myList.add(new Hund("Flippo","Schaefer",101)); myList.add("Bello"); for(int i=0; i<myList.size();i++){ Hund h = (Hund) myList.get(i); System.out.println(h.getName()); } } Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to Hund at Test.main(Test.java:15) 09.02.2017 Compiler-Fehler, da statische Typ-Prüfung jetzt fehlschlägt! Javakurs 14: Generics - Lorenz Schauer 5 Generische Typen deklarierten Man kann eigene generische Typen (Klassen) definieren: public class MeineKlasse<T> {…} Objekterzeugung dann: MeineKlasse<Typ> objektname = new MeineKlasse<Typ>(); Bsp mit String: MeineKlasse<String> k1 = new MeineKlasse<String>(); Seit Java 7 auch möglich: MeineKlasse<String> k1 = new MeineKlasse<>(); Das T steht für den generischen Typen und kann nun als Typ-Variable in der Klasse benutzt werden: Als Instanzvariable: Bsp.: private T meine_instanzvariable; Als Parameter: Bsp.: public MeineKlasse(T value){…} Als Rückgabetyp: Bsp.: public T doSomeStuff(){…} Hinweis: Laut Konvention schreibt man T für Typen und E für Elemente Muss aber nicht sein, man kann auch <BlaDiBlub> verwenden 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 6 Allgemeines Beispiel public class MeineKlasse<T> { Generische Klasse Mit Typ-Variable T // Instanzvariablen private T instanzvariable1; private T instanzvariable2; private String name; private int alter; Generische (parametrisierte) Instanzvariablen // Konstruktoren public MeineKlasse(){ this.name = "DefaultName"; this.alter = 0; } public MeineKlasse(T value1, T value2){ this.instanzvariable1=value1; this.instanzvariable1=value2; } public MeineKlasse(T value1, T value2, String name, int alter){ this.instanzvariable1=value1; this.instanzvariable1=value2; this.name = name; this.alter = alter; } //Getter and Setter public T getInstanzvariable1() { return instanzvariable1; } public void setInstanzvariable1(T instanzvariable1) { this.instanzvariable1 = instanzvariable1; } public String getName() {return name;} public void setName(String name) {this.name = name;} } 09.02.2017 Javakurs 14: Generics - Lorenz Schauer Nicht generische Instanzvariablen Konstruktor erwartet die generischen Typen Konstruktor erwartet generische und nicht generische Typen Methode liefert generischen Typ zurück Methode erwartet generischen Typ 7 Wie wird die Klasse nun benutzt? Ein Objekt der generischen Klasse kann nun beim Erzeugen angeben, welchen Typ es in die Typ-Variable einsetzt. Analog zur ArrayList ArrayList<String> meineListe = new ArrayList<String>(); ArrayList meineGenerelleListe = new ArrayList(); Wird kein Typ angegeben wird implizit die Super-Klasse Object eingesetzt D.h.: Dann weiß der Compiler genauso wenig über die „echten“ Typen wie zuvor und man muss auf Typ-Casting zurückgreifen! Das ist nicht der Sinn von Generics und wird mit einer Compiler-Warnung bestraft: “ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized“ 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 8 Wie wird die Klasse nun benutzt? public class Test{ objekt1 legt keinen generischen Typ fest. => Es wird implizit Object verwendet public static void main(String[] args) { // Objekt erzeugen: MeineKlasse objekt1 = new MeineKlasse(); MeineKlasse<String> objekt2=new MeineKlasse<>(); objekt2 setzt String MeineKlasse<Integer> objekt3=new MeineKlasse<>(); objekt3 setzt Integer objekt1 setzt Instanzvariable1 // Methoden-Aufrufe auf „Peter“. objekt1.setInstanzvariable1("Peter"); String s1 =(String)objekt1.getInstanzvariable1(); Korrekt, da generischer Typ = Object objekt2.setInstanzvariable1("Maria"); Bei Nutzung Typ-Cast nötig! String s2 = objekt2.getInstanzvariable1(); objekt3.setInstanzvariable1(23); int s3 = objekt3.getInstanzvariable1(); } } 09.02.2017 objekt2 setzt Instanzvariable1 auf „Maria“ Korrekt, da generischer Typ = String objekt3 setzt Instanzvariable1 auf 23 Korrekt, da generischer Typ = int Javakurs 14: Generics - Lorenz Schauer 9 Generische Typen einschränken Manchmal ist der bloße Typ <T> zu generisch und man möchte die akzeptierten Möglichkeiten einschränken: Mittels <T extends ClassOrInterface> lässt sich ein Typ bestimmen der eine bestimmte Klasse erweitert oder ein Interface implementiert Bsp.: public class MeineKlasse <T extends CharSequence> {…} Die Erzeugung mittels new MeineKlasse<String>(); ist demnach erlaubt Eine Erzeugung mit new MeineKlasse<Integer>(); verbietet der Compiler - Bound mismatch: The type Integer is not a valid substitute for the bounded parameter <T extends CharSequence> of the type MeineKLasse<T> Soll <T> zu mehreren Typen passen, kann man mittels & noch weitere Interfaces hinzufügen Bsp.: public class MeineKlasse <T extends CharSequence & Comparable> 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 10 Programmieraufgabe zu Generics Schreiben Sie in Analogie zur ArrayList eine generische Klasse BlackBox<E> welche folgende Methoden anbieten soll: add(E element) fügt ein Element Ihrer Box hinzu remove(E element) löscht ein Element aus Ihrer Box clear() löscht alle Element aus Ihrer Box getElementAtPosition(int i) liefert das Element an der Position i zurück printNamesOfElements() gibt die Namen sämtlicher Elemente in der BlackBox auf der Konsole aus Hinweis: Nutzen Sie hierfür die Methode toString(), welche von der Oberklasse Object bereitgestellt wird. Zusätzlich soll die Klasse folgende Attribute bereitstellen String name (welcher über den Konstruktor gesetzt wird) Eine geeignete Datenstruktur zum Speichern der Elemente Hinweis: Der Einfachheit halber nutzen Sie hier die ArrayList<E>, mit der Sie oben genannte Methoden leicht implementieren können. Nutzen Sie nun Ihre neue Klasse BlackBox und erzeugen Sie 2 Instanzen mit unterschiedlichen Typen. Sie können auf Klassen von vorangegangenen Stunden zurückgreifen (Hund, Auto) Erzeugen Sie nun mehrere Objekte der beiden gewählten Klassen und fügen Sie diese in die entsprechene BlackBox ein. Was erscheint auf der Konsole, wenn Sie die printNamesOfElements() Methode auf den beiden BlackBox Objekten aufrufen? 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 11 Zusammenfassung Sie haben nun die grundlegenden Elemente der objektorientierten Programmiersprache Java kennengelernt: Klassen, Objekte, Variablen & Methoden Zusammenspiel verschiedener Klassen und Objekte Abstraktion und Datenkapselung Kontrollstrukturen Statische und dynamische Arrays Vererbung Subtyping, Typ-Casting, … Abstrakte Klassen Interfaces Anonyme innere Klassen Polymorphismus Exceptions Generics UML Java API Dokumentation 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 12 Und jetzt…? Fortführende Themen wären: Weitere Datenstrukturen (verkettete) Listen, Stack, Schlangen (Queues), Hash-Tables, Bäume (Trees) Frameworks Collection-Framework Nebenläufigkeit Threads, Conditions, Synchronisation, Monitore GUI-Programmierung mit Swing / JavaFX Design-Pattern Algorithmen Sortieren & Suchen, Rekursion, … U.v.m. Einfach am Ball bleiben und viel programmieren ;) Den Java-Kurs für Fortgeschrittene im nächsten Semester besuchen… 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 13 Vielen Dank! Vielen Dank fürs Zuhören und Mitmachen! 09.02.2017 Javakurs 14: Generics - Lorenz Schauer 14