Hilfe. Generics und BiFunctions

stefan_jj

Mitglied
Hallo liebe Community,

ich habe folgendes Problem. Und zwar blicke ich generell noch immer nicht ganz bei Generics durch.

Die unten angeführte Methode soll Einträge in Gruppen einteilen. Sie verwendet zwei Funktionsparameter:
Der erste Funktionsparameter groupingFn berechnet aus den Keys und Werten der Einträge einen Wert (von Typ G) für die Gruppierung.
Der zweite Funktionsparameter valueFn berechnet dann den Wert (von Typ R), der in die Gruppierung aufgenommen werden soll.
Ergebnis ist eine Map mit den Werten aus G für die Keys und Listen mit Werten von Typ R. Das heißt, für alle Einträge, die gleichen Gruppierungswert liefern, werden die Ergebnisse der Funktion valueFn in die entsprechende Liste aufgenommen.

Nun mein Problem ist, dass ich gerade etwas auf der Leitung stehe und nicht durchblicke wie ich die beiden Funktionsparameter aufrufen kann. (1x Key berechnen, 1x Wert berechnen) Genauer gesagt, ich verstehe nicht wie die Werte für die Gruppierungen berechnet werden können.

Eigentlich dürfte das ja nicht allzu schwer sein. Ich gehe davon aus, das sollte doch mit einer forEach-Schleife und den beiden Funktionsparameter-Aufrufen zu lösen sein.

Bei Bedarf kann ich auch das gesamte Interface reinstellen.

danke und lg.


Java:
    // group
    public default <G, R> Map<G, List<R>> group(BiFunction<? super K, ? super V, G> groupingFn,
            BiFunction<? super K, ? super V, R> valueFn) {
        Map<G, List<R>> resultMap = new ArrayMap<>();
        for (Entry<K, V> element : this) {
           ...
        }
    }
 
Eine BiFunction ist nichts anderes als eine Funktion mit 2 Eingabewerten (In deinem Fall vom Typ K und V) und einem Rückgabewert (in deinem Fall einmal vom Typ G und einmal von Typ R).

Eine BiFunction ist z.B. das:
Java:
public static Integer addiereLaengeZweierStrings(String s1, String s2) {
  // ...
}

Diese Methode lässt sich so als BiFunction darstellen:
Java:
BiFunction<String, String, Integer> biFunc = (String s1, String s2) -> /* ... */;

Oder auch referenziert
Java:
BiFunction<String, String, Integer> biFunc = addiereLaengeZweierStrings;

Achtung, eine Klassen-Methode (non-static) hat ihre aufrufende Klasse auch als "Parameter"
Java:
class A {
  public B getB (A otherA) { // ist auch eine BiFunction<A, A, B>
    return /* ... */
  }
}

Ich gehe davon aus, dass K und V Key und Value Werte sind. Übergibst der group Methode für beide Parameter eine Funktion mit Key und Value als Parameter, die irgendeinen anderen Wert erzeugen und diesen dann entsprechend in die Map eintragen.

Beispiel
Du hast eine Mitarbeiterabfrage gemacht, wer welche Speise zur Weihnachtsfeier essen möchte. Du hast dann das (vermutlich Map-ähnliche) Objekt
Java:
DeineGenerischeKlasse<Mitarbeiter, Gericht> weihnachtsessen;

