Commit 6bdd32fc authored by Dominikus Herzberg's avatar Dominikus Herzberg

Reorg für Generics und Collections

parent 5dc81305
= Generische Programmierung (Generics)
Dominikus Herzberg, Christopher Schölzel
:toc: left
:toctitle: Inhaltsverzeichnis
:toclevels: 4
:icons: font
:stylesheet: italian-pop.asciidoc.css
:source-highlighter: coderay
:sourcedir: code/10
include::prelude.adoc[]
Die Aufgaben in diesem Kapitel üben die sogenannte generische Programmierung ein.
.Mehr ist besser
****
Wenn Sie noch ein paar Idee für Aufgaben haben, machen Sie uns Vorschläge!
****
== Generischer Stack
Sie haben eventuell bereits generische Klassen mit Typparametern in Form der Collection-API kennengelernt und verwendet, aber wie funktioniert das eigentlich? Wie kann man eine eigene generische Klasse definieren?
Genau das sollen Sie in dieser Übung lernen. Dazu schauen wir uns zunächst Auszüge aus der Definition der Klasse `ArrayList` an. Auslassungen sind dabei mit dem Ausdruck `[...]` gekennzeichnet.
[source,java]
----
include::{sourcedir}/arrayList.java[]
----
<1> Hier wird definiert, dass zu der Klasse `ArrayList` ein Typparameter namens `E` gehört. Wird die Klasse einfach nur als `ArrayList` verwendet, verhält sie sich so als würde man jedes `E` durch ein `Object` austauschen. Eine `ArrayList<Integer>` dagegen arbeitet mit `Integer`-Objekten, wo in der Definition ein `E` vorkommt.
<2> Der Parameter `E` kann überall auftauchen wo Klassennamen verwendet werden: Als Typ einer Variable oder eines Parameters oder als Rückgabetyp. Natürlich kann man von einem Objekt von einem unbekannten bzw. beliebigen Typ `E` auch nur Methoden aufrufen, die _jedes_ Objekt in Java besitzt - also alles was in der Klasse `Objekt` definiert ist. Für die `ArrayList` reicht das völlig, da mit den Objekten ja nichts anderes geschieht als dass sie in ein Array gesteckt oder wieder aus diesem herausgeholt werden.
<3> Es wirkt etwas seltsam, dass das interne Array nicht vom Typ `E[]` statt vom Typ `Object[]` ist. Es spricht nichts dagegen eine Variable oder ein Feld vom Typ `E[]` zu definieren, aber in Java ist das Erzeugen von generischen Arrays etwas komplizierter. Das hat damit zu tun, dass Typparameter vom Java-Compiler entfernt und durch den Typ `Object` (mit entsprechenden Typcasts an den relevanten Stellen) ersetzt werden.
Für ihre Klasse `Stack` bzw. `Stapel` aus den vorherigen Übungsblättern ist die Situation sogar noch etwas einfacher. Schaffen Sie es analog zu dem Beispiel der `ArrayList` auch einen generischen `Stack` zu bauen, so dass z.B. die folgenden Aufrufe möglich werden?
----
Stack<String> stringStack = new Stack<String>("First");
stringStack.push("Second");
Stack<Double> doubleStack = new Stack<Double>(Double.NaN);
doubleStack.push(3.5);
double d = doubleStack.pop();
----
include::preDetailsSolution.adoc[]
[source,java]
----
include::{sourcedir}/genericStack.java[]
----
<1> Hier haben wir einen Trick verwendet um unser generisches Array zu erzeugen, der auch in der Methode `toArray` von `ArrayList` zu sehen ist: Wir verlangen vom Benutzer, dass er uns ein Array des korrekten Typs übergibt, damit wir und mit `Arrays.newInstance` ein Array vom gleichen Typ erzeugen können. Man kann die Implementierung noch etwas effizienter machen indem man direkt das übergebene Array zum speichern der Werte verwendet, falls es groß genug ist.
<2> Auch Methoden können eigene Typparameter besitzen. Bei dieser statischen Methode ist das nötig, da bei dem Aufruf von `Stack.fromArray` kein Objekt vom Typ `Stack` beteiligt ist von dem man den Typparameter ableiten könnte.
include::postDetails.adoc[]
== Turing-Maschine
Der Mathematiker und Informatik-Pionier Alan Turing erfand in jungen Jahren die Turing-Maschine, die allerdings lediglich als theoretisches Konstrukt gedacht war. Turing benötigte ein einfaches Rechenmodell, mit dem er über das Wesen und die Möglichkeiten maschinellen Rechnens nachdenken konnte. Er tat das zu einer Zeit, bevor es einen "echten" Computer gab. Allerdings lag die Idee einer vollautomatischen Rechenmaschine in der Luft. Man sieht es der Idee Turings an, sie kombiniert die Idee der Schreibmaschine mit einem Bandgerät, das an ein Tonband erinnert, und lässt beides durch einen kleinen Steuerapparat koordiniert arbeiten.
Finden Sie anhand des Codes heraus, wie die Turing-Maschine funktioniert. Vermutlich werden Ihnen die Erläuterungen auf https://de.wikipedia.org/wiki/Turingmaschine[Wikipedia] eine Hilfe sein.
[source,java]
----
include::{sourcedir}/turing.java[]
----
\ No newline at end of file
= Collections
Dominikus Herzberg, Christopher Schölzel
:toc: left
:toctitle: Inhaltsverzeichnis
:toclevels: 4
:icons: font
:stylesheet: italian-pop.asciidoc.css
:source-highlighter: coderay
:sourcedir: code/11
include::prelude.adoc[]
Sie kennen bislang Arrays, die es erlauben, eine feste Anzahl von Elementen eines Typs gemeinschaftlich zu verwalten. Was, wenn man die Anzahl der Elemente vorher noch nicht kennt? Für diesen Fall gibt es die sogenannten _Collections_.
.Mehr ist besser
****
Wenn Sie noch ein paar Idee für Aufgaben haben, machen Sie uns Vorschläge!
****
== Recherchen zu Collections
`Collection` ist keine Klasse, sondern ein Interface. Es gibt mehrere Klassen, die das Interface implementieren.
* Suchen Sie die Dokumentation zu `Collection` heraus; verschaffen Sie sich einen Überblick über die deklarierten Methoden
* Zwei wichtige Implementierungen sind `ArrayList` und `Stack`; schauen Sie sich auch hier die deklarierten Methoden an.
Eine `Collection` oder eine Implementierungsklasse wie z.B. `ArrayList` muss wissen, von welchem Typ die aufgenommenen Elemente in der Kollektion sind. Aus diesem Grund muss die `Collection` bzw. die `ArrayList` mit einem Typ parametrisiert (man könnte auch sagen: konfiguriert) werden. Recherchieren Sie dazu im Internet und erstellen Sie ein kleines Beispiel in der JShell:
* Ihre Kollektion `c` soll durch eine `ArrayList` realisiert werden
* Fügen Sie der Kollektion zwei `Integer`-Elemente hinzu, wie z.B. `2` und `5`
* Zeigen Sie, wie die Größe der Kollektion anwächst
Durch die Elemente einer Kollektion kann man mit der sogenannten _foreach_-Variante der `for`-Schleife iterieren.
* Recherchieren Sie, wie diese Form der `for`-Schleife anzuwenden ist
* Iterieren Sie über die Elemente der Kollektion `c` und geben Sie die Elemente per `System.out.printf` aus
include::preDetailsSolution.adoc[]
Die Links zu:
* Collection: https://docs.oracle.com/javase/9/docs/api/java/util/Collection.html
* ArrayList: https://docs.oracle.com/javase/9/docs/api/java/util/ArrayList.html
* Stack: https://docs.oracle.com/javase/9/docs/api/java/util/Stack.html
Eine Kollektion aufsetzen und nutzen:
----
jshell> Collection<Integer> c = new ArrayList<>()
c ==> []
jshell> c.size()
$30 ==> 0
jshell> c.add(2)
$31 ==> true
jshell> c.size()
$32 ==> 1
jshell> c.add(5)
$33 ==> true
jshell> c.size()
$34 ==> 2
----
Iteration und Ausgabe der Kollektionswerte
----
jshell> for(Integer i : c) System.out.printf("Element %d\n",i);
Element 2
Element 5
----
include::postDetails.adoc[]
== Warenlager
In dieser Aufgabe soll es um die "Selbstverwaltung" von Dingen gehen. Jede neue Instanz eines Dings (`Thing`) bekommt mit dem Konstruktor eine eindeutige Kennung (_identity_ oder auch kurz ID) als Ganzzahl zugeordnet. Keine Kennung darf doppelt vorkommen, sonst wird eine Ausnahme "illegales Argument" geworfen. Zum schnellen Abgleich mit bereits vergebenen IDs soll eine statische Variable namens `IDs` vom Typ `Set` genutzt werden. Alle neuen Dinge sind außerdem in einer statischen Variable namens `inventory` vom Typ `Collection` einzutragen.
Folgende Methoden sind zu implementieren:
* `setPrice` ordnet einem Ding einen Preis zu.
* `getPrice` liefert den aktuellen Preis des Dings zurück
* `find` (statisch) liefert zu einer gegebenen Identität die entsprechende Ding-Instanz zurück
* `showInventory` (statisch) gibt alle Dinge anhand ihrer ID und ihrem Preis auf der Konsole aus
Hinweise:
* Sichern Sie Instanzvariablen gegen den Zugriff von außen ab
* Schützen Sie die Kennung (Identität) gegenüber Änderungen
* Nutzen Sie eine Variante der `for`-Schleife, die als _foreach_-Schleife bezeichnet wird
.Lernziele
* Sie verwenden die Interfaces `Collection` und `Set` und machen sich mit den verfügbaren Methoden vertraut
* Sie erlernen, dass Interfaces auch Referenztypen sind
* Sie können eine geeignete Implementierungen zu einer `Collection` wählen, sie parametrisieren und nutzen
* Sie werfen an geeigneten Stellen Ausnahmen, um ungültige Argumente abzufangen
* Sie setzen die sogenannte _foreach_-Schleife ein
include::preDetailsSolution.adoc[]
[source,java]
----
include::{sourcedir}/Thing.java[]
----
include::postDetails.adoc[]
== Umwandlung (Array -> Set)
Implementieren sie die folgenden Methoden mit einem Objekt vom Typ `Set` statt mit Arrays. Vergewissern sie sich, dass ihre Implementierung genau so funktioniert, wie die Array-Implementierung.
[TIP]
====
Denken Sie daran, dass ein `Set` per Definition keine Duplikate enthalten kann.
====
[source,java]
----
include::{sourcedir}/transformSet.java[]
----
include::preDetailsSolution.adoc[]
[source,java]
----
include::{sourcedir}/transformSet_solution.java[]
----
include::postDetails.adoc[]
== Umwandlung (Array -> List)
Implementieren sie die folgenden Methoden mit einem Objekt vom Typ `List` statt mit Arrays. Vergewissern sie sich, dass ihre Implementierung genau so funktioniert, wie die Array-Implementierung.
[source,java]
----
include::{sourcedir}/transformList.java[]
----
include::preDetailsSolution.adoc[]
[source,java]
----
include::{sourcedir}/transformList_solution.java[]
----
<1> Auch Methoden können Typparameter haben. Wenn man die Methode so definiert wie in dieser Musterlösung, kann man sie für Listen beliebigen Typs verwenden. Es wäre aber auch eine korrekte Lösung, wenn man den Typparameter weglässt und mit `List<Integer>` arbeitet statt mit `List<E>`.
<2> Der sogenannte _Diamantoperator_ `<>` kann bei der Definition von Variablen eines generischen Typs verwendet werden. Der Compiler leitet dann die Typparameter von dem Variablentyp ab.
include::postDetails.adoc[]
== Umwandlung (Array -> Map)
Implementieren sie die folgenden Methoden mit einem Objekt vom Typ `Map` statt mit Arrays. Vergewissern sie sich, dass ihre Implementierung genau so funktioniert, wie die Array-Implementierung.
[source,java]
----
include::{sourcedir}/transformMap.java[]
----
include::preDetailsSolution.adoc[]
[source,java]
----
include::{sourcedir}/transformMap_solution.java[]
----
include::postDetails.adoc[]
== Suche in Collections
Schreiben Sie eine Methode `find`, die ein Objekt vom Typ `java.util.Collection<Integer>` und ein Objekt vom Typ `Integer` übernimmt und den Index zurückgibt, an dem der gesuchte Integer in der Collection steht. Wird der Integer nicht gefunden, soll der Wert `-1` zurückgegeben werden.
[TIP]
====
Das Interface `Collection` ist zu allgemein um einen Zugriff über einen Index zu erlauben. Stattdessen müssen Sie hier mit Iteratoren (oder einer _foreach_-Schleife) arbeiten.
====
Schaffen Sie es, Ihre Methode so zu erweitern, dass sie für einen beliebigen Typen `T` funktioniert statt nur für `Integer`?
== Binäre Suche
Wenn man schon weiß, dass eine Liste oder ein Array sortiert ist, kann man einen deutlich performanteren Algorithmus zum Suchen eines Elements verwenden, als einfach jedes Element zu überprüfen. Bei dieser sogenannten https://de.wikipedia.org/wiki/Bin%C3%A4re_Suche[_binären Suche_] schaut man sich immer das Element in der Mitte des noch zu durchsuchenden Bereichs an. Entweder ist dieses Element schon das gesuchte Element oder man muss nur noch links davon (wenn das gesuchte Element kleiner ist) oder rechts davon (wenn das gesuchte Element größer ist) weitersuchen.
Implementieren Sie die binäre Suche in `Collection`-Objekten als eigene Methode `binarySearch`.
== Implementierung einer eigenen ArrayList
Grundsätzlich gilt: Immer wenn Sie eine Datenstruktur benötigen oder ein bestimmtes Feature umsetzten wollen recherchieren Sie zuerst, ob es das
nicht schon im JDK oder ggf. auch in anderen Libaries gibt. Denn Codewiederverwendung hilft Fehler zu vermeiden, da man auf Bewährtes und (hoffentlich) getesten Code zurückgreift. Und es spart Zeit, da sich zwar in die API eingearbeitet, aber nichts entwickelt werden muss.
Bei dieser Aufgabe weichen wir nun davon ab: Implementieren Sie Ihre eigene `ArrayList`, die zunächst nur Strings aufnehmen kann. Dies einmal
auszuprobieren hilft Ihnen, ein besseres Verständnis für die Funktionaliät der regulären `ArrayList` zu bekommen.
=== Implementierung ohne Generics
Die Funktionalität Ihrer Klasse `MyArrayList` beschränken Sie dabei auf folgende Features:
* `boolean add(String e)`
* `String get(int index)`
* `boolean isEmpty()`
* `String remove(int index)`
* `int size()`
* `Object[] toArray()`
Zum Erzeugen nutzen Sie einen Konstruktor `MyArrayList()`.
=== Implementierung mit Generics
Erweitern Sie Ihre Implementierung so, dass Sie beliebige Typen aufnehmen
können, ebenso, wie es die "echte" `ArrayList` kann. Mindestens diese drei Methoden
müssen Sie anpassen in:
* `boolean add(E e)`
* `E get(int index)`
* `E remove(int index)`
Was bedeutet das `E`. Überlegen Sie, was noch überarbeitet werden muss.
......@@ -234,45 +234,7 @@ include::{sourcedir}/10/MidiKeyboard.java[]
<1> Dieser statische initialisierer ist notwendig, weil es keinen (schönen) Weg gibt, eine `Map` samt Inhalt in einer Zeile anzulegen.
endif::solution[]
== Collections und Streams
=== Implementierung einer eigenen ArrayList
Grundsätzlich gilt: Immer wenn Sie eine Datenstruktur benötigen oder ein
bestimmtes Feature umsetzten wollen recherchieren Sie zuerst, ob es das
nicht schon im JDK oder ggf. auch in anderen Libaries gibt. Denn
Codewiederverwendung hilft Fehler zu vermeiden, da man auf Bewährtes und
(hoffentlich) getesten Code zurückgreift. Und es spart Zeit, da sich zwar in
die API eingearbeitet, aber nichts entwickelt werden muss.
Bei dieser Aufgabe weichen wir nun davon ab: Implementieren Sie Ihre
eigene `ArrayList`, die zunächst nur Strings aufnehmen kann. Dies einmal
auszuprobieren hilft Ihnen, ein besseres Verständnis für die Funktionaliät
der regulären `ArrayList` zu bekommen.
Die Funktionalität Ihrer Klasse `MyArrayList` beschränken Sie dabei auf
folgende Features:
* `boolean add(String e)`
* `String get(int index)`
* `boolean isEmpty()`
* `String remove(int index)`
* `int size()`
* `Object[] toArray()`
Zum Erzeugen nutzen Sie einen Konstruktor `MyArrayList()`.
// Intern legen Sie die Werte bitte in einem Array ab.
Erweitern Sie Ihre Implementierung so, dass Sie beliebige Typen aufnehmen
können, ebenso, wie es die "echte" `ArrayList` kann. Mindestens diese drei Methoden
müssen Sie anpassen in:
* `boolean add(E e)`
* `E get(int index)`
* `E remove(int index)`
Was bedeutet das `E`. Überlegen Sie, was noch überarbeitet werden muss.
== Verständnisfragen
......
......@@ -46,155 +46,6 @@ CAUTION: Benutzen Sie Ihren regulären Ausdruck niemals zur Überprüfung von Em
Einen Eindruck, wie kompliziert es sein kann, eine Email-Adresse korrekt zu erkennen, vermitteln Ihnen die Beiträge zu der Frage http://stackoverflow.com/questions/46155/validate-email-address-in-javascript["Validate email address in JavaScript?"] auf http://stackoverflow.com/. Wenn Sie es richtig ernst meinen, sollten Sie zur Validierung (Überprüfung) von Email-Adressen z.B. die https://java.net/projects/javamail[JavaMail-API]
nutzen.
== Collections
Sie kennen bislang Arrays, die es erlauben, eine feste Anzahl von Elementen eines Typs gemeinschaftlich zu verwalten. Was, wenn man die Anzahl der Elemente vorher noch nicht kennt? Für diesen Fall gibt es die sogenannten _Collections_.
=== Recherchen zu Collections
`Collection` ist keine Klasse, sondern ein Interface. Es gibt mehrere Klassen, die das Interface implementieren.
* Suchen Sie die Dokumentation zu `Collection` heraus; verschaffen Sie sich einen Überblick über die deklarierten Methoden
* Zwei wichtige Implementierungen sind `ArrayList` und `Stack`; schauen Sie sich auch hier die deklarierten Methoden an.
Eine `Collection` oder eine Implementierungsklasse wie z.B. `ArrayList` muss wissen, von welchem Typ die aufgenommenen Elemente in der Kollektion sind. Aus diesem Grund muss die `Collection` bzw. die `ArrayList` mit einem Typ parametrisiert (man könnte auch sagen: konfiguriert) werden. Recherchieren Sie dazu im Internet und erstellen Sie ein kleines Beispiel in der JShell:
* Ihre Kollektion `c` soll durch eine `ArrayList` realisiert werden
* Fügen Sie der Kollektion zwei `Integer`-Elemente hinzu, wie z.B. `2` und `5`
* Zeigen Sie, wie die Größe der Kollektion anwächst
Durch die Elemente einer Kollektion kann man mit der sogenannten _foreach_-Variante der `for`-Schleife iterieren.
* Recherchieren Sie, wie diese Form der `for`-Schleife anzuwenden ist
* Iterieren Sie über die Elemente der Kollektion `c` und geben Sie die Elemente per `printf` aus
ifdef::solution[]
.Lösung
Die Links zu:
* Collection: https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html
* ArrayList: https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html
* Stack: https://docs.oracle.com/javase/8/docs/api/java/util/Stack.html
Eine Kollektion aufsetzen und nutzen:
----
jshell> Collection<Integer> c = new ArrayList<>()
c ==> []
jshell> c.size()
$30 ==> 0
jshell> c.add(2)
$31 ==> true
jshell> c.size()
$32 ==> 1
jshell> c.add(5)
$33 ==> true
jshell> c.size()
$34 ==> 2
----
Iteration und Ausgabe der Kollektionswerte
----
jshell> for(Integer i : c) printf("Element %d\n",i);
Element 2
Element 5
----
endif::solution[]
=== Warenlager (V)
In dieser Aufgabe soll es um die "Selbstverwaltung" von Dingen gehen. Jede neue Instanz eines Dings (`Thing`) bekommt mit dem Konstruktor eine eindeutige Kennung (_identity_ oder auch kurz ID) als Ganzzahl zugeordnet. Keine Kennung darf doppelt vorkommen, sonst wird eine Ausnahme "illegales Argument" geworfen. Zum schnellen Abgleich mit bereits vergebenen IDs soll eine statische Variable namens `IDs` vom Typ `Set` genutzt werden. Alle neuen Dinge sind außerdem in einer statischen Variable namens `inventory` vom Typ `Collection` einzutragen.
Folgende Methoden sind zu implementieren:
* `setPrice` ordnet einem Ding einen Preis zu.
* `getPrice` liefert den aktuellen Preis des Dings zurück
* `find` (statisch) liefert zu einer gegebenen Identität die entsprechende Ding-Instanz zurück
* `showInventory` (statisch) gibt alle Dinge anhand ihrer ID und ihrem Preis auf der Konsole aus
Hinweise:
* Sichern Sie Instanzvariablen gegen den Zugriff von außen ab
* Schützen Sie die Kennung (Identität) gegenüber Änderungen
* Nutzen Sie eine Variante der `for`-Schleife, die gerne als "foreach"-Schleife bezeichnet wird; recherchieren Sie zuvor, was eine foreach-Schleife in Java ist
.Lernziele
* Sie verwenden die Interfaces `Collection` und `Set` und machen sich mit den verfügbaren Methoden vertraut
* Sie erlernen, dass Interfaces auch Referenztypen sind
* Sie können eine geeignete Implementierungen zu einer `Collection` wählen, sie parametrisieren und nutzen
* Sie werfen an geeigneten Stellen Ausnahmen, um ungültige Argumente abzufangen
* Sie setzen die sogenannte foreach-Schleife ein
ifdef::solution[]
.Lösung
[source,java]
----
include::{sourcedir}/Thing.java[]
----
endif::solution[]
=== Umwandlung (Array -> Set)
Implementieren sie die folgenden Methoden mit einem Objekt vom Typ `Set` statt mit Arrays. Vergewissern sie sich, dass ihre Implementierung genau so funktioniert, wie die Array-Implementierung.
[TIP]
====
Denken Sie daran, dass ein `Set` per Definition keine Duplikate enthalten kann.
====
[source,java]
----
include::{sourcedir}/transformSet.java[]
----
ifdef::solution[]
.Lösung
[source,java]
----
include::{sourcedir}/transformSet_solution.java[]
----
endif::solution[]
=== Umwandlung (Array -> List)
Implementieren sie die folgenden Methoden mit einem Objekt vom Typ `List` statt mit Arrays. Vergewissern sie sich, dass ihre Implementierung genau so funktioniert, wie die Array-Implementierung.
[source,java]
----
include::{sourcedir}/transformList.java[]
----
ifdef::solution[]
.Lösung
[source,java]
----
include::{sourcedir}/transformList_solution.java[]
----
<1> Auch Methoden können Typparameter haben. Wenn man die Methode so definiert wie in dieser Musterlösung, kann man sie für Listen beliebigen Typs verwenden. Es wäre aber auch eine korrekte Lösung, wenn man den Typparameter weglässt und mit `List<Integer>` arbeitet statt mit `List<E>`.
<2> Der sogenannte _Diamantoperator_ `<>` kann bei der Definition von Variablen eines generischen Typs verwendet werden. Der Compiler leitet dann die Typparameter von dem Variablentyp ab.
endif::solution[]
=== Umwandlung (Array -> Map)
Implementieren sie die folgenden Methoden mit einem Objekt vom Typ `Map` statt mit Arrays. Vergewissern sie sich, dass ihre Implementierung genau so funktioniert, wie die Array-Implementierung.
[source,java]
----
include::{sourcedir}/transformMap.java[]
----
ifdef::solution[]
.Lösung
[source,java]
----
include::{sourcedir}/transformMap_solution.java[]
----
endif::solution[]
== Standardbibliothek
=== Bildbearbeitung
......@@ -211,41 +62,8 @@ Experimentieren Sie ein wenig mit den Möglichkeiten dieser Bibliotheksklassen u
+
_Tipp: Die Differenzen im Bild werden in der Regel nicht sehr groß sein. Wenn Sie sich das Ergebnis wieder als Bilddatei anzeigen lassen wollen ist es also sinnvoll, die Graustufenwerte vorher mit einem Faktor (z.B. Faktor 10 oder 20) zu multiplizieren._
== Knobelaufgaben
=== Generischer Stack
Sie haben generische Klassen mit Typparametern schon in Form der Collection-API kennengelernt und verwendet, aber wie funktioniert das eigentlich? Wie kann man eine eigene generische Klasse definieren?
Genau das sollen Sie in dieser Übung lernen. Dazu schauen wir uns zunächst Auszüge aus der Definition der Klasse `ArrayList` an. Auslassungen sind dabei mit dem Ausdruck `[...]` gekennzeichnet.
[source,java]
----
include::{sourcedir}/arrayList.java[]
----
<1> Hier wird definiert, dass zu der Klasse `ArrayList` ein Typparameter namens `E` gehört. Wird die Klasse einfach nur als `ArrayList` verwendet, verhält sie sich so als würde man jedes `E` durch ein `Object` austauschen. Eine `ArrayList<Integer>` dagegen arbeitet mit `Integer`-Objekten, wo in der Definition ein `E` vorkommt.
<2> Der Parameter `E` kann überall auftauchen wo Klassennamen verwendet werden: Als Typ einer Variable oder eines Parameters oder als Rückgabetyp. Natürlich kann man von einem Objekt von einem unbekannten bzw. beliebigen Typ `E` auch nur Methoden aufrufen, die _jedes_ Objekt in Java besitzt - also alles was in der Klasse `Objekt` definiert ist. Für die `ArrayList` reicht das völlig, da mit den Objekten ja nichts anderes geschieht als dass sie in ein Array gesteckt oder wieder aus diesem herausgeholt werden.
<3> Es wirkt etwas seltsam, dass das interne Array nicht vom Typ `E[]` statt vom Typ `Object[]` ist. Es spricht nichts dagegen eine Variable oder ein Feld vom Typ `E[]` zu definieren, aber in Java ist das Erzeugen von generischen Arrays etwas komplizierter. Das hat damit zu tun, dass Typparameter vom Java-Compiler entfernt und durch den Typ `Object` (mit entsprechenden Typcasts an den relevanten Stellen) ersetzt werden.
Für ihre Klasse `Stack` bzw. `Stapel` aus den vorherigen Übungsblättern ist die Situation sogar noch etwas einfacher. Schaffen Sie es analog zu dem Beispiel der `ArrayList` auch einen generischen `Stack` zu bauen, so dass z.B. die folgenden Aufrufe möglich werden?
----
Stack<String> stringStack = new Stack<String>("First");
stringStack.push("Second");
Stack<Double> doubleStack = new Stack<Double>(Double.NaN);
doubleStack.push(3.5);
double d = doubleStack.pop();
----
ifdef::solution[]
.Lösung
[source,java]
----
include::{sourcedir}/genericStack.java[]
----
<1> Hier haben wir einen Trick verwendet um unser generisches Array zu erzeugen, der auch in der Methode `toArray` von `ArrayList` zu sehen ist: Wir verlangen vom Benutzer, dass er uns ein Array des korrekten Typs übergibt, damit wir und mit `Arrays.newInstance` ein Array vom gleichen Typ erzeugen können. Man kann die Implementierung noch etwas effizienter machen indem man direkt das übergebene Array zum speichern der Werte verwendet, falls es groß genug ist.
<2> Auch Methoden können eigene Typparameter besitzen. Bei dieser statischen Methode ist das nötig, da bei dem Aufruf von `Stack.fromArray` kein Objekt vom Typ `Stack` beteiligt ist von dem man den Typparameter ableiten könnte.
endif::solution[]
////
Ideen:
......
......@@ -126,25 +126,6 @@ Speichern Sie sich das https://moodle.thm.de/mod/forum/view.php?id=142571[Nachri
Implementieren Sie eine einfache https://en.wikipedia.org/wiki/Caesar_cipher[Caesar-Verschlüsselung] indem Sie einen übergebenen String erst in Kleinbuchstaben umwandeln und dann alle darin enthaltenen Buchstaben von 'a' bis 'z' um eine vorgegebene Schrittzahl im Alphabet verschieben. Wenn ein Buchstabe dabei über das 'z' hinaus verschoben werden müsste, beginnt man wieder bei 'a'.
=== Suche in Collections
Schreiben Sie eine Methode `find`, die ein Objekt vom Typ `java.util.Collection<Integer>` und ein Objekt vom Typ `Integer` übernimmt und den Index zurückgibt, an dem der gesuchte Integer in der Collection steht. Wird der Integer nicht gefunden, soll der Wert `-1` zurückgegeben werden.
[TIP]
====
Das Interface `Collection` ist zu allgemein um einen Zugriff über einen Index zu erlauben. Stattdessen müssen Sie hier mit Iteratoren (oder einer foreach-Schleife) arbeiten.
====
==== Bonusaufgabe: Suche in generischen Collections
Schaffen Sie es, ihre Methode so zu erweitern, dass sie für einen beliebigen Typen `E` funktioniert statt nur für `Integer`?
=== Binäre Suche
Wenn man schon weiß, dass eine Liste oder ein Array sortiert ist, kann man einen deutlich performanteren Algorithmus zum Suchen eines Elements verwenden als einfach jedes Element zu überprüfen. Bei dieser _binären Suche_ schaut man sich immer das Element in der Mitte des noch zu durchsuchenden Bereichs an. Entweder dieses Element ist schon das gesuchte Element, oder man muss nur noch links davon (wenn das gesuchte Element kleiner ist) oder rechts davon (wenn das gesuchte Element größer ist) weitersuchen.
Implementieren Sie die binäre Suche in `Collection`-Objekten als eigene Methode `binarySearch`.
== BigInteger und BigDecimal
=== Hoher PowerTower
......@@ -177,9 +158,6 @@ endif::solution[]
// == Zeit und Datum
// == Collections und Streams
////
Ideen (CS):
......
// https://docs.oracle.com/javase/8/docs/api/java/util/Stack.html
// https://docs.oracle.com/javase/9/docs/api/java/util/Stack.html
enum Head { LEFT, RIGHT, NONE }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment