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

Das Collec­tions Frame­work

Das Paket java.util bie­tet mit dem Collec­tions Frame­work ge­ne­ri­sche Da­ten­ty­pen für Samm­lun­gen15 gleich­ar­ti­ger Ob­jek­te an. Für die meis­ten An­wen­dun­gen fin­den sich hier Schnitt­stel­len und Im­ple­men­tie­run­gen. Es ist daher sel­ten not­wen­dig, selbst kom­ple­xe ge­ne­ri­sche Da­ten­ty­pen zu de­kla­rie­ren. Als Aus­gangs­punkt für die Suche nach dem „rich­ti­gen“ Da­ten­typ bie­tet sich fol­gen­de Seite in der Java-Do­ku­men­ta­ti­on an:

https://​docs.​ora­cle.​com/​en/​java/​ja­va­se/​11/​docs/​api/​java.​base/​java/​util/​doc-​files/​coll-​re­fe­rence.​html

Eben­so gibt es ein of­fi­zi­el­les Tu­to­ri­al zum Frame­work: https://​docs.​ora­cle.​com/​ja­va­se/​tu­to­ri­al/​collec­tions/​index.​html

Die Do­ku­men­ta­ti­on der je­wei­li­gen Schnitt­stel­len ist aus­führ­lich genug, dass Schü­le­rin­nen und Schü­ler der Kurs­stu­fe nach Ein­füh­rung der ge­ne­ri­schen Da­ten­ty­pen und einer Er­klä­rung zu In­ter­faces, diese ohne wei­te­re Hilfs­mit­tel nut­zen kön­nen.

In Ab­bil­dung 7 sind die wich­tigs­ten In­ter­faces und Klas­sen dar­ge­stellt. Die­ses Klas­sen­dia­gramm kann im Un­ter­richt zur Ver­fü­gung ge­stellt wer­den. Wenn ge­nü­gend Zeit ist, kann der Kurs einen Teil davon selbst durch Re­cher­che in der Do­ku­men­ta­ti­on ent­wer­fen.

Programmierparadigmen

Ab­bil­dung 7: Klas­sen­dia­gramm des Collec­tions Frame­work ZPG In­for­ma­tik [CC BY-SA 4.0 DE], aus 01_hin­ter­grund.pdf, be­ar­bei­tet

„Collec­tion“ gilt als Wur­zel-In­ter­face der Collec­tion Hier­ar­chie. Al­ler­dings ist es für den Un­ter­richt wich­tig zu­erst das Iterable In­ter­face ge­nau­er zu be­trach­ten. Dabei kann auch die Ar­beit mit der Do­ku­men­ta­ti­on ex­em­pla­risch vor­ge­führt wer­den.

Ite­ra­ti­on und er­wei­ter­te for-Schlei­fe

Na­he­zu alle Klas­sen des Collec­tion Frame­work im­ple­men­tie­ren die Iterable Schnitt­stel­le. Diese bil­det die Grund­la­ge für das Durch­lau­fen aller Ele­men­te einer Samm­lung.

Diese Schnitt­stel­le ist recht ein­fach und schreibt le­dig­lich drei Me­tho­den vor:

void forEach(Consumer<? Super T> action)
Iterator<T> iterator() // Iteration
Spliterator<T> spliterator() // Traversierung und Partitionierung

Die erste wird zu­sam­men mit Lamb­da­aus­drü­cken ge­nutzt. Die zwei­te lie­fert einen Ite­ra­tor, der mit Schlei­fen (while, for und er­wei­ter­tes for) ge­nutzt wird. Die letz­te wird für den Un­ter­richt nicht be­nö­tigt.

Im Un­ter­richt soll­te zu­nächst auf den Ite­ra­tor ein­ge­gan­gen wer­den. Ein ge­mein­sa­mer Blick in die Do­ku­men­ta­ti­on zeigt, dass diese Schnitt­stel­le vier Me­tho­den vor­schreibt. Zwei wer­den für den wei­te­ren Un­ter­richts­gang be­nö­tigt:

boolean hasNext() //Gibt true zurück, falls noch unbesuchte Elemente
E next() //Besucht das nächste Element und gibt es zurück

Es wird klar, dass eine Samm­lung durch­lau­fen wer­den kann, indem man sich zu­nächst den Ite­ra­tor zu­rück­ge­ben lässt und mit die­sem Schritt für Schritt alle Ele­men­te be­sucht bzw. durch­läuft. Für fol­gen­de Bei­spie­le sei eine Samm­lung Collection<String> c be­lie­bi­ger Zei­chen­ket­ten ge­ge­ben, wel­che der Reihe nach aus­ge­ge­ben wer­den sol­len.

Ite­ra­ti­on mit while-Schlei­fe:

Iterator<String> iter = c.iterator();
 while( iter.hasNext() ){
     System.out.println( "Wert= " + iter.next() );
}

