Zur Haupt­na­vi­ga­ti­on sprin­gen [Alt]+[0] Zum Sei­ten­in­halt sprin­gen [Alt]+[1]

Ge­ne­ri­sche Da­ten­ty­pen

Not­wen­dig­keit für ge­ne­ri­sche Da­ten­ty­pen an­hand eines Bei­spiel­pro­jekts

Eine der fun­da­men­ta­len Idee der In­for­ma­tik ist die Abs­trak­ti­on zur Ver­mei­dung von Du­pli­ka­ten. Ent­spre­chend gilt das Man­tra:

„Wenn Du zwei Pro­gramm­tei­le siehst, die sich nur an we­ni­gen Stel­len un­ter­schei­den und die in­halt­lich ver­wandt sind, ab­stra­hie­re!“

Werk­zeu­ge der struk­tu­rie­ren, ob­jekt­ori­en­tier­ten Pro­gram­mie­rung sind hier­für bei­spiels­wei­se Klas­sen, Me­tho­den und Schlei­fen. Aber selbst unter wei­test­ge­hen­der Nut­zung die­ser Mit­tel kann es dazu kom­men, dass es Me­tho­den bzw. Klas­sen gibt, die sich nur in ihrer Si­gna­tur und der darin ver­wen­de­ten Typen un­ter­schei­den. Häu­fig fin­det man sol­che Dop­pe­lun­gen bei Samm­lun­gen gleich­ar­ti­ger Ob­jek­te.

Programmierparadigmen

Ab­bil­dung 2: Klas­sen­dia­gramm des Pro­jek­tes „Film­samm­lung“ von ZPG In­for­ma­tik [CC BY-SA 4.0 DE], aus 01_hin­ter­grund.pdf

Als Bei­spiel dient uns eine Filmsammlung (be­trach­te das zu­ge­hö­ri­ge Klas­sen­dia­gramm in Ab­bil­dung 2), die so­wohl Kinofilme als auch Serien er­fasst. Eine Serie ist dabei wie­der­um eine Samm­lung von SerienEpisoden, deren An­zahl be­stimmt wer­den kann. So­wohl Filmen als auch SerienEpisoden kön­nen eine Be­wer­tung be­kom­men. Die Be­wer­tung einer Serie be­stimmt sich aber aus den Be­wer­tun­gen der ent­hal­te­nen SerienEpisoden.

Die Film­samm­lung kann nun auf ein­fachs­te Weise als eine Rei­hung im­ple­men­tiert wer­den, in die Filme ein­ge­tra­gen wer­den kön­nen. Durch Po­ly­mor­phie ist so­wohl ein Kinofilm, eine Serie, als auch eine SerienEpisode ein Film. In­stan­zen die­ser Klas­sen kön­nen dem­nach pro­blem­los einer Film­samm­lung hin­zu­ge­fügt wer­den. Im Be­gleit­ma­te­ri­al ist eine sol­che ein­fa­che Im­ple­men­tie­rung mit­ge­ge­ben.

Für die fol­gen­den Er­läu­te­run­gen wer­den als Bei­spiel­ob­jek­te die Se­ri­en s1 und s2 mit Epi­so­den e1 bis e4 ge­nutzt. In Ab­bil­dung 3 sind diese in einem Ob­jekt­dia­gramm dar­ge­stellt.

Programmierparadigmen

Ab­bil­dung 3: Bei­spiel­ob­jek­te in einer Film­samm­lung von ZPG In­for­ma­tik [CC BY-SA 4.0 DE], aus 01_hin­ter­grund.pdf

Be­trach­te nun fol­gen­den ge­kürz­ten Aus­schnitt eines Pro­gramms

