web-dev-qa-db-fra.com

Quel est le substitut le plus proche d'un pointeur de fonction en Java?

J'ai une méthode qui compte environ dix lignes de code. Je veux créer plus de méthodes qui font exactement la même chose, à l'exception d'un petit calcul qui va changer une ligne de code. C'est une application parfaite pour passer un pointeur de fonction afin de remplacer cette ligne, mais Java n'a pas de pointeur de fonction. Quelle est ma meilleure alternative?

299
Bill the Lizard

Classe intérieure anonyme

Supposons que vous souhaitiez faire passer une fonction avec un paramètre String qui renvoie un int.
Vous devez d’abord définir une interface avec la fonction comme membre unique, si vous ne pouvez pas réutiliser une interface existante.

interface StringFunction {
    int func(String param);
}

Une méthode qui prend le pointeur accepterait simplement StringFunction instance comme ceci:

public void takingMethod(StringFunction sf) {
   int i = sf.func("my string");
   // do whatever ...
}

Et serait appelé comme suit:

ref.takingMethod(new StringFunction() {
    public int func(String param) {
        // body
    }
});

EDIT: Dans Java 8, vous pouvez l'appeler avec une expression lambda:

ref.takingMethod(param -> bodyExpression);
269
sblundy

Pour chaque "pointeur de fonction", je créerais un petit classe de foncteur qui implémentera votre calcul. Définissez une interface que toutes les classes implémenteront et transmettez des instances de ces objets à votre fonction la plus grande. Ceci est une combinaison des " modèle de commande " et " modèle de stratégie ".

L'exemple de @ sblundy est bon.

32
Blair Conrad

Lorsqu'il existe un nombre prédéfini de calculs différents que vous pouvez effectuer sur cette ligne, l'utilisation d'un enum est un moyen rapide, mais clair, de mettre en œuvre un modèle de stratégie.

public enum Operation {
    PLUS {
        public double calc(double a, double b) {
            return a + b;
        }
    },
    TIMES {
        public double calc(double a, double b) {
            return a * b;
        }
    }
     ...

     public abstract double calc(double a, double b);
}

Bien entendu, la déclaration de la méthode de stratégie, ainsi que exactement une instance de chaque implémentation sont tous définis dans une même classe/fichier.

28
javashlook

Vous devez créer une interface fournissant la ou les fonctions que vous souhaitez transmettre. par exemple:

/**
 * A simple interface to wrap up a function of one argument.
 * 
 * @author rcreswick
 *
 */
public interface Function1<S, T> {

   /**
    * Evaluates this function on it's arguments.
    * 
    * @param a The first argument.
    * @return The result.
    */
   public S eval(T a);

}

Ensuite, lorsque vous devez passer une fonction, vous pouvez implémenter cette interface:

List<Integer> result = CollectionUtilities.map(list,
        new Function1<Integer, Integer>() {
           @Override
           public Integer eval(Integer a) {
              return a * a;
           }
        });

Enfin, la fonction map utilise le passé dans Function1 comme suit:

   public static <K,R,S,T> Map<K, R> zipWith(Function2<R,S,T> fn, 
         Map<K, S> m1, Map<K, T> m2, Map<K, R> results){
      Set<K> keySet = new HashSet<K>();
      keySet.addAll(m1.keySet());
      keySet.addAll(m2.keySet());

      results.clear();

      for (K key : keySet) {
         results.put(key, fn.eval(m1.get(key), m2.get(key)));
      }
      return results;
   }

Vous pouvez souvent utiliser Runnable à la place de votre propre interface si vous n'avez pas besoin de transmettre de paramètres, ou vous pouvez utiliser diverses autres techniques pour rendre le paramètre param moins "fixe", mais il s'agit généralement d'un compromis avec la sécurité de type. (Vous pouvez également redéfinir le constructeur pour que l'objet de fonction passe dans les paramètres. Il existe de nombreuses approches et certaines fonctionnent mieux dans certaines circonstances.)

24
rcreswick

Références de méthodes à l'aide de l'opérateur ::

Vous pouvez utiliser des références de méthode dans les arguments de méthode lorsque la méthode accepte une interface fonctionnelle . Une interface fonctionnelle est une interface qui contient une seule méthode abstraite. (Une interface fonctionnelle peut contenir une ou plusieurs méthodes par défaut ou méthodes statiques.)

IntBinaryOperator est une interface fonctionnelle. Sa méthode abstraite, applyAsInt , accepte deux paramètres ints et renvoie un int. Math.max accepte également deux ints et renvoie un int. Dans cet exemple, A.method(Math::max); fait que parameter.applyAsInt Envoie ses deux valeurs d'entrée à Math.max Et renvoie le résultat de cette opération Math.max.

import Java.util.function.IntBinaryOperator;