Der Mitarbeiter ist dein Key K und dein Gericht das Value V. Du brauchst nun 2 Methoden, die Mitarbeiter und Gericht "verbrauchen" und dazu einen neuen Wert generieren. Angenommen der Mitarbeiter hat eine Vorliebe zu Rotwein oder Weißwein. Das Gericht hat einen Rotwein und einen Weißwein zur Auswahl, welcher dann mit serviert wird. Du möchtest jetzt wissen welchen Wein (ist dann G) du für welche Mitarbeiter servieren möchtest (R = K // spricht nichts dagegen).

Java:
Map<Wein, List<Mitarbeiter>> weineServierenAn =  weihnachtsessen.group(
  (mitarbeiter, gericht) -> mitarbeiter.getVorliebeWein.equals("weiß") ? gericht.getWeißwein() : gericht.getRotwein(), // du extrahierst den Wein aus dem Gericht mit der Vorliebe des Mitarbeiters
  (mitarbeiter, gericht) -> mitarbeiter
);

Letztlich definierst du nur mit diesen Parametern wie du an die Werte für G und R kommst. Den Rest übernimmt ja die gezeigte Implementierung.
 
Zuletzt bearbeitet:
Eine BiFunction ist nichts anderes als eine Funktion mit 2 Eingabewerten (In deinem Fall vom Typ K und V) und einem Rückgabewert (in deinem Fall einmal vom Typ G und einmal von Typ R).

Eine BiFunction ist z.B. das:
Java:
public static Integer addiereLaengeZweierStrings(String s1, String s2) {
  // ...
}

Diese Methode lässt sich so als BiFunction darstellen:
Java:
BiFunction<String, String, Integer> biFunc = (String s1, String s2) -> /* ... */;

Oder auch referenziert
Java:
BiFunction<String, String, Integer> biFunc = addiereLaengeZweierStrings;

Achtung, eine Klassen-Methode (non-static) hat ihre aufrufende Klasse auch als "Parameter"
Java:
class A {
  public B getB (A otherA) { // ist auch eine BiFunction<A, A, B>
    return /* ... */
  }
}

Ich gehe davon aus, dass K und V Key und Value Werte sind. Übergibst der group Methode für beide Parameter eine Funktion mit Key und Value als Parameter, die irgendeinen anderen Wert erzeugen und diesen dann entsprechend in die Map eintragen.

Beispiel
Du hast eine Mitarbeiterabfrage gemacht, wer welche Speise zur Weihnachtsfeier essen möchte. Du hast dann das (vermutlich Map-ähnliche) Objekt
Java:
DeineGenerischeKlasse<Mitarbeiter, Gericht> weihnachtsessen;

Der Mitarbeiter ist dein Key K und dein Gericht das Value V. Du brauchst nun 2 Methoden, die Mitarbeiter und Gericht "verbrauchen" und dazu einen neuen Wert generieren. Angenommen der Mitarbeiter hat eine Vorliebe zu Rotwein oder Weißwein. Das Gericht hat einen Rotwein und einen Weißwein zur Auswahl, welcher dann mit serviert wird. Du möchtest jetzt wissen welchen Wein (ist dann G) du für welche Mitarbeiter servieren möchtest (R = K // spricht nichts dagegen).

Java:
Map<Wein, List<Mitarbeiter>> weineServierenAn =  weihnachtsessen.group(
  (mitarbeiter, gericht) -> mitarbeiter.getVorliebeWein.equals("weiß") ? gericht.getWeißwein() : gericht.getRotwein(), // du extrahierst den Wein aus dem Gericht mit der Vorliebe des Mitarbeiters
  (mitarbeiter, gericht) -> mitarbeiter
);

Letztlich definierst du nur mit diesen Parametern wie du an die Werte für G und R kommst. Den Rest übernimmt ja die gezeigte Implementierung.

Ahaaaaa. Nun wird mir einiges klarer. Dh. in meinem Fall würde groupingFn den Schlüssel (key) der neuen Map liefern und valueFn die Werte der Liste. Sowohl groupingFn als auch valueFn können vom Typ K als auch vom Typ V sein (oder beides).

Beispiel.: Studenten - Klausurnote
Java:
gegeben ist eine Map mit <Student, Punkte>

Map<Student, Punkte>
K -> V:
Student -> Punkte

bspw.:
groupingFn: (*, Punkte) -> Note
valueFn: (Student, *) -> Student
Note -> List<Student> dh. Map<Note, List<Student>>

groupingFn: (Student, *) -> Student
valueFn: (*, Punkte) -> Note
Student -> List<Note> dh. Map<Student, List<Note>>

groupingFn: (*, Punkte) -> Punkte
valueFn: (*, Punkte) -> Note
Punkte -> List<Note> dh. Map<Punkte, List<Note>>
 
Dh. in meinem Fall würde groupingFn den Schlüssel (key) der neuen Map liefern und valueFn die Werte der Liste.
Ja.

Sowohl groupingFn als auch valueFn können vom Typ K als auch vom Typ V sein (oder beides).
Beide Funktionen bekommeen sowohl K (Student) als auch V (Punkte) als Parameter. Unter Umständen wird einer dieser Parameter aber nicht verwendet. Das wird durch ein * in deiner weiteren Ausführung indiziert. (*, Punkte) -> Note sagt nur, dass nur die Punkte in Betracht gezogen werden um die Note zu generieren, nicht jedoch der Schüler. Der Schüler ist trotzdem Teil der BiFunction.
 
Ja.


Beide Funktionen bekommeen sowohl K (Student) als auch V (Punkte) als Parameter. Unter Umständen wird einer dieser Parameter aber nicht verwendet. Das wird durch ein * in deiner weiteren Ausführung indiziert. (*, Punkte) -> Note sagt nur, dass nur die Punkte in Betracht gezogen werden um die Note zu generieren, nicht jedoch der Schüler. Der Schüler ist trotzdem Teil der BiFunction.

danke. Jetzt macht es Sinn =)
 
Jetzt könnte man sich noch UseCases überlegen wo sowohl Punkte als auch Schüler Einfluss haben auf das Resultat. Eventuell bei Sport (Geschlecht des Schülers) oder so 😀
 
Ich hätte eine Frage zu deinem BiFunction-Beispiel.:

folgendes Szenario:
Java:
Map<String, List<Integer>> records = new ArrayMap<>();

records.put("Huber",   List.of( 8, 12, 22, 21, 20,  0, 17, 19,  3, 18, 16));


Ich möchte mithilfe einer LambdaExpression zählen wie oft Huber mehr wie 8 Punkte erreicht hat.
Java:
Map<String, Integer> nrSolved = records.map((x, y) -> x + y.filter(y.size() > 8));


folgende Methoden sollen benutzt werden.....
Java:
     /**
     * Returns a list with the elements of this list
     * which pass the test of the given predicate function.
     * @param predicate the test function
     * @return the list with the elements passing the test
     */
public default List<E> filter(Predicate<? super E> predicate) {
        List<E> result = createResultList();
        for (E e : this) {
            if (predicate.test(e)) {
                result.add(e);
            }
        }
        return result;
    }
    
    /**
     * Returns the number of elements of this list
     *
     * @return the number of elements
     */
public int size();

ich verstehe leider nicht ganz wie diese beiden Methoden benutzt werden müssen. Ich muss filter ja einen Wert mitgeben.
Nur bekomme ich leider immer einen Fehler.

lg
 
Wie genau ist diese mapping Funktion implementiert? Das müsste man wissen um die Frage ganz genau beantworten zu können.

Ich verstehe leider nicht ganz wie diese beiden Methoden benutzt werden müssen. Ich muss filter ja einen Wert mitgeben.

Denke an die Seite, welche ich dir gelinkt habe. https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/function/package-summary.html

Ein Predicate ist eine Funktion, welche einen Wert erhält (bei dir vom Typ E) und einen boolean (das ist fest für ein Predicate definiert) zurückgibt. Das hier wäre z.B. ein Predicate:
Java:
public static boolean istEineGeradeZahl(int n) {
  return n % 2 == 0;
}
Diese Funktion lässt sich auf diese Weise als Predicate in einem Parameter übergeben:
Java:
testeEineZahlAufEineEigenschaft((int n) -> n % 2 == 0);

y.size() > 8 ist bei dir nicht ausreichend. Denke daran, dass y die Liste ist. Du willst aber die Listenelemente testen. Das Predicate erhält als input das Listenelement. Es muss heißen y.filter((Integer element) -> element > 8). Das Ergebnis dieser Filterung sollte eine kleinere Liste sein, diese enthält die Elemente > 8. Die kannst du mit size() dann zählen, also: y.filter((Integer element) -> element > 8).size().
 
Zuletzt bearbeitet:

Neue Themen


Zurück
Oben