Ite­ra­ti­on mit for-Schlei­fe:

for( Iterator<String> it = c.iter(); iter.hasNext(); ){
     System.out.println( "Wert= " + iter.next() );
}

Ver­ein­fach­te No­ta­ti­on mit der er­wei­ter­ten for-Schlei­fe:

for( String s : c ){ //Sprich: für jeden String s in c
     System.out.println( "Wert= " + s );
}

Da im Bei­spiel­pro­jekt FilmsammlungGeneric<E> ein Sub­typ von ArrayList<E> ist, im­ple­men­tiert diese auch die nö­ti­ge Iterable Schnitt­stel­le. Sol­len bei­spiels­wei­se für eine FilmsammlungGeneric<Film> fsG die Titel aller ent­hal­te­nen Filme aus­ge­ge­ben wer­den, geht dies mit der er­wei­ter­ten for-Schlei­fe wie folgt:

for( Film f : fsG ){// Für jeden Film f in der Filmsammlung fsG
     System.out.println( f.getTitel() );
 }

Lamb­da­aus­drü­cke

Java ist keine funk­tio­na­le Pro­gram­mier­spra­che, un­ter­stützt aber seit Ver­si­on 8 ei­ni­ge As­pek­te funk­tio­na­ler Pro­gram­mie­rung. In Java sind Me­tho­den immer an ein Ob­jekt ge­bun­den, auf das über eine Re­fe­renz zu­ge­grif­fen wird. In einer funk­tio­na­len Spra­che kön­nen Funk­tio­nen als Ar­gu­men­te über­ge­ben wer­den. Dies ist in Java nicht mög­lich, da es keine Re­fe­renz auf Me­tho­den gibt.

Al­ler­dings ent­spricht ein Ob­jekt ohne At­tri­bu­te mit nur einer ein­zi­gen Me­tho­de im We­sent­li­chen einer Funk­ti­on. Die Re­fe­renz auf die­ses Ob­jekt kann als Re­fe­renz auf die ent­hal­te­ne Me­tho­de auf­ge­fasst wer­den. De­kla­riert man einen Typ durch ein In­ter­face oder eine abs­trak­te Klas­se mit nur einer ein­zi­gen Me­tho­de, nennt die­sen auch SAM-Typ (engl. sin­gle ab­stract me­thod). Ist ein for­ma­ler Wert­pa­ra­me­ter von einem sol­chen SAM-Typ, kann ihm als kon­kre­tes Ar­gu­ment jedes Ob­jekt über­ge­ben wer­den, das den SAM-Typ er­wei­tert. Das Ar­gu­ment kann aus einer be­nann­ten Klas­se, aber auch über eine an­ony­me Klas­se in­stan­zi­iert wer­den. In bei­den Fäl­len ent­steht Boi­ler­pla­te16 . Um die damit ver­bun­de­ne Schreib­ar­beit zu ver­mei­den und den Quell­code les­ba­rer zu ge­stal­ten, stellt Java so­ge­nann­te funk­tio­na­le In­ter­faces und Lamb­da­aus­drü­cke zur Ver­fü­gung.

Ein Funk­tio­na­les In­ter­face ist mit der An­no­ta­ti­on @FunctionalInterface ver­se­hen und be­sitzt nur eine ein­zi­ge abs­trak­te Me­tho­de, die funk­tio­na­le Me­tho­de ge­nannt wird. Ein sol­che funk­tio­na­le Schnitt­stel­le lie­fert den Ziel­typ für einen Lamb­da­aus­druck.

Ein Lamb­da­aus­druck ist eine No­ta­ti­on für an­ony­me (na­men­lo­se) Ob­jek­te, die ein funk­tio­na­les In­ter­face im­ple­men­tie­ren. Sie lesen sich somit wie die di­rek­te De­kla­ra­ti­on einer Funk­tio­nen.

Die all­ge­mei­ne Syn­tax für einen Lamb­da­aus­druck lau­tet:

( Parameterliste ) -> { Ausdruck oder Anweisungen }

Der Lamb­da­aus­druck wird als kon­kre­tes Ar­gu­ment für einen for­ma­len Pa­ra­me­ter vom Typ eines funk­tio­na­len In­ter­faces über­ge­ben. So­wohl der Rück­ga­be­wert des Aus­drucks, als auch die Typ­an­ga­ben in der Pa­ra­me­ter­lis­te wer­den durch Typ­in­fe­renz au­to­ma­tisch ab­ge­lei­tet und kön­nen daher weg­ge­las­sen wer­den.