class A {
    static void method(IntBinaryOperator parameter) {
        int i = parameter.applyAsInt(7315, 89163);
        System.out.println(i);
    }
}
import Java.lang.Math;

class B {
    public static void main(String[] args) {
        A.method(Math::max);
    }
}

En général, vous pouvez utiliser:

method1(Class1::method2);

au lieu de:

method1((arg1, arg2) -> Class1.method2(arg1, arg2));

qui est court pour:

method1(new Interface1() {
    int method1(int arg1, int arg2) {
        return Class1.method2(arg1, agr2);
    }
});

Pour plus d'informations, voir l'opérateur :: (doubles points-virgules) dans Java 8 et spécification de langage Java, §15.1 .

17
The Guy with The Hat

Vous pouvez également faire cela (ce qui dans certaines [~ # ~] rare [~ # ~] occasionne un sens). Le problème (et c’est un gros problème), c’est que vous perdez toute sécurité typologique d’utilisation d’une classe/interface et vous devez gérer le cas où la méthode n’existe pas.

"L'avantage" est que vous pouvez ignorer les restrictions d'accès et appeler des méthodes privées (non montré dans l'exemple, mais vous pouvez appeler des méthodes que le compilateur ne vous laisserait normalement pas appeler).

Encore une fois, il est rare que cela ait du sens, mais à ces occasions, il s'agit d'un outil agréable à utiliser.

import Java.lang.reflect.InvocationTargetException;
import Java.lang.reflect.Method;

class Main
{
    public static void main(final String[] argv)
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        final String methodName;
        final Method method;
        final Main   main;

        main = new Main();

        if(argv.length == 0)
        {
            methodName = "foo";
        }
        else
        {
            methodName = "bar";
        }

        method = Main.class.getDeclaredMethod(methodName, int.class);

        main.car(method, 42);
    }

    private void foo(final int x)
    {
        System.out.println("foo: " + x);
    }

    private void bar(final int x)
    {
        System.out.println("bar: " + x);
    }

    private void car(final Method method,
                     final int    val)
        throws IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        method.invoke(this, val);
    }
}
15
TofuBeer

Si vous avez juste une ligne différente, vous pouvez ajouter un paramètre tel qu'un indicateur et une instruction if (indicateur) qui appelle une ligne ou l'autre.

13
Peter Lawrey

Vous pouvez également être intéressé par le travail en cours pour Java 7 impliquant des fermetures:

Quel est l’état actuel des fermetures en Java?

http://gafter.blogspot.com/2006/08/closures-for-Java.html
http://tech.puredanger.com/Java7/#closures

12
Dave L.

Nouveau Java 8 Interfaces fonctionnelles et Références de méthode à l'aide de la commande :: opérateur.

Java 8 est capable de gérer les références de méthodes (MyClass :: new) avec les pointeurs "@ Functional Interface". Le même nom de méthode n’est pas nécessaire, seule la signature de la méthode est requise.

Exemple:

@FunctionalInterface
interface CallbackHandler{
    public void onClick();
}

public class MyClass{
    public void doClick1(){System.out.println("doClick1");;}
    public void doClick2(){System.out.println("doClick2");}
    public CallbackHandler mClickListener = this::doClick;

    public static void main(String[] args) {
        MyClass myObjectInstance = new MyClass();
        CallbackHandler pointer = myObjectInstance::doClick1;
        Runnable pointer2 = myObjectInstance::doClick2;
        pointer.onClick();
        pointer2.run();
    }
}

Alors, qu'est-ce qu'on a ici?

  1. Interface fonctionnelle - il s'agit d'une interface annotée ou non avec @ FunctionalInterface, qui ne contient qu'une déclaration de méthode.
  2. Références de méthodes - Ceci est juste une syntaxe spéciale, ressemble à ceci, objectInstance :: methodName, rien de plus, rien de moins.
  3. Exemple d'utilisation - juste un opérateur d'affectation, puis un appel de méthode d'interface.

VOUS DEVEZ UTILISER DES INTERFACES FONCTIONNELLES POUR DES LISTENERS UNIQUEMENT ET UNIQUEMENT!

Parce que tous les autres indicateurs de fonction de ce type sont vraiment mauvais pour la lisibilité du code et pour la capacité de comprendre. Cependant, les références directes aux méthodes sont parfois utiles, avec foreach par exemple.

Il existe plusieurs interfaces fonctionnelles prédéfinies:

Runnable              -> void run( );
Supplier<T>           -> T get( );
Consumer<T>           -> void accept(T);
Predicate<T>          -> boolean test(T);
UnaryOperator<T>      -> T apply(T);
BinaryOperator<T,U,R> -> R apply(T, U);
Function<T,R>         -> R apply(T);
BiFunction<T,U,R>     -> R apply(T, U);
//... and some more of it ...
Callable<V>           -> V call() throws Exception;
Readable              -> int read(CharBuffer) throws IOException;
AutoCloseable         -> void close() throws Exception;
Iterable<T>           -> Iterator<T> iterator();
Comparable<T>         -> int compareTo(T);
Comparator<T>         -> int compare(T,T);

