Funktionen übergeben

#1

Ich brauche eine Methode, der ich beliebig viele Funktionen übergeben kann, welche (alle/jede) ein int bekommen und ein double zurückgeben. (Deklaration/Definition der Methode)

Kann mir dabei jemand unter die Arme greifen?

#2
  1. Definiere Dir ein funktionales Interface mit passender Signatur (double intToDouble(int i))
  2. Kreiere die gewünschte Methode mit dem funktionalen Interface als Parameter, ggf. Varargs oder Array von Methoden.
  3. Nun kannst Du die Methode mit Lambdas, Instanzen des Interfaces oder Methoden-Referenzen aufrufen.
#3

Dankeschön [emoji5] Wenn ich ein Interface bräuchte, dann brauche ich doch gar nicht diese neuen Java 8-Funktionalitäten oder?

#4

Eine Liste von https://docs.oracle.com/javase/8/docs/api/java/util/function/IntToDoubleFunction.html - Instanzen…

#5

Sowas?

public void foo(Function<Integer, Double> ... functions) {
    ...
}
#6

Es gibt doch die Spezialisierung mit IntToDoubleFunction?!

(Ich hatte mal einen Satz (“mathematischer”) Funktionen als Function<Double,Double> zusammengebaut, und mich dann gewundert, warum das Programm dauernd mehrere Sekunden lang komplett stillzustehen schien: Ein verbose:gc und ein Blick in die jVisualVM brachten das ans Licht, was man sich eigentlich schon hätte denken können: Er hat immer wieder Full GCs gemacht, in denen er Millionen und Abermillionen von Double-Instanzen weggeräumt hat (und sehr viele davon waren 0.0 … ))

#7

Hab jetzt Folgendes:

        //...
    }
}

interface IntToDoubleInterface {

    String getName();

    double intToDouble(int i);
}```

Und ein Aufruf, der da lautet:

```        zeichne(0, 70, 800, 400, "title", "x", "y", "aPng.png", new IntToDoubleInterface() {
            @Override
            public String getName() {
                return "Line 1";
            }

            @Override
            public double intToDouble(int i) {
                //return kompliziert;
            }
        },
                new IntToDoubleInterface() {
            @Override
            public String getName() {
                return "Line 2";
            }

            @Override
            public double intToDouble(int i) {
                //return kompliziert;
            }
        }, new IntToDoubleInterface() {
            @Override
            public String getName() {
                return "Line 3";
            }

            @Override
            public double intToDouble(int i) {
                //return kompliziert;
            }
        });```

Das muss sich mit den neuen Java 8-Funktionalitäten doch auch "eleganter" schreiben lassen.
Zudem gefällt mir das getName() noch nicht, da möchte ich ein zweites varargs übergeben. :rolleyes:
#8

Würdet ihr sagen, die Parameterliste von zeichne (oder auch foo, bar, baz…) ist anwender-/entwicklerfreundlich? (Anzahl d. Parameter, Reihenfolge d. Parameter)?

Was möchte ich eigentlich machen? Ich möchte mit nur einer Methode (mehrere) Line Chart(s) [mit beliebiger Größe usw.] zeichnen!

Ich lese mich jetzt ein in funktionales Interface.

Edit:
Antwort gerade gefunden:
Java 8 Lambda Expressions - what about multiple methods in nested class - Stack Overflow

From JLS 9.8

A functional interface is an interface that has just one abstract method, and thus represents a single function contract.

Lambdas require these functional interfaces so are restricted to their single method. Anonymous interfaces still need to be used for implementing multi-method interfaces.

oop - Java 8 lambdas with multiple interface methods - Stack Overflow

No, there isn’t. If I understood your question correctly you’d want to use lambdas for interfaces with more than one abstract method. In that case the answer is negative:

A functional interface is any interface that contains only one abstract method.

Das ist leider schade… Oder sieht jemand noch eine Möglichkeit?

Nochmal ein Edit: Wäre es so leichter verständlich?:

        zeichne(0, 70, 800, 400, "title", "foo", "bar", "baz.png",
                new GetNameInterface[]{
                    () -> "first",
                    () -> "second",
                    () -> "third"},
                new IntToDoubleInterface[]{
                    i -> 7.99 + (i * 0.37),
                    i -> {if (i <= 30) {
                            return 19.95; // ndg
                        } else {
                            return (0.25 * (i - 30) + 19.95);
                        }},
                    i -> {if (i <= 50) {
                            return 24.95; // ndg
                        } else {
                            return (0.19 * (i - 50) + 24.95);
                        }}});
    }

/* ... */

interface GetNameInterface {
     String getName();
}

interface IntToDoubleInterface {
     double intToDouble(int i);
}```
Ich glaube, XXXSupplier wäre als Bezeichnen "richtiger".
#9
    public static void main(String[] args)  {
        zeichne(0, 70, 800, 400, "title", "x", "y", "aPng.png", // Kontrolle Zeilenumbruch
                build(() -> "Line 1", i -> i * 2.0), // bzw. jeweils nur den Namensstring übergeben, hier wohl keine Funktion nötig
                build(() -> "Line 2", i -> i * 4.0), //
                build(() -> "Line 3", i -> i * 6.0) //
        );
    }

    public static IntToDoubleInterface build(Supplier<String> functName, Function<Integer, Double> functDouble)  {
        return new IntToDoubleInterface()    {

                @Override
                public String getName()   {
                    return functName.get();
                }

                @Override
                public double intToDouble(int i)     {
                    return functDouble.apply(i);
                }
            };
    }

    public static void zeichne(int start, int end, int w, int h, String title, String x, String y, String fileName,    IntToDoubleInterface... values)  {
        for (IntToDoubleInterface v : values)     {
            System.out.println("next: " + v.getName() + ", " + v.intToDouble(5));
        }
    }
}