Für die Ziel­ty­pen der Lamb­da­aus­drü­cke stellt das Paket java.util.function17 ge­nü­gend ge­ne­ri­sche funk­tio­na­le In­ter­faces zur Ver­fü­gung. Auch hier soll­ten die Schü­le­rin­nen und Schü­ler di­rekt mit der Do­ku­men­ta­ti­on ar­bei­ten, um pas­sen­de In­ter­faces zu einer Auf­ga­be zu fin­den.

Bei­spie­le für Lamb­da­aus­drü­cke18 :

Code Schnip­sel Er­läu­te­rung In­ter­face mit funk­tio­na­ler Me­tho­de Typ des for­ma­len Pa­ra­me­ters
( File f ) -> {
                      return f.isFile();}
                   
Bei An­wei­sun­gen müs­sen ge­schweif­te Klam­mern ge­setzt wer­den.
Predicate<T>{
                          boolean test(T t)}
                         Predicate<File>
                       
double x -> 2 * x
                     
Bei Aus­drü­cken ent­fal­len die rech­ten ge­schweif­ten Klam­mern. Bei nur einem Ar­gu­ment zudem die run­den Klam­mern.
ToDoubleFunction<T>{
                            double applyAsDouble(
                             T value)}
                           ToDoubleFunction<double>
                         
() -> "blupp"
                           
Eine leere Pa­ra­me­ter­lis­te ist er­laubt.
Supplier<T>{ T get()}
                              Supplier<String>
                              
( int x, int y ) -> x + y
                                 
Es kön­nen meh­re­re Pa­ra­me­ter de­kla­riert wer­den.
IntBinarayOperator{
                                   int applyAsInt(int
                                  left,
                                   int right)}
                                  IntBinaryOperator
( x, y ) -> x + y
                                       
Auch bei meh­re­ren Pa­ra­me­tern kön­nen die Typ­an­ga­ben ent­fal­len.
( x, y ) ->
                                    (x>y )?x:y
                                 
Der ?:-Ope­ra­tor gilt als ein ein­zi­ger Aus­druck und die rech­ten ge­schweif­ten Klam­mern ent­fal­len.
( x, y ) -> {
                                          if( x > y )return x;
                                          if( y > x ) return y;
                                          return 0;}
                                       
An­wei­sungs­se­quen­zen sind mög­lich.

Lamb­da­aus­drü­cke kön­nen in meh­re­ren Kon­tex­ten ver­wen­det wer­den, z.b. in Zu­wei­sun­gen, Me­tho­den­auf­ru­fen, und Typ­kon­ver­tie­run­gen:

// Zuweisungs-Kontext
Predicate<String> p = String::isEmpty;
// Methodenaufruf-Kontext stream.filter(e -> e.getSize() > 10)...
// Cast-Kontext (Typkonvertierung) stream.map((ToIntFunction) e -> e.getSize())...

Ite­ra­ti­on mit Lamb­da-Aus­drü­cken

Die Iterable Schnitt­stel­le schreibt fol­gen­de Me­tho­de vor, wel­che als Wert­pa­ra­me­ter das funk­tio­na­le In­ter­face Consumer<T> de­kla­riert:

void forEach(Consumer<? super T> action)

Diese Me­tho­de kann dem­nach mit einem Lamb­da­aus­druck auf­ge­ru­fen wer­den. Dabei wird der Lamb­da­aus­druck auf jedes Ele­ment der Samm­lung (wel­che die Schnitt­stel­le im­ple­men­tiert) an­ge­wandt.

Für fol­gen­des Bei­spiel sei eine Samm­lung Collection<String> c; be­lie­bi­ger Zei­chen­ket­ten ge­ge­ben, wel­che der Reihe nach aus­ge­ge­ben wer­den sol­len.

c.forEach( s → { System.out.println( "Wert= " + s ) } );

Mit dem Lamb­da­aus­druck wird der Code für die Ite­ra­ti­on über die Samm­lung sehr ein­fach und zudem für einen Men­schen gut les­bar.

Sol­len bei­spiels­wei­se für eine FilmsammlungGeneric<Film> fsG die Titel aller ent­hal­te­nen Filme aus­ge­ge­ben wer­den geht das mit einer ein­zi­gen Zeile Code:

fsG.forEach(f -> { System.out.println( f.getTitel() ); });

Soll­te beim Ent­wi­ckeln der Film­da­ten­bank FilmsammlungGeneric auf eine an­de­re Collec­tion (z.B. LinkedList statt ArrayList) um­ge­stellt wer­den, dann funk­tio­niert das Bei­spiel immer noch und zwar ohne Än­de­rung der Syn­tax. Sol­len statt dem Titel die Be­wer­tun­gen aller Filme aus­ge­ge­ben wer­den, muss nicht etwa FilmsammlungGeneric um eine Me­tho­de hier­für er­gänzt, son­dern le­dig­lich der Lamb­da­aus­druck an­ge­passt wer­den. Dies er­höht die Wie­der­ver­wend­bar­keit und Fle­xi­bi­li­tät von Code dras­tisch.