1| Filmsammlung fs = new Filmsammlung( new Film[]{s1, s2, e1} );
2| fs.get(1).setBewertung( 0.0 );
3| ...println( fs.get(0).getTitel()
4| ...println( fs.get(0).getAnzahlEpisoden() );
5| ...println( ( (Serie) fs.get(0) ).getAnzahlEpisoden() );
6| ...println( ( (Serie) fs.get(2) ).getAnzahlEpisoden() );
  • In­Zei­le2­wird­durch­Po­ly­mor­phie­die­über­schrie­be­ne­Me­tho­de­der­Se­rie­auf­g­e­r­u­f­en. Dort kann si­cher­ge­stellt wer­den, dass die Be­wer­tung nicht ver­än­dert wird.
  • In Zeile 3 fin­det eine im­pli­zi­te er­wei­tern­de Ty­pum­wand­lung (Serie → Film) statt. Es wird der Titel der Serie aus­ge­ge­ben.
  • Zeile 4 führt zu einem Kom­pi­lie­rungs­feh­ler, da eine ein­schrän­ken­de Um­wand­lung (Film → Serie) durch­ge­führt wer­den müss­te, die in Java eine ex­pli­zi­te Typ­kon­ver­tie­rung er­for­dert.
  • Damit die An­zahl der Epi­so­den aus­ge­ge­ben wer­den kann, muss in Zeile 5 die Typ­kon­ver­tie­rung (Film → Serie) an­ge­ge­ben wer­den. Es fin­det dann eine ex­pli­zi­te ein­schrän­ken­de Ty­pum­wand­lung statt. Da es sich bei s1 tat­säch­lich um eine Serie han­delt, wird die An­zahl Epi­so­den wie ge­wünscht aus­ge­ge­ben.
  • Wenn­al­ler­dings­wiein­Zei­le6ei­ne­Typ­kon­ver­tie­rung(Film→Serie)auf­ei­nen­fal­schen Sub­typ an­ge­wandt wird, kommt es zu einem Lauf­zeit­feh­ler: Es wird eine Clas­s­Cas­tEx­cep­ti­on ge­wor­fen. Das Ob­jekt e1 ist eine SerienEpisode, wel­che nicht in di­rek­ter Ver­wandt­schaft zu einer Serie steht.

Der Lauf­zeit­feh­ler bei der Typ­kon­ver­tie­rung kann ver­mie­den wer­den, indem man für Se­ri­en­epi­so­den eine ei­ge­ne neue Klas­se Episodensammlung schreibt. Diese un­ter­schei­det sich dann von der Filmsammlung nur in der Si­gna­tur (siehe Ab­bil­dung 4). Die Im­ple­men­tie­rung wäre na­he­zu iden­tisch.

Programmierparadigmen

Ab­bil­dung 4: Film­samm­lung und Epi­so­den­samm­lung: bei­na­he iden­tisch von ZPG In­for­ma­tik [CC BY-SA 4.0 DE], aus 01_hin­ter­grund.pdf

Mit Ver­si­on 5 der JDK wurde Java um ge­ne­ri­sche Da­ten­ty­pen er­wei­tert. Diese er­mög­li­chen es, bei der Pro­gram­mie­rung einer Klas­se von den ent­hal­te­nen Da­ten­ty­pen zu ab­stra­hie­ren, sich bei der Nut­zung aber trotz­dem auf einen Typ zu be­schrän­ken.

Ein­fa­che ge­ne­ri­sche Typen de­kla­rie­ren

Zum Ver­ständ­nis der Syn­tax folgt ein klei­ner Aus­zug aus den De­fi­ni­tio­nen der Schnitt­stel­len List und Iterator im Paket java.util:


public interface List <E> {
    void add(E x);
    Iterator<E> iterator();
}

public interface Iterator<E> {
    E next();
    boolean hasNext();
}

Neu in die­sem Code sind die An­ga­ben in den spit­zen Klam­mern. Damit wer­den die for­ma­len Typ­pa­ra­me­ter der bei­den Schnitt­stel­len List und Iterator de­kla­riert.

For­ma­le Typ­pa­ra­me­ter kön­nen, bis auf we­ni­ge Aus­nah­men, über­all dort ver­wen­det wer­den, wo man nor­ma­le Typen ver­wen­den würde.

Wird die Filmsammlung auf diese Weise pa­ra­me­tri­siert, sieht das Klas­sen­dia­gramm aus wie Ab­bil­dung 5. Ver­gleicht man mit der Si­gna­tur des ge­ne­ri­schen Typs ArrayList<E> aus dem Paket java.util, fällt auf, dass FilmsammlungGeneric<E> le­dig­lich an­de­re Kon­struk­to­ren und we­ni­ge zu­sätz­li­che Me­tho­den hat, an­sons­ten aber die selbe Funk­tio­na­li­tät for­dert. Bei der Im­ple­men­tie­rung er­wei­tert man daher am bes­ten ein­fach den ge­ne­ri­schen Typ.

public class FilmsammlungGeneric<E> extends ArrayList<E>
Programmierparadigmen

Ab­bil­dung 5: Klas­sen­dia­gramm der ge­ne­ri­schen Typen FilmsammlungGeneric<E> und ArrayList<E> von ZPG In­for­ma­tik [CC BY-SA 4.0 DE], aus 01_hin­ter­grund.pdf, be­ar­bei­tet

Es ist auch mög­lich meh­re­re for­ma­le Typ­pa­ra­me­ter zu de­kla­rie­ren, diese wer­den dann in­ner­halb der spit­zen Klam­mern Komma-ge­trennt auf­ge­lis­tet.

public interface Map<K,V>{…

Ein­fa­che ge­ne­ri­sche Typen nut­zen

Mit Hilfe selbst de­kla­rier­ter oder be­reits in Bi­blio­the­ken vor­han­de­ner ge­ne­ri­scher Typen kann be­reits im Pro­gramm­code vor­ge­ge­ben wer­den, wel­che kon­kre­ten Typ­pa­ra­me­ter er­laubt sind. Feh­ler wer­den dann be­reits bei der Kom­pi­lie­rung er­kannt und es kommt nicht zu einem Lauf­zeit­feh­ler, wie zuvor im Bei­spiel er­läu­tert. Da­durch wird Typ­si­cher­heit ge­währ­leis­tet.

Bei der Nut­zung ge­ne­ri­scher Typen wird ana­log der Nut­zung nor­ma­ler Typen vor­ge­gan­gen.

1|	ArrayList l1 = new ArrayList();
2|	ArrayList l2 = new ArrayList<>(); // Typinferenz
3|	ArrayList l3 = new ArrayList(); // Laufzeitfehler möglich
4|	ArrayList l4 = new ArrayList(); // Laufzeitfehler möglich
5|	ArrayList l5 = new ArrayList(); //Kompilierungsfehler

Dabei soll­te immer die Syn­tax wie in Zeile 1 oder 2 ver­wen­det wer­den. Wird wie in Zeile 3 und 4 nur der Ori­gi­nal­typ (ohne spit­ze Klam­mern) no­tiert, dann wird Typ­si­cher­heit nicht bei der Kom­pi­lie­rung über­prüft son­dern zur Lauf­zeit ver­la­gert. Das Ver­hal­ten ist dann iden­tisch zum nicht pa­ra­me­tri­sier­ten Ori­gi­nal­typ. Es kön­nen Lauf­zeit­zeit­feh­ler an Stel­len im Pro­gramm­code auf­tre­ten, an denen das je­wei­li­ge Ob­jekt ver­wen­det wird.

Zeile 2 ist er­laubt, da hier durch Typ­in­fe­renz der kon­kre­te Typ­pa­ra­me­ter (in den spit­zen Klam­mern) ab­ge­lei­tet wird. Man nennt die zwei spit­zen Klam­mern ohne kon­kre­ten Typ­pa­ra­me­ter auch den Dia­mant-Ope­ra­tor <>. Die­ser kann immer dann ver­wen­det wer­den, wenn Typ­in­fe­renz mög­lich ist.

Zeile 5 schlägt fehl, da in ge­ne­ri­schen Typen nur Re­fe­renz­ty­pen als kon­kre­te Typ­pa­ra­me­ter er­laubt sind. Pri­mi­ti­ve Da­ten­ty­pen wie int müs­sen zu­nächst durch Boxing in einen Re­fe­renz­typ ge­packt wer­den.

Be­trach­te nun den kur­zen Pro­gramm­aus­schnitt, dies­mal mit ge­ne­ri­schem Da­ten­typ

1| FilmsammlungGeneric serien =
    new FilmsammlungGeneric<>( new Serie[]{s1, s2} );
2| serien.get(1).setBewertung( 0.0 );
3| ...println( serien.get(0).getTitel() );
4| ...println( serien.get(0).getAnzahlEpisoden() );

Zeile 2 funk­tio­niert nach wie vor mit dem sel­ben Er­geb­nis.

Auch Zeile 3 lie­fert das glei­che Er­geb­nis, al­ler­dings ist keine Ty­pum­wand­lung not­wen­dig. Die von der Su­per­klas­se ge­erb­te Me­tho­de wird auf­ge­ru­fen.

In Zeile 4 ist weder eine ex­pli­zi­te Typ­kon­ver­tie­rung, noch eine im­pli­zi­te Ty­pum­wand­lung nötig, da si­cher ge­stellt ist, dass es sich um ein Ob­jekt des Typs Serie han­delt.

Für das Ver­ständ­nis der ge­ne­ri­schen Typen und der Feh­ler­mel­dun­gen, die beim Pro­gram­mie­ren auf­tre­ten kön­nen, ist es im Un­ter­richt not­wen­dig, Fach­be­grif­fe klar zu de­fi­nie­ren und durch­gän­gig rich­tig an­zu­wen­den. Lei­der wur­den in der deut­schen Li­te­ra­tur die eng­li­schen Be­grif­fe viel­fach un­ter­schied­lich und teils falsch bzw. miss­ver­ständ­lich über­setzt. Ta­bel­le 3 fasst die in die­sem Do­ku­ment ver­wen­de­ten Be­grif­fe zu­sam­men. Ich emp­feh­le aus­schließ­lich diese deut­schen Be­grif­fe oder die eng­li­schen Ori­gi­nal­be­grif­fe zu ver­wen­den.

Programmierparadigmen

Ta­bel­le 3: Fach­be­grif­fe zu ge­ne­ri­schen Typen von ZPG In­for­ma­tik [CC BY-SA 4.0 DE], aus 01_hin­ter­grund.pdf, be­ar­bei­tet

Ge­ne­ri­sche Typen und Ver­er­bung10

In­ner­halb einer Ver­er­bungs­hier­ar­chie kann ein Ob­jekt aus un­ter­schied­li­chen Sich­ten be­trach­tet wer­den. So kann ein Ob­jekt eines Typs dann einem Ob­jekt eines an­de­ren Typs zu­ge­wie­sen wer­den, wenn diese kom­pa­ti­bel sind, also eine Ty­pum­wand­lung mög­lich ist. Für Re­fe­renz­ty­pen ist dies i.A. dann der Fall, wenn eine Ver­er­bungs­be­zie­hung be­steht.

Ge­ne­ri­sche Typen kön­nen ge­nau­so wie nor­ma­le Typen er­wei­tert wer­den. Falls bei der Nut­zung die kon­kre­ten Typ­pa­ra­me­ter nicht va­ri­iert wer­den, bleibt die Sub­typ-Be­zie­hung be­ste­hen.

Gehe von fol­gen­der Si­tua­ti­on aus:

// Klassen Signaturen
final class Integer extends Number
final class Double extends Number
class BigBox extends Box
// Methoden Signaturen public void numberTest( Number n ); public void boxTest( Box b );
Die fol­gen­den Zei­len Code kom­pi­lie­ren bis auf die letz­te ohne Feh­ler.
Object einObjekt;
Integer einInteger = new Integer(10);
einObjekt = einInteger; // OK: Integer ist Subtyp von Object
numberTest( new Integer(10)); // OK
Box box = new Box(); box.add( new Integer(10) ); // OK Box bbox = new BigBox; // OK boxTest( new Box() ); // OK boxTest( new BigBox() ); //OK
boxTest( new Box() ); // FEHLER

Die letz­te Zeile führt zu einem Kom­pi­lie­rungs­feh­ler, da Box<Integer> kein Sub­typ von Box<Number> ist, ob­wohl Integer ein Sub­typ von Number ist (siehe Ab­bil­dung 6).

Programmierparadigmen

Ab­bil­dung 6: Bei­spiel mög­li­cher Ver­er­bungs­hier­ar­chi­en bei ge­ne­ri­schen Typen von ZPG In­for­ma­tik [CC BY-SA 4.0 DE], aus 01_hin­ter­grund.pdf

Dies liegt daran, wie ge­ne­ri­sche Typen vom Com­pi­ler ver­ar­bei­tet wer­den (Type Era­su­re11 ). Der ge­ne­risch de­kla­rier­te Typ wird in eine ein­zi­ge class-Datei kom­pi­liert, so wie jede an­de­re Klas­se oder In­ter­face. Es gibt nicht meh­re­re Ver­sio­nen des Codes für ver­schie­de­ne kon­kre­te Typ­pa­ra­me­ter: weder im Code, noch zur Lauf­zeit. Jede In­stanz einer ge­ne­ri­schen Klas­se teilt sich die selbe Klas­se un­ab­hän­gig vom im Code ver­wen­de­ten kon­kre­ten Typ­pa­ra­me­ter.

Typ­pa­ra­me­ter sind ana­log zu den nor­ma­len Pa­ra­me­tern, die in Me­tho­den oder Kon­struk­to­ren ver­wen­det wer­den. Ähn­lich wie eine Me­tho­de for­ma­le Wert­pa­ra­me­ter hat, die die Art von Wer­ten be­schrei­ben, mit denen sie ar­bei­tet, hat eine ge­ne­ri­sche De­kla­ra­ti­on for­ma­le Typ­pa­ra­me­ter.

Wenn eine Me­tho­de auf­ge­ru­fen wird, wer­den die for­ma­len Pa­ra­me­ter durch kon­kre­te Ar­gu­men­te er­setzt, und der Me­tho­den­kör­per wird aus­ge­wer­tet. Wenn eine ge­ne­ri­sche De­kla­ra­ti­on auf­ge­ru­fen wird, wer­den die for­ma­len Typ­pa­ra­me­ter durch kon­kre­te Ar­gu­men­te er­setzt. For­ma­le Typ­pa­ra­me­ter wer­den nach Auf­ruf weder bei der Kom­pi­lie­rung, noch zur Lauf­zeit durch kon­kre­te Typ­pa­ra­me­ter er­setzt, son­dern durch die kon­kre­ten Typar­gu­men­te.12

Ein­schrän­ken der Typ­pa­ra­me­ter über Bounds

Im Bei­spiel­pro­jekt er­mög­licht der ge­ne­ri­sche Typ FilmsammlungGeneric<E> Typ­si­cher­heit und ver­mei­det dop­pel­ten Code. Die Me­tho­de getBewertung() einer Film­samm­lung soll den Durch­schnitt der Be­wer­tun­gen aller Ele­men­te in der Samm­lung zu­rück geben. Die Ob­jek­te in der Samm­lung – deren Typ durch den kon­kre­ten Typ­pa­ra­me­ter vor­ge­ge­ben ist – müs­sen dem­nach selbst eine Me­tho­de getBewertung() haben.

Al­ler­dings kann bei der In­stan­zi­ie­rung jeder be­lie­bi­ge kon­kre­te Typ­pa­ra­me­ter ver­wen­det wer­den. Der Com­pi­ler kann beim Par­sen der ge­ne­ri­schen Klas­se des­halb nicht wis­sen, ob zur Lauf­zeit nur sol­che kon­kre­ten Typar­gu­men­te vor­lie­gen, die die Me­tho­de getBewertung() haben. Damit das Pro­gramm ohne Feh­ler com­pi­liert, muss über einen so­ge­nann­ten Bound si­cher­ge­stellt wer­den, dass nur sol­che kon­kre­ten Typ­pa­ra­me­ter er­laubt sind, die die Me­tho­de getBewertung() be­reit­stel­len. Im Bei­spiel sind das all die­je­ni­gen Typen, die Sub­typ von Film sind. Film stellt in der Ver­er­bungs­hier­ar­chie eine obere Schran­ke, den so­ge­nann­ten upper Bound, dar. Im Pro­gramm­code wird dies durch das Schlüs­sel­wort extends er­reicht, wobei dies so­wohl er­wei­tern (wie bei Klas­sen) oder im­ple­men­tie­ren (wie bei Schnitt­stel­len) be­deu­tet. Der Typ­pa­ra­me­ter mit Bound lau­tet dann:

FilmsammlungGeneric<E extends Film>

Nun sind für E noch die Typen Film, Kinofilm, Serie und Serienepisode er­laubt.

Als Bound dür­fen so­wohl Klas­sen als auch Schnitt­stel­len ver­wen­det wer­den. Bei der Ver­wen­dung von Schnitt­stel­len ist es sogar mög­lich, meh­re­re Bounds an­zu­ge­ben. Ist einer der Bounds eine Klas­se, muss diese als ers­tes an­ge­ge­ben wer­den. Zum Bei­spiel:13

class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

 

class D <T extends A & B & C>{ /* ... */ }

Ge­ne­ri­sche Me­tho­den

Auch Me­tho­den kön­nen ge­ne­risch de­fi­niert wer­den. Die Syn­tax lau­tet:

public <K, V> boolean compare( Pair<K, V> p1, Pair<K, V> p2 );

Dabei steht eine Liste der for­ma­len Typ­pa­ra­me­ter in spit­zen Klam­mern vor dem Rück­ga­be­wert der Me­tho­de. Beim Auf­ruf der Me­tho­de müs­sen die kon­kre­ten Typ­pa­ra­me­ter nicht an­ge­ge­ben wer­den, son­dern der Com­pi­ler lei­tet sie au­to­ma­tisch ab (Typ­in­fe­renz).

Nicht mit ge­ne­ri­schen Me­tho­den zu ver­wech­seln sind Me­tho­den, die Typ­va­ria­blen aus einer sie um­ge­ben­den ge­ne­ri­schen Klas­se in ihrer De­fi­ni­ti­on haben:

public E set (int index, E element)

Sie sind nicht ge­ne­risch, weil der Typ­pa­ra­me­ter E an an­de­rer Stel­le fest­ge­legt wird und hier gar nicht mehr frei („ge­ne­risch“) ist. Im Un­ter­richt müs­sen ge­ne­ri­sche Me­tho­den nicht be­han­delt wer­den. Die Schü­le­rin­nen und Schü­ler soll­ten le­dig­lich die Syn­tax der Si­gna­tur einer ge­ne­ri­schen Me­tho­de ken­nen. Die­ser be­geg­nen sie bei der Ar­beit mit der of­fi­zi­el­len Java- Do­ku­men­ta­ti­on.

Bei ge­ne­ri­schen Me­tho­den spie­len neben upper Bounds auch lower Bounds und Wild­cards eine Rolle, auf diese wird hier aber nicht näher ein­ge­gan­gen. Es emp­fiehlt sich das Stu­di­um der ent­spre­chen­den Quel­len.14

 

10https://​docs.​ora­cle.​com/​ja­va­se/​tu­to­ri­al/​java/​ge­ne­rics/​in­heri­t­an­ce.​html (aus­ge­wer­tet am 22.12.2020)

11https://​docs.​ora­cle.​com/​ja­va­se/​tu­to­ri­al/​java/​ge­ne­rics/​era­su­re.​html (aus­ge­wer­tet am 22.12.2020)

12 https://​docs.​ora­cle.​com/​ja­va­se/​tu­to­ri­al/​extra/​ge­ne­rics/​intro.​html (Ora­cle, Gilad Bracha, The Ja­vaTM Tu­to­ri­als Les­son: Ge­ne­rics, aus­ge­wer­tet am 22.12.2020)

13https://​docs.​ora­cle.​com/​ja­va­se/​tu­to­ri­al/​java/​ge­ne­rics/​boun­ded.​html (aus­ge­wer­tet am 22.12.2020)

14https://​docs.​ora­cle.​com/​ja­va­se/​tu­to­ri­al/​java/​ge­ne­rics/​me­thods.​html (aus­ge­wer­tet am 22.12.2020)

 

Hin­ter­grund­in­for­ma­tio­nen: Her­un­ter­la­den [odt][370 KB]

Hin­ter­grund­in­for­ma­tio­nen: Her­un­ter­la­den [pdf][480 KB]

 

Wei­ter zu Collec­tions Frame­work