interface IntToDoubleInterface {

    String getName();

    double intToDouble(int i);
}
#10

@SlaterB : Puh, das muss ich erst mal verstehen… Danke für Deine Antwort! :daumen:

#11

Natürlich, wie immer. Nachdem diese Frage beantwortet wurde taucht plötzlich ein getName() auf, das noch nie zuvor erwähnt wurde.

#12

Das ist mir erst im Laufe des agilen Entwicklungsprozesses eingefallen…

Edit: Sonst hätte ich aber keine Fragen.

Edit 2: Außerdem ist die Überschrift doch sehr allgemein, außerdem musste so kein neues Thema geröffnet werden.

*** Edit ***

Sorry wenn ich jetzt nochmal nerve oder es absurd wird, aber ich hab SlaterB’s Methode jetzt einfach “übernommen” und das kommt bei raus:


    public static void main(String[] args) throws IOException, InterruptedException {
        zeichne(0, 70, 800, 400, "title", "foo", "bar", "bazzz.png",
                build(() -> "first" , i -> 7.99 + (i * 0.37)),
                build(() -> "second", i -> {
                    if (i <= 30) {
                        return 19.95; // ndg
                    } else {
                        return (0.25 * (i - 30) + 19.95);
                    }
                }),
                build(() -> "third" , i -> {
                    if (i <= 50) {
                        return 24.95; // ndg
                    } else {
                        return (0.19 * (i - 50) + 24.95);
                    }
                }));
    }

    public static void zeichne(int start, int end, int w, int h, String title, String x, String y, String fileName, IntToDoubleInterface... functions) throws IOException, InterruptedException {
        XYSeriesCollection dataset = new XYSeriesCollection();
        for (IntToDoubleInterface function : functions) {
            XYSeries sery = new XYSeries(function.getName());
            for (int i = start; i < end; i++) {
                sery.add(i, function.intToDouble(i));
            }
            dataset.addSeries(sery);
        }
        // Chart zeichnen...
        // Zeichnung speichern...
    }

    public static IntToDoubleInterface build(Supplier<String> functName, Function<Integer, Double> functDouble) {
        return new IntToDoubleInterface() {

            @Override
            public String getName() {
                return functName.get();
            }

            @Override
            public double intToDouble(int i) {
                return functDouble.apply(i);
            }
        };
    }
}

interface IntToDoubleInterface {

    String getName();

