Puntatori di funzioni in Java


148

Questo può essere qualcosa di comune e banale, ma mi sembra di avere problemi a trovare una risposta concreta. In C # esiste un concetto di delegati, che è fortemente correlato all'idea di puntatori a funzioni di C ++. Esiste una funzionalità simile in Java? Dato che i puntatori sono in qualche modo assenti, qual è il modo migliore per farlo? E per essere chiari, stiamo parlando di prima classe qui.


1
Sono solo curioso di sapere perché vorresti che gli ascoltatori o altri costrutti OOP facessero lo stesso compito mantenendo la loro natura di oggetto. Voglio dire, capisco la necessità della funzionalità offerta dal concetto solo che può essere raggiunta con un semplice oggetto .. a meno che, ovviamente, mi manchi qualcosa, quindi la mia domanda! :-)
Newtopian

2
Java 8 ha espressioni lambda: docs.oracle.com/javase/tutorial/java/javaOO/… Potresti voler dare un'occhiata. Non proprio un puntatore a funzione, ma potrebbe essere ancora più utile.
FrustratedWithFormsDesigner

6
I riferimenti al metodo Java 8 sono esattamente ciò che stai chiedendo.
Steven,

8
I riferimenti al metodo Java 8 sono esattamente ciò che stai chiedendo. this::myMethodè semanticamente uguale alla creazione di una lambda paramA, paramB -> this.myMethod(paramA, paramB).
Steven,

Risposte:


126

Il linguaggio Java per funzionalità simili a puntatori a funzioni è una classe anonima che implementa un'interfaccia, ad es

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

Aggiornamento: quanto sopra è necessario nelle versioni Java precedenti a Java 8. Ora abbiamo alternative molto più belle, vale a dire lambdas:

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

e riferimenti ai metodi:

list.sort(MyClass::isGreaterThan);

2
Il modello di progettazione della strategia. Non brutto come un puntatore a una funzione C o C ++ quando si desidera che la funzione utilizzi alcuni dati non globali che non vengono forniti come argomento alla funzione.
Raedwald,

75
@Raedwald C ++ ha functors e C ++ 0x ha chiusure e lambda costruite sopra a functors. Ha anche std::bind, che lega i parametri alle funzioni e restituisce un oggetto richiamabile. Non posso difendere C per questi motivi, ma C ++ è davvero meglio di Java per questo.
zneak,

21
@zneak, @Raedwald: Puoi farlo in C passando un puntatore ai dati dell'utente (es: struct). (e può incapsularlo con ogni nuovo livello di riferimento indiretto di callback) In realtà è abbastanza pulito.
brice,

25
Sì, prenderò effettivamente i puntatori di funzione C su classi wrapper crufty ogni giorno
BT

3
Java 8 ha una sintassi migliore per questo.
Thorbjørn Ravn Andersen,

65

È possibile sostituire un puntatore a funzione con un'interfaccia. Diciamo che vuoi scorrere una raccolta e fare qualcosa con ogni elemento.

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

Questa è l'interfaccia che potremmo passare ad alcuni dire CollectionUtils2.doFunc (Collection c, IFunction f).

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

Ad esempio, abbiamo una raccolta di numeri e desideri aggiungere 1 a ogni elemento.

CollectionUtils2.doFunc(List numbers, new IFunction() {
    public void execute(Object o) {
       Integer anInt = (Integer) o;
       anInt++;
    }
});

42

Puoi usare la riflessione per farlo.

Passare come parametro l'oggetto e il nome del metodo (come stringa) e quindi invocare il metodo. Per esempio:

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

E poi usalo come in:

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

Ovviamente, controlla tutte le eccezioni e aggiungi i cast necessari.


7
La riflessione non è la risposta giusta a tutto tranne che a "Come faccio a scrivere codice java malandato lento": D
Jacob

5
Potrebbe essere "brutto" e lento, ma credo che la riflessione consenta di fare cose che la soluzione basata sull'interfaccia nella risposta accettata non può fare (a meno che non mi sbagli), vale a dire chiamare vari metodi dello stesso oggetto all'interno dello stesso codice porzione.
Eusebio

@zoquete .. Stavo attraversando questo post e ho avuto un dubbio .. quindi nel tuo approccio, se accedo alla variabile "theDescription", la funzione verrà chiamata ogni volta che si accede alla variabile ??
karthik27,

20

No, le funzioni non sono oggetti di prima classe in Java. Puoi fare la stessa cosa implementando una classe handler: ecco come vengono implementati i callback nello Swing ecc.

Ci sono comunque proposte di chiusure (il nome ufficiale di cui stai parlando) nelle future versioni di java - Javaworld ha un articolo interessante.


15

Questo mi ricorda l' Esecuzione di Steve Yegge nel regno dei nomi . Sostanzialmente afferma che Java ha bisogno di un oggetto per ogni azione, e quindi non ha entità "solo verbo" come i puntatori a funzioni.


8

Per ottenere funzionalità simili è possibile utilizzare classi interne anonime.

Se dovessi definire un'interfaccia Foo:

interface Foo {
    Object myFunc(Object arg);
}

Crea un metodo barche riceverà un 'puntatore a funzione' come argomento:

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

Infine chiama il metodo come segue:

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

7

Java8 ha introdotto lambdas e riferimenti ai metodi . Pertanto, se la tua funzione corrisponde a un'interfaccia funzionale (puoi crearne una tua), puoi utilizzare un riferimento al metodo in questo caso.

Java fornisce una serie di interfacce funzionali comuni . mentre potresti fare quanto segue:

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
   }
}

c'è il concetto di un alias: eg public List<T> collect(T t) = Collections::collect
javadba

@javadba per quanto ne so.
Alex

5

Non esiste nulla di simile in Java. Dovrai racchiudere la tua funzione in un oggetto e passare il riferimento a quell'oggetto per passare il riferimento al metodo su quell'oggetto.

Sintatticamente, questo può essere alleggerito in una certa misura usando le classi anonime definite sul posto o le classi anonime definite come variabili membro della classe.

Esempio:

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");
        }
    }
}

3

Ho implementato il supporto callback / delegate in Java usando la reflection. Dettagli e fonte di lavoro sono disponibili sul mio sito web .

Come funziona

Abbiamo una classe di principio chiamata Callback con una classe nidificata chiamata WithParms. L'API che necessita del callback prenderà un oggetto Callback come parametro e, se necessario, creerà un Callback.WithParms come variabile di metodo. Dal momento che molte delle applicazioni di questo oggetto saranno ricorsive, questo funziona in modo molto pulito.

Con le prestazioni ancora una priorità per me, non volevo che mi venisse richiesto di creare un array di oggetti usa e getta per contenere i parametri per ogni invocazione - dopotutto in una grande struttura di dati potrebbero esserci migliaia di elementi e nell'elaborazione di un messaggio scenario potremmo finire per elaborare migliaia di strutture di dati al secondo.

Per essere thread-safe, l'array di parametri deve esistere in modo univoco per ogni invocazione del metodo API e per efficienza lo stesso dovrebbe essere usato per ogni invocazione del callback; Avevo bisogno di un secondo oggetto che sarebbe stato economico da creare per legare il callback con un array di parametri per l'invocazione. Ma, in alcuni scenari, il chiamante avrebbe già un array di parametri per altri motivi. Per questi due motivi, l'array di parametri non apparteneva all'oggetto Callback. Anche la scelta dell'invocazione (passando i parametri come un array o come singoli oggetti) appartiene all'API usando il callback che le consente di usare qualunque invocazione sia più adatta al suo funzionamento interno.

La classe nidificata WithParms, quindi, è facoltativa e ha due scopi, contiene l'array di oggetti parametro necessario per le chiamate di callback e fornisce 10 metodi invoke () sovraccarichi (con da 1 a 10 parametri) che caricano l'array di parametri e quindi invocare il target di callback.



-1

Rispetto alla maggior parte delle persone qui sono nuovo di Java, ma poiché non ho visto un suggerimento simile ho un'altra alternativa da suggerire. Non sono sicuro che sia una buona pratica o no, o addirittura suggerito prima e non l'ho capito. Mi piace solo perché penso che sia auto-descrittivo.

 /*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;

quindi a seconda dell'applicazione, assegnare

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

eccetera.

e chiama da

 functionpointer.execute();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.