Funktionszeiger in Java

148

Dies mag allgemein und trivial sein, aber ich habe anscheinend Probleme, eine konkrete Antwort zu finden. In C # gibt es ein Konzept von Delegaten, das sich stark auf die Idee von Funktionszeigern aus C ++ bezieht. Gibt es eine ähnliche Funktionalität in Java? Was ist der beste Weg, da Zeiger etwas fehlen? Und um klar zu sein, wir sprechen hier erstklassig.

Dreadwail
quelle
1
Ich bin nur neugierig, warum Sie dies möchten, wenn Listener oder andere OOP-Konstrukte dieselbe Aufgabe ausführen und dabei ihre Objektnatur beibehalten. Ich meine, ich verstehe die Notwendigkeit der Funktionalität, die das Konzept bietet, nur damit es mit einem einfachen Objekt erreicht werden kann. Es sei denn, ich vermisse natürlich etwas, daher stelle ich diese Frage! :-)
Newtopian
2
Java 8 hat Lambda-Ausdrücke: docs.oracle.com/javase/tutorial/java/javaOO/… Vielleicht möchten Sie das überprüfen. Kein richtiger Funktionszeiger, könnte aber dennoch von größerem Nutzen sein.
FrustratedWithFormsDesigner
6
Java 8-Methodenreferenzen sind genau das, wonach Sie fragen.
Steven
8
Java 8- Methodenreferenzen sind genau das, wonach Sie fragen. this::myMethodist semantisch dasselbe wie ein Lambda zu erstellen paramA, paramB -> this.myMethod(paramA, paramB).
Steven

Antworten:

126

Das Java-Idiom für funktionszeigerähnliche Funktionen ist eine anonyme Klasse, die eine Schnittstelle implementiert, z

Collections.sort(list, new Comparator<MyClass>(){
    public int compare(MyClass a, MyClass b)
    {
        // compare objects
    }
});

Update: Das Obige ist in Java-Versionen vor Java 8 erforderlich. Jetzt haben wir viel schönere Alternativen, nämlich Lambdas:

list.sort((a, b) -> a.isGreaterThan(b));

und Methodenreferenzen:

list.sort(MyClass::isGreaterThan);
Michael Borgwardt
quelle
2
Das Strategie-Entwurfsmuster. Nicht so hässlich wie ein C- oder C ++ - Funktionszeiger, wenn die Funktion einige nicht globale Daten verwenden soll, die nicht als Argument für die Funktion bereitgestellt werden.
Raedwald
75
@Raedwald C ++ hat Funktoren und C ++ 0x hat Verschlüsse und Lambdas, die auf Funktoren aufgebaut sind. Es hat sogar std::bind, was Parameter an Funktionen bindet und ein aufrufbares Objekt zurückgibt. Ich kann nicht aus diesen Gründen verteidigen C, aber C ++ wirklich ist besser als Java für diesen.
Zneak
21
@zneak, @Raedwald: Sie können dies in C tun, indem Sie einen Zeiger auf Benutzerdaten übergeben (z. B. struct). (und kann es mit jeder neuen Stufe der Rückruf-Indirektion kapseln) Es ist eigentlich ziemlich sauber.
Brice
25
Ja, ich werde jeden Tag C-Funktionszeiger über Crufty-Wrapper-Klassen nehmen
BT
3
Java 8 hat hierfür eine bessere Syntax.
Thorbjørn Ravn Andersen
65

Sie können einen Funktionszeiger durch eine Schnittstelle ersetzen. Nehmen wir an, Sie möchten eine Sammlung durchlaufen und mit jedem Element etwas tun.

public interface IFunction {
  public void execute(Object o);
}

Dies ist die Schnittstelle, die wir an einige sagen könnten, CollectionUtils2.doFunc (Collection c, IFunction f).

public static void doFunc(Collection c, IFunction f) {
   for (Object o : c) {
      f.execute(o);
   }
}

Angenommen, wir haben eine Sammlung von Zahlen, und Sie möchten jedem Element 1 hinzufügen.