Streams

Neben den Lamb­da­aus­drü­cken wur­den mit Java Ver­si­on 8 mit dem Paket java.​util.​stream mäch­ti­ge Schnitt­stel­len für Ope­ra­tio­nen auf Rei­hun­gen und Samm­lun­gen ein­ge­führt. Da eine ge­naue Be­hand­lung den Rah­men des Bil­dungs­plans über­schrei­tet, wird hier auf die of­fi­zi­el­le Do­ku­men­ta­ti­on ver­wie­sen, die „Streams“ recht gut er­klärt.

https://​docs.​ora­cle.​com/​en/​java/​ja­va­se/​11/​docs/​api/​java.​base/​java/​util/​stream/​pa­cka­ge-​sum­ma­ry.​html

Das Collection<E>In­ter­face de­fi­niert die Me­tho­de Stream<E> stream(), wel­che die Ele­men­te der Samm­lung als Strom von Re­fe­ren­zen dar­stellt. Die­ser er­laubt es, ver­ket­te­te Ope­ra­tio­nen auf die­sen Re­fe­ren­zen nach­ein­an­der oder par­al­lel aus­zu­füh­ren. Wie bei funk­tio­na­ler Pro­gram­mie­rung wer­den die Daten, die durch die Re­fe­ren­zen re­prä­sen­tiert wer­den, durch den Stream selbst nicht ver­än­dert.19

Von der Schnitt­stel­le Stream<E> wird keine Im­ple­men­tie­rung mit­ge­lie­fert. Dies braucht es auch nicht, da die von ihr de­fi­nier­ten Me­tho­den meist Lamb­da­aus­drü­cke über­ge­ben be­kom­men. Die Me­tho­den wer­den in zwei Haupt­ka­te­go­ri­en ein­ge­teilt:

  • in­ter­me­diä­re Ope­ra­tio­nen (in­ter­me­dia­te ope­ra­ti­ons) lie­fern wie­der­um einen Stream, der wei­ter­ver­ar­bei­tet wer­den kann (z.B. filter(), map(), distinct(), sorted(), etc.).
  • ter­mi­na­le Ope­ra­tio­nen (ter­mi­nal ope­ra­ti­ons) füh­ren ih­rer­seits Ope­ra­tio­nen auf den Re­fe­ren­zen des Streams aus (forEach(), reduce(), toArray(), etc.). Sie kön­nen einen Wert lie­fern und schlie­ßen den Strom. Da­nach kön­nen keine wei­te­ren Ope­ra­tio­nen auf ihm aus­ge­führt wer­den.

Neh­men wir bei­spiels­wei­se eine FilmsammlungGeneric<Film> fsG, kann der Film­ti­tel mit der schlech­tes­ten Be­wer­tung in einer Zeile Code be­stimmt wer­den:

fsG.stream().reduce(fsG.get(0),
       (min, t) -> (t.getBewertung() < min.getBewertung()) ? t : min)
     ).getTitel();

Oft wer­den die Me­tho­den nach dem Mus­ter Fil­ter-Map-Re­du­ce auf einen Stream an­ge­wandt.

  • Fil­ter: Zu­nächst wer­den ge­wünsch­te Ele­men­te aus dem Stream aus­ge­wählt. Zum Bei­spiel mit filter()
  • Map: Die Ele­men­te des Streams wer­den trans­for­miert. Zum Bei­spiel mit map(), mapToInt()
  • Re­du­ce: Der Stream wird auf ein End­er­geb­nis re­du­ziert. Zum Bei­spiel mit reduce(), sum()

Die An­zahl aller Filme der Samm­lung mit einer Be­wer­tung bes­ser als 7.0 er­hält man mit:

fsG.stream().filter( f -> f.getBewertung()>7.0 ).mapToInt(f -> 1).sum() ); 

 

15 Siehe auch Fuß­no­te 1 auf Seite 2.

16 Unter Boi­ler­pla­te wer­den hier Code­schnip­sel ohne ei­ge­ne Funk­tio­na­li­tät ver­stan­den, wel­che bei­na­he un­ver­än­dert an vie­len Stel­len im Code ein­ge­fügt wer­den müs­sen.

17 https://​docs.​ora­cle.​com/​en/​java/​ja­va­se/​11/​docs/​api/​java.​base/​java/​util/​func­tion/​pa­cka­ge-​sum­ma­ry.​html (aus­ge­wer­tet am 22.12.20)

18 https://​www.​tors­ten-​horn.​de/​tech­docs/​java-​lamb­das.​htm (aus­ge­wer­tet am 22.12.2020) er­gänzt um rech­te Spal­te.

19 https://​jav​abeg​inne​rs.​de/​Ar­rays_​und_​Ver­wand­tes/​Streams.​php (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 Quel­len