Pour les versions antérieures Java, vous devriez essayer les bibliothèques Guava, dont les fonctionnalités et la syntaxe sont similaires à celles mentionnées par Adrian Petrescu ci-dessus.

Pour des recherches supplémentaires, consultez Java 8 Cheatsheet

et merci au gars avec le chapeau pour le lien spécification de langage Java, §15.1 .

11
user3002379

La réponse de @ sblundy est excellente, mais les classes internes anonymes ont deux petites imperfections, la principale étant qu'elles ont tendance à ne pas être réutilisables et la seconde est une syntaxe volumineuse.

La bonne chose est que son modèle se développe en classes complètes sans aucun changement dans la classe principale (celle effectuant les calculs).

Lorsque vous instanciez une nouvelle classe, vous pouvez lui transmettre des paramètres qui peuvent servir de constantes dans votre équation. Par conséquent, si l'une de vos classes internes ressemble à ceci:

f(x,y)=x*y

mais parfois vous en avez besoin:

f(x,y)=x*y*2

et peut-être un tiers qui est:

f(x,y)=x*y/2

au lieu de créer deux classes internes anonymes ou d'ajouter un paramètre "passthrough", vous pouvez créer une seule classe ACTUAL que vous instanciez en tant que:

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f=new InnerFunc(2.0);// for the second
calculateUsing(f);
f=new InnerFunc(0.5);// for the third
calculateUsing(f);

Il s'agirait simplement de stocker la constante dans la classe et de l'utiliser dans la méthode spécifiée dans l'interface.

En fait, si SAVEZ que votre fonction ne sera pas stockée/réutilisée, vous pouvez faire ceci:

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f.setConstant(2.0);
calculateUsing(f);
f.setConstant(0.5);
calculateUsing(f);

Mais les classes immuables sont plus sûres - je ne peux pas trouver de justification pour rendre une classe comme celle-ci mutable.

Je ne publie vraiment cela que parce que je grince des dents chaque fois que j'entends une classe interne anonyme - j'ai vu beaucoup de code redondant qui était "Required" car la première chose que le programmeur a faite a été de passer de manière anonyme alors qu'il aurait dû utiliser une classe réelle et jamais repensé sa décision.

9
Bill K

Les Google Guava libraries , qui deviennent très populaires, ont un objet Fonction et Predicate générique dans lequel ils ont travaillé dans de nombreuses parties de leur API.

6
Adrian Petrescu

oK, ce fil est déjà assez vieux, donc très probablement ma réponse n'est pas utile pour la question. Mais puisque ce fil m'a aidé à trouver ma solution, je la publierai ici quand même.

J'avais besoin d'utiliser une méthode statique variable avec une entrée connue et une sortie connue (les deux double). Alors, connaissant le paquet de méthodes et son nom, je pourrais travailler comme suit:

Java.lang.reflect.Method Function = Class.forName(String classPath).getMethod(String method, Class[] params);

pour une fonction qui accepte un double comme paramètre.

Donc, dans ma situation concrète, je l'ai initialisé avec

Java.lang.reflect.Method Function = Class.forName("be.qan.NN.ActivationFunctions").getMethod("sigmoid", double.class);

et l'a invoqué plus tard dans une situation plus complexe avec

return (Java.lang.Double)this.Function.invoke(null, args);

Java.lang.Object[] args = new Java.lang.Object[] {activity};
someOtherFunction() + 234 + (Java.lang.Double)Function.invoke(null, args);

où activité est une valeur double arbitraire. Je pense peut-être à faire ceci un peu plus abstrait et à le généraliser, comme l'a fait SoftwareMonkey, mais actuellement, je suis assez content de la façon dont il est. Trois lignes de code, pas de classes ni d'interfaces nécessaires, c'est pas mal.

4
yogibimbi

Une des choses qui me manque le plus lorsque je programme Java, ce sont les rappels de fonctions. Une situation dans laquelle ils ont continué à se présenter concernait le traitement récursif des hiérarchies dans lesquelles vous souhaitez effectuer une action spécifique pour chaque élément. C'est comme marcher dans une arborescence de répertoires ou traiter une structure de données. Le minimaliste en moi déteste devoir définir une interface, puis une implémentation pour chaque cas spécifique.

Un jour je me suis demandé pourquoi pas? Nous avons des pointeurs de méthode - l'objet Method. Avec l’optimisation des compilateurs JIT, l’invocation par réflexion n’entraîne plus vraiment de pénalité en termes de performances. Et outre le fait de copier un fichier d’un emplacement à un autre, le coût de l’appel de méthode reflété devient insignifiant.