    double intToDouble(int i);
}```

In main() hab ich bei dem Aufruf wieder 4 schließende Klammern, aber dafür die Sicherheit, das immer name-func-Paare "übergeben" werden. (Ist das eigentlich Typsicherheit?)

Fällt euch jetzt noch etwas Gravierendes auf, oder könnte ich das so lassen?

Äh, könnte das Interface abstraktes inneres Interface werden, weil so beschwert er sich noch, "Exporting non-public type through public API"....
#13

wenn nirgendwo sonst gebraucht ginge hier eine LinkedHashMap (Linked für Einfügereihenfolge)

        map.put("Line 4", i -> i * 2.0);
        map.put("Line 2", i -> i * 4.0);
        map.put("Line 3", i -> i * 6.0);

        zeichne(0, 70, 800, 400, "title", "x", "y", "aPng.png", map);

ohne Interface, build und mit mehr Ordnung


OO-sauberer könnte ein größerer Neuaufbau mit eigenen Container für die XYSeriesCollection (oder austauschbar anderes darin)

  • zusätzliche Daten zum Zeichen + Hilfsmethoden für Series sein
        c.addSeries("Line 4", i -> i * 2.0); // führt gleich Schleife zur Erstellung XYSeries aus oder merkt sich nur Funktion in interner Liste von Funktionen
        c.addSeries("Line 2", i -> i * 4.0);
        c.addSeries("Line 3", i -> i * 6.0);
        zeichne(c);
#14

Dass in dem “IntToDoubleInterface” nun ein “getName” steht, und es damit kein Functional Interface mehr ist, ist etwas häßlich. Wenn es mehr ist, als eine Funktion, dann sollte es einen Namen haben. Z.B. “Chart”.

Vermeintliche Details, wie die Frage, warum in der “build”-Methode für den Namen ein Supplier übergeben wird, könnte man jetzt auch diskutieren. Ist es eine konkrete, technische Anforderung, diesen Namen nachträglich “von außen” (durch Änderung des Verhaltens des Suppliers) ändern zu können? Selbst wenn ja: Da wird der Supplier nicht reichen. Niemand kriegt mit, ob sich das ändert, was der Supplier zurückgibt. Da wäre noch irgendwo eine Art “refresh” nötig, aber bevor man das irgendwie reindengelt, sollte man sich genau klar machen, WAS man da eigentlich modellieren will…

Ansonsten … die “zeichne”-Funktion ist etwas, wofür ich sowas wie einen Builder oder irgendwas anderes fluent-mäßiges in Betracht ziehen würde. Abgesehen davon, dass

// Chart zeichnen...
// Zeichnung speichern...

aussieht, als könnte man das im Sinne von “Single Responsibility” weiter runterbrechen.

Chart chartA = build("chartA", i -> i*2);
Chart chartB = build("chartB", i -> i*3);
Chart chartC = build("chartC", i -> i*4);

BufferedImage chartImage = ChartPainters
    .create("title")
    .setSize(500,300).
    .setLabelX("x")
    .setLabelY("y")
    .addCharts(chartA, chartB, chartC);
    .paint();

ImageIO.write(...chartImage...);

oder so…

#15

Wenn es ein Performance-Problem ist sind die Spezialisierungen natürlich sinnvoll, aber wenn es nur um ein paar Funktionen geht, finde ich das einfach unhandlich. Vergleichbar mit Streams, wo man mit einem IntStream oder so kaum etwas vernünftiges anfangen kann, und dann doch wieder die geboxte Version verwenden muss. Wird wirklich langsam Zeit für Project Valhalla, aber ob ich das noch erlebe :grampa:

#16

Man könnte nun drüber streiten, ob IntToDoubleFunction oder Function<Integer, Double> einfacher, übersichtlicher oder mit weniger clutter behaftet ist. Und sicher wäre bei einem “einfachen Funktionspoltter” der Garbage noch überschaubar: 800 Pixel = 800 Double-Objekte. Aber das kann recht schnell ausufern…

#17

Ich verstehe nicht, was ihr mit neuen Objekten meint (800 neue Objekte…). Ich habe doch nur 3 bzw. 6 Funktionen-Objekte.
Könntet ihr zwei Sequenzdiagramme zeichnen, was beim Aufruf von … geschieht?:
Zeile 25: XYSeries sery = new XYSeries(function.getName());
und
Zeile 27: sery.add(i, function.intToDouble(i));
?

getName() liefert doch einfach 3 mal einen hardcoded String zurück.

intToDouble() liefert doch einfach das nächste double zurück (primitiver Typ). Insgesamt 800 einfache “Berechnungen”.

(Oder hat das etwas mit IntToDoubleFunction und StringSupplier vs. Function<Typ1[, Typ2]> zu tun?)
[HR][/HR]
Entwicklerfreundlichkeit: Die Signatur bleibt jetzt so:
zeichne(int start, int end, int w, int h, String title, String x, String y, String fileName, IntToDoubleInterface... functions)
start inklusive
end exklusive
w width
h hight
title Überschrift
x x-Achse Beschriftung
y y-Achse Beschriftung
fileName Zu speichern unter … (nicht Fielmann :wink: )
functions Funktionen, für die der Line chart gezeichnet werden soll
[HR][/HR]
Wenn ich Funktionen vorher einer Sammlung hinzufüge, hätte ich nicht mehr den praktischen 1-Methoden-Aufruf.

#18

Auch dieser “praktische 1-Methoden-Aufruf” sollte intern runtergebrochen werden, in den Aufruf von einigen wenigen privaten Methoden. Aber natürlich muss man das nicht.

Bei den 800 Objekten ging es um den Fall, dass man eine Function<Integer,Double> verwenden würde. Hier nicht relevant.

#19

eigentlich hast du ja eine Liste von Funktionen und solltest einen Collector schreiben, der diese in einer Zeichnung einsammelt

#20

Bleiglanz wird mich killen, aber jetzt kommt noch eine neue Anforderung hinzu, die aber nicht im Widerspruch zur Überschrift steht.

Auf dem Weg zum most user-friendly tool/program möchte ich jetzt alle Funktionen per/via Kommandozeilen-Argument (in nur einer Zeile) übergeben!

Gesucht ist also (quasi) die Umkehrung von Reflection. Teile des Programms sollen also erst zur Laufzeit “eingefügt” werden".

Sicherheitsbedenken habe ich keine, da “böswillige Eingaben” eines nicht bei mir laufenden Programms nicht meiner Umgebung schaden würd.

Was meint/sagt ihr dazu?