CollectionUtils2.doFunc(List numbers, new IFunction() {
    public void execute(Object o) {
       Integer anInt = (Integer) o;
       anInt++;
    }
});
Björn Raupach
quelle
42

Sie können Reflexion verwenden, um es zu tun.

Übergeben Sie als Parameter das Objekt und den Methodennamen (als Zeichenfolge) und rufen Sie dann die Methode auf. Beispielsweise:

Object methodCaller(Object theObject, String methodName) {
   return theObject.getClass().getMethod(methodName).invoke(theObject);
   // Catch the exceptions
}

Und dann verwenden Sie es wie in:

String theDescription = methodCaller(object1, "toString");
Class theClass = methodCaller(object2, "getClass");

Überprüfen Sie natürlich alle Ausnahmen und fügen Sie die erforderlichen Besetzungen hinzu.

Zoquete
quelle
7
Reflexion ist nicht die richtige Antwort auf irgendetwas anderes als "Wie schreibe ich langsamen, zwielichtigen Java-Code": D
Jacob
5
Es mag "hässlich" und langsam sein, aber ich glaube, dass Reflexion es erlaubt, Dinge zu tun, die die schnittstellenbasierte Lösung in der akzeptierten Antwort nicht kann (es sei denn, ich irre mich), nämlich verschiedene Methoden desselben Objekts innerhalb desselben Codes aufzurufen Portion.
Eusebius
@zoquete .. Ich habe diesen Beitrag durchgesehen und hatte Zweifel .. Wenn ich also in Ihrem Ansatz auf die Variable "theDescription" zugreife, wird die Funktion jedes Mal aufgerufen, wenn auf die Variable zugegriffen wird?
karthik27
20

Nein, Funktionen sind in Java keine erstklassigen Objekte. Sie können dasselbe tun, indem Sie eine Handlerklasse implementieren - so werden Rückrufe im Swing usw. implementiert.

Es gibt jedoch Vorschläge für Schließungen (der offizielle Name für das, worüber Sie sprechen) in zukünftigen Versionen von Java - Javaworld hat einen interessanten Artikel.

Jwoolard
quelle
8

Um ähnliche Funktionen zu erreichen, können Sie anonyme innere Klassen verwenden.

Wenn Sie eine Schnittstelle definieren würden Foo:

interface Foo {
    Object myFunc(Object arg);
}

Erstellen Sie eine Methode, bardie als Argument einen 'Funktionszeiger' erhält:

public void bar(Foo foo) {
    // .....
    Object object = foo.myFunc(argValue);
    // .....
}

Rufen Sie die Methode abschließend wie folgt auf:

bar(new Foo() {
    public Object myFunc(Object arg) {
        // Function code.
    }
}
Kingamajick
quelle
7

Java8 hat Lambdas und Methodenreferenzen eingeführt . Also , wenn Ihre Funktion eine Spiel funktionale Schnittstelle (Sie selbst erstellen können) können Sie eine Methode Referenz in diesem Fall verwendet werden .

Java bietet eine Reihe allgemeiner Funktionsschnittstellen . Sie könnten jedoch Folgendes tun:

public class Test {
   public void test1(Integer i) {}
   public void test2(Integer i) {}
   public void consumer(Consumer<Integer> a) {
     a.accept(10);
   }
   public void provideConsumer() {
     consumer(this::test1);   // method reference
     consumer(x -> test2(x)); // lambda
   }
}
Alex
quelle
aliaspublic List<T> collect(T t) = Collections::collect
Gibt
@javadba soweit ich nicht weiß.
Alex
5

In Java gibt es so etwas nicht. Sie müssen Ihre Funktion in ein Objekt einschließen und den Verweis auf dieses Objekt übergeben, um den Verweis auf die Methode für dieses Objekt zu übergeben.

Syntaktisch kann dies bis zu einem gewissen Grad vereinfacht werden, indem an Ort und Stelle definierte anonyme Klassen oder anonyme Klassen verwendet werden, die als Mitgliedsvariablen der Klasse definiert sind.

Beispiel:

class MyComponent extends JPanel {
    private JButton button;
    public MyComponent() {
        button = new JButton("click me");
        button.addActionListener(buttonAction);
        add(button);
    }

    private ActionListener buttonAction = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            // handle the event...
            // note how the handler instance can access 
            // members of the surrounding class
            button.setText("you clicked me");
        }
    }
}
VoidPointer
quelle
3

Ich habe Callback / Delegate-Unterstützung in Java mithilfe von Reflection implementiert. Details und Arbeitsquelle finden Sie auf meiner Website .

Wie es funktioniert

Wir haben eine Hauptklasse namens Callback mit einer verschachtelten Klasse namens WithParms. Die API, die den Rückruf benötigt, verwendet ein Callback-Objekt als Parameter und erstellt bei Bedarf einen Callback.WithParms als Methodenvariable. Da sehr viele Anwendungen dieses Objekts rekursiv sind, funktioniert dies sehr sauber.

Da die Leistung für mich immer noch eine hohe Priorität hat, wollte ich nicht verpflichtet sein, ein Wegwerfobjektarray zu erstellen, das die Parameter für jeden Aufruf enthält - schließlich kann es in einer großen Datenstruktur Tausende von Elementen geben und in einer Nachrichtenverarbeitung In diesem Szenario könnten wir am Ende Tausende von Datenstrukturen pro Sekunde verarbeiten.

Um threadsicher zu sein, muss das Parameterarray für jeden Aufruf der API-Methode eindeutig vorhanden sein, und aus Effizienzgründen sollte für jeden Aufruf des Rückrufs dasselbe verwendet werden. Ich brauchte ein zweites Objekt, dessen Erstellung billig wäre, um den Rückruf mit einem Parameterarray zum Aufrufen zu binden. In einigen Szenarien verfügt der Aufrufer jedoch aus anderen Gründen bereits über ein Parameterarray. Aus diesen beiden Gründen gehörte das Parameterarray nicht zum Callback-Objekt. Auch die Wahl des Aufrufs (Übergabe der Parameter als Array oder als einzelne Objekte) liegt in den Händen der API, die den Rückruf verwendet, um den Aufruf zu verwenden, der für das Innenleben am besten geeignet ist.

Die verschachtelte WithParms-Klasse ist also optional und dient zwei Zwecken: Sie enthält das für die Rückrufaufrufe erforderliche Parameterobjektarray und bietet 10 überladene invoke () -Methoden (mit 1 bis 10 Parametern), die das Parameterarray laden und dann Rufen Sie das Rückrufziel auf.

Lawrence Dol
quelle
2

Überprüfen Sie die Abschlüsse, wie sie in der Lambdaj-Bibliothek implementiert wurden. Sie haben tatsächlich ein Verhalten, das C # -Delegierten sehr ähnlich ist:

http://code.google.com/p/lambdaj/wiki/Closures

Mario Fusco
quelle
-1

Im Vergleich zu den meisten Leuten hier bin ich neu in Java, aber da ich keinen ähnlichen Vorschlag gesehen habe, kann ich eine andere Alternative vorschlagen. Ich bin mir nicht sicher, ob es eine gute Praxis ist oder nicht oder sogar vorher vorgeschlagen und ich habe es einfach nicht verstanden. Ich mag es einfach, weil ich denke, dass es selbstbeschreibend ist.

 /*Just to merge functions in a common name*/
 public class CustomFunction{ 
 public CustomFunction(){}
 }

 /*Actual functions*/
 public class Function1 extends CustomFunction{
 public Function1(){}
 public void execute(){...something here...}
 }

 public class Function2 extends CustomFunction{
 public Function2(){}
 public void execute(){...something here...}
 }

 .....
 /*in Main class*/
 CustomFunction functionpointer = null;

dann je nach anwendung zuweisen

 functionpointer = new Function1();
 functionpointer = new Function2();

etc.

und rufen Sie an

 functionpointer.execute();
ozgeneral
quelle