En y réfléchissant davantage, je me suis rendu compte qu'un rappel dans le paradigme OOP nécessite la liaison d'un objet et d'une méthode - entrez l'objet Callback.

Découvrez ma solution basée sur la réflexion pour Callbacks en Java . Gratuit pour toute utilisation.

4
Lawrence Dol

Pour faire la même chose sans interfaces pour un tableau de fonctions:

class NameFuncPair
{
    public String name;                // name each func
    void   f(String x) {}              // stub gets overridden
    public NameFuncPair(String myName) { this.name = myName; }
}

public class ArrayOfFunctions
{
    public static void main(String[] args)
    {
        final A a = new A();
        final B b = new B();

        NameFuncPair[] fArray = new NameFuncPair[]
        {
            new NameFuncPair("A") { @Override void f(String x) { a.g(x); } },
            new NameFuncPair("B") { @Override void f(String x) { b.h(x); } },
        };

        // Go through the whole func list and run the func named "B"
        for (NameFuncPair fInstance : fArray)
        {
            if (fInstance.name.equals("B"))
            {
                fInstance.f(fInstance.name + "(some args)");
            }
        }
    }
}

class A { void g(String args) { System.out.println(args); } }
class B { void h(String args) { System.out.println(args); } }
4
vwvan

Cela ressemble à un modèle de stratégie pour moi. Découvrez les modèles fluffycat.com Java.

4
Dennis S

Wow, pourquoi ne pas simplement créer une classe Delegate, ce qui n’est pas si difficile compte tenu de ce que j’ai déjà fait pour Java et l’utiliser pour passer le paramètre où T est le type de retour. Je suis désolé, mais en tant que Le programmeur C++/C # en général juste pour apprendre Java, j’ai besoin de pointeurs de fonction car ils sont très pratiques. Si vous êtes familier avec une classe qui traite d’informations de méthode, vous pouvez le faire. Dans Java bibliothèques qui serait Java.lang.reflect.method.

Si vous utilisez toujours une interface, vous devez toujours la mettre en œuvre. Dans la gestion des événements, il n'y a pas vraiment de meilleur moyen de s'inscrire/désinscrire de la liste des gestionnaires, mais pour les délégués dans lesquels vous devez transmettre des fonctions et non le type de valeur, ce qui permet à une classe delegate de la gérer pour surclasser une interface.

3
Robert

Découvrez lambdaj

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

et en particulier sa nouvelle fonction de fermeture

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

et vous trouverez un moyen très lisible de définir un pointeur de fermeture ou de fonction sans créer d'interface dénuée de sens ni utiliser des classes internes laides

3
Mario Fusco

Aucune des Java 8 réponses n'a donné un exemple complet et cohérent, alors voici ce qu'il en est.

Déclarez la méthode qui accepte le "pointeur de fonction" comme suit:

void doCalculation(Function<Integer, String> calculation, int parameter) {
    final String result = calculation.apply(parameter);
}

Appelez-le en fournissant à la fonction une expression lambda:

doCalculation((i) -> i.toString(), 2);
2
mrts

Avant Java 8, le substitut le plus proche d'une fonctionnalité semblable à un pointeur de fonction était une classe anonyme. Par exemple:

Collections.sort(list, new Comparator<CustomClass>(){
    public int compare(CustomClass a, CustomClass b)
    {
        // Logic to compare objects of class CustomClass which returns int as per contract.
    }
});

Mais maintenant dans Java 8 nous avons une alternative très soignée appelée expression lambda , qui peut être utilisée comme:

list.sort((a, b) ->  { a.isBiggerThan(b) } );

où isBiggerThan est une méthode dans CustomClass. Nous pouvons également utiliser les références de méthodes ici:

list.sort(MyClass::isBiggerThan);
1
i_am_zero

Si quelqu'un a du mal à passer une fonction qui prend un ensemble de paramètres pour définir son comportement, mais un autre ensemble de paramètres sur lequel s'exécuter, comme celle de Scheme:

(define (function scalar1 scalar2)
  (lambda (x) (* x scalar1 scalar2)))

voir Fonction Pass avec un comportement défini par paramètre en Java

1
Scott Emmons

Depuis Java8, vous pouvez utiliser lambdas, qui possède également des bibliothèques dans l'API officielle de SE 8.

Utilisation: Vous devez utiliser une interface avec une seule méthode abstraite. Créez-en une instance (vous pouvez utiliser celle-ci Java SE 8 déjà fournie) comme ceci:

Function<InputType, OutputType> functionname = (inputvariablename) {
... 
return outputinstance;
}

Pour plus d'informations, consultez la documentation: https://docs.Oracle.com/javase/tutorial/Java/javaOO/lambdaexpressions.html

1
Alex