Pointeurs de fonction en Java

148

C'est peut-être quelque chose de commun et d'insignifiant, mais il me semble que j'ai du mal à trouver une réponse concrète. En C #, il existe un concept de délégués, qui est fortement lié à l'idée des pointeurs de fonction de C ++. Existe-t-il une fonctionnalité similaire en Java? Étant donné que les pointeurs sont quelque peu absents, quelle est la meilleure façon de procéder? Et pour être clair, nous parlons de première classe ici.

dreadwail
la source
1
Je suis juste curieux de savoir pourquoi vous voudriez cela où les auditeurs ou d'autres constructions OOP feraient la même tâche tout en conservant leur nature d'objet. Je veux dire que je comprends le besoin de la fonctionnalité offerte par le concept juste qu'il peut être réalisé avec un objet simple .. à moins que, bien sûr, il me manque quelque chose, d'où ma question! :-)
Newtopian
2
Java 8 a des expressions lambda: docs.oracle.com/javase/tutorial/java/javaOO / ... Vous voudrez peut-être vérifier cela. Ce n'est pas tout à fait un pointeur de fonction, mais cela pourrait quand même être plus utile.
FrustratedWithFormsDesigner
6
Les références de méthode Java 8 sont exactement ce que vous demandez.
Steven
8
Les références de méthode Java 8 sont exactement ce que vous demandez. this::myMethodest sémantiquement identique à la création d'un lambda paramA, paramB -> this.myMethod(paramA, paramB).
Steven

Réponses:

126

L'idiome Java pour la fonctionnalité de type pointeur de fonction est une classe anonyme implémentant une interface, par ex.

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

Mise à jour: ce qui précède est nécessaire dans les versions Java antérieures à Java 8. Nous avons maintenant des alternatives beaucoup plus agréables, à savoir les lambdas:

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

et références de méthode:

list.sort(MyClass::isGreaterThan);
Michael Borgwardt
la source
2
Le modèle de conception de la stratégie. Pas aussi laid qu'un pointeur de fonction C ou C ++ lorsque vous voulez que la fonction utilise des données non globales qui ne sont pas fournies comme argument de la fonction.
Raedwald
75
@Raedwald C ++ a des foncteurs, et C ++ 0x a des fermetures et des lambdas construits sur des foncteurs. Il a même std::bind, qui lie les paramètres aux fonctions et renvoie un objet appelable. Je ne peux pas défendre C pour ces raisons, mais C ++ est vraiment meilleur que Java pour cela.
zneak
21
@zneak, @Raedwald: Vous pouvez faire cela en C en passant un pointeur vers les données utilisateur (par exemple: struct). (et peut l'encapsuler à chaque nouveau niveau d'indirection de rappel) C'est en fait assez propre.
brice
25
Ouais, je prendrai en fait des pointeurs de fonction C sur des classes de wrapper cruelles n'importe quel jour
BT
3
Java 8 a une meilleure syntaxe pour cela.
Thorbjørn Ravn Andersen
65

Vous pouvez remplacer un pointeur de fonction par une interface. Disons que vous voulez parcourir une collection et faire quelque chose avec chaque élément.

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

C'est l'interface que nous pourrions passer à certains, disons CollectionUtils2.doFunc (Collection c, IFunction f).

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

À titre d'exemple, disons que nous avons une collection de nombres et que vous souhaitez ajouter 1 à chaque élément.

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

Vous pouvez utiliser la réflexion pour le faire.

Passez en paramètre l'objet et le nom de la méthode (sous forme de chaîne) puis invoquez la méthode. Par exemple:

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

Et puis utilisez-le comme dans:

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

Bien sûr, vérifiez toutes les exceptions et ajoutez les transtypages nécessaires.

zoquete
la source
7
La réflexion n'est pas la bonne réponse à autre chose que "Comment écrire du code java lent et douteux": D
Jacob
5
Cela peut être "moche" et lent, mais je pense que la réflexion permet de faire des choses que la solution basée sur l'interface dans la réponse acceptée ne peut pas faire (sauf si je me trompe), à ​​savoir appeler diverses méthodes du même objet dans le même code portion.
Eusebius
@zoquete .. J'étais en train de parcourir ce post et j'avais un doute .. donc dans votre approche, si j'accède à la variable "theDescription", la fonction sera appelée à chaque fois que la variable est accédée ??
karthik27
20

Non, les fonctions ne sont pas des objets de première classe en java. Vous pouvez faire la même chose en implémentant une classe de gestionnaire - c'est ainsi que les rappels sont implémentés dans le Swing, etc.

Il y a cependant des propositions de fermetures (le nom officiel de ce dont vous parlez) dans les futures versions de java - Javaworld a un article intéressant.

jwoolard
la source
15

Cela me rappelle l' exécution de Steve Yegge dans le royaume des noms . Il déclare essentiellement que Java a besoin d'un objet pour chaque action, et n'a donc pas d'entités "verb-only" comme les pointeurs de fonction.

Yuval F
la source
8

Pour obtenir des fonctionnalités similaires, vous pouvez utiliser des classes internes anonymes.

Si vous deviez définir une interface Foo:

interface Foo {
    Object myFunc(Object arg);
}

Créez une méthode barqui recevra un 'pointeur de fonction' comme argument:

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

Appelez enfin la méthode comme suit:

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

Java8 a introduit des lambdas et des références de méthode . Donc, si votre fonction correspond à une interface fonctionnelle (vous pouvez créer la vôtre), vous pouvez utiliser une référence de méthode dans ce cas.

Java fournit un ensemble d' interfaces fonctionnelles communes . alors que vous pouvez faire ce qui suit:

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
la source
y a-t-il le concept d'un alias: eg public List<T> collect(T t) = Collections::collect
javadba
@javadba pour autant que je sache.
Alex le
5

Une telle chose n'existe pas en Java. Vous devrez envelopper votre fonction dans un objet et passer la référence à cet objet afin de passer la référence à la méthode sur cet objet.

D'un point de vue syntaxique, cela peut être facilité dans une certaine mesure en utilisant des classes anonymes définies sur place ou des classes anonymes définies en tant que variables membres de la classe.

Exemple:

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
la source
3

J'ai implémenté le support de rappel / délégué en Java en utilisant la réflexion. Les détails et la source de travail sont disponibles sur mon site Web .

Comment ça fonctionne

Nous avons une classe principale nommée Callback avec une classe imbriquée nommée WithParms. L'API qui a besoin du rappel prendra un objet Callback comme paramètre et, si nécessaire, créera un Callback.WithParms comme variable de méthode. Comme un grand nombre d'applications de cet objet seront récursives, cela fonctionne très proprement.

Les performances étant toujours une priorité pour moi, je ne voulais pas être obligé de créer un tableau d'objets jetables pour contenir les paramètres de chaque appel - après tout, dans une grande structure de données, il pourrait y avoir des milliers d'éléments, et dans un traitement de message scénario, nous pourrions finir par traiter des milliers de structures de données par seconde.

Afin d'être sûr pour les threads, le tableau de paramètres doit exister de manière unique pour chaque invocation de la méthode API, et pour plus d'efficacité, le même doit être utilisé pour chaque appel du rappel; J'avais besoin d'un deuxième objet qui serait bon marché à créer afin de lier le rappel avec un tableau de paramètres pour l'invocation. Mais, dans certains scénarios, l'invocateur aurait déjà un tableau de paramètres pour d'autres raisons. Pour ces deux raisons, le tableau de paramètres n'appartenait pas à l'objet Callback. Le choix de l'invocation (passer les paramètres sous forme de tableau ou d'objets individuels) appartient également à l'API en utilisant le callback lui permettant d'utiliser l'invocation la mieux adaptée à son fonctionnement interne.

La classe imbriquée WithParms est donc facultative et sert à deux fins, elle contient le tableau d'objets de paramètres nécessaire pour les appels de rappel, et elle fournit 10 méthodes invoke () surchargées (avec de 1 à 10 paramètres) qui chargent le tableau de paramètres, puis appeler la cible de rappel.

Lawrence Dol
la source
2

Vérifiez les fermetures comment elles ont été implémentées dans la bibliothèque lambdaj. Ils ont en fait un comportement très similaire aux délégués C #:

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

Mario Fusco
la source
-1

Par rapport à la plupart des gens ici, je suis nouveau sur Java, mais comme je n'ai pas vu de suggestion similaire, j'ai une autre alternative à suggérer. Je ne sais pas si c'est une bonne pratique ou non, ou même suggérée avant et je ne l'ai tout simplement pas comprise. Je l'aime juste car je pense que c'est auto-descriptif.

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

puis selon l'application, attribuez

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

etc.

et appeler

 functionpointer.execute();
ozgénéral
la source