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


299

J'ai une méthode qui fait 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 pour remplacer cette seule ligne, mais Java n'a pas de pointeurs de fonction. Quelle est ma meilleure alternative?


5
Java 8 aura des expressions Lambda . Vous pouvez en savoir plus sur les expressions lambda ici .
Marius

4
@Marius Je ne pense pas que les expressions lambda comptent comme des pointeurs de fonction. L' ::opératrice, en revanche ...
Le gars au chapeau

Désolé pour le commentaire tardif;) - généralement, vous n'avez pas besoin d'un pointeur de fonction pour cela. Utilisez simplement une méthode de modèle! ( en.wikipedia.org/wiki/Template_method_pattern )
isnot2bad

2
@ isnot2bad - en regardant cet article, cela semble exagéré - plus complexe que les réponses données ici. Plus précisément, la méthode du modèle nécessite la création d'une sous - classe pour chaque calcul alternatif. Je ne vois pas OP avoir déclaré quoi que ce soit qui nécessite des sous-classes ; il veut simplement créer plusieurs méthodes et partager l'essentiel de l'implémentation. Comme le montre la réponse acceptée, cela se fait facilement "sur place" (à l'intérieur de chaque méthode), même avant Java 8 avec ses lambdas.
ToolmakerSteve

@ToolmakerSteve La solution acceptée requiert également une classe par calcul (même s'il ne s'agit que d'une classe interne anonyme). Et le modèle de méthode de modèle peut également être réalisé à l'aide de classes internes anonymes, il ne diffère donc pas beaucoup de la solution acceptée concernant la surcharge (avant Java 8). C'est donc plus une question de modèle d'utilisation et d'exigences détaillées, que nous ne connaissons pas. J'apprécie la réponse acceptée et je voulais juste ajouter une autre possibilité de réflexion.
isnot2bad

Réponses:


269

Classe intérieure anonyme

Supposons que vous souhaitiez qu'une fonction soit transmise avec un Stringparamètre qui renvoie un int.
Vous devez d'abord définir une interface avec la fonction comme seul membre, 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 une StringFunctioninstance comme ceci:

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

Et serait appelé ainsi:

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

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

ref.takingMethod(param -> bodyExpression);

13
C'est un exemple du "Command Patern", soit dit en passant. en.wikipedia.org/wiki/Command_Pattern
Ogre Psalm33

3
@Ogre Psalm33 Cette technique pourrait également être le modèle de stratégie, selon la façon dont vous l'utilisez. La différence entre le modèle de stratégie et le modèle de commande .
Rory O'Kane

2
Voici une implémentation de fermeture pour Java 5, 6 et 7 mseifed.blogspot.se/2012/09/… Elle contient tout ce que l'on peut demander ... Je pense que c'est assez génial!
mmm

@SecretService: Ce lien est mort.
Lawrence Dol

@ LawrenceDol Ouais, ça l'est. Voici une boîte à pâte de la classe que j'utilise. pastebin.com/b1j3q2Lp
mmm


28

Lorsqu'il existe un nombre prédéfini de calculs différents que vous pouvez effectuer sur cette ligne, l'utilisation d'une énumération est un moyen rapide mais clair d'implémenter 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);
}

Évidemment, la déclaration de méthode de stratégie, ainsi qu'exactement une instance de chaque implémentation sont toutes définies dans une seule classe / fichier.


24

Vous devez créer une interface qui fournit les fonctions que vous souhaitez faire circuler. 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 de carte 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 au lieu de votre propre interface si vous n'avez pas besoin de passer de paramètres, ou vous pouvez utiliser diverses autres techniques pour rendre le nombre de paramètres moins "fixe", mais c'est généralement un compromis avec la sécurité de type. (Ou vous pouvez remplacer le constructeur pour que votre objet fonction passe dans les paramètres de cette façon .. il existe de nombreuses approches, et certaines fonctionnent mieux dans certaines circonstances.)


4
Cette «réponse» concerne plus l' ensemble de problèmes que l' ensemble de solutions.
tchrist

18

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

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

IntBinaryOperatorest une interface fonctionnelle. Sa méthode abstraite,, applyAsIntaccepte deux ints comme paramètres et renvoie un int. Math.maxaccepte également deux ints et renvoie un int. Dans cet exemple, A.method(Math::max);fait parameter.applyAsIntenvoyer ses deux valeurs d'entrée à Math.maxet retourner le résultat de cela 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));

ce qui est l'abréviation de:

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

Pour plus d'informations, voir l' opérateur :: (double colon) dans Java 8 et Java Language Specification §15.13 .


15

Vous pouvez également le faire (ce qui, dans certaines occasions RARE, a du sens). Le problème (et c'est un gros problème) est que vous perdez toute la sécurité de type d'utilisation d'une classe / interface et que vous devez faire face au cas où la méthode n'existe pas.

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

Encore une fois, c'est un cas rare que cela a du sens, mais dans ces occasions, c'est un bel outil à avoir.

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

1
J'utilise cela pour la gestion des menus / GUI parfois parce que la syntaxe de la méthode est beaucoup plus simple que la syntaxe de classe interne anonyme. C'est bien, mais vous ajoutez la complexité de la réflexion que certaines personnes ne veulent pas approfondir, alors sachez bien que vous avez bien compris et que vous avez des erreurs textuelles claires pour chaque condition d'erreur possible.
Bill K

Vous pouvez le faire en toute sécurité en utilisant des génériques et vous n'avez pas besoin de réflexion.
Luigi Plinge

1
Je ne vois pas comment l'utilisation de génériques et de non-réflexion vous permettrait d'appeler une méthode par un nom contenu dans une chaîne?
TofuBeer

1
@ LuigiPlinge - pouvez-vous fournir un extrait de code de ce que vous voulez dire?
ToolmakerSteve

13

Si vous n'avez qu'une seule 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.


1
La réponse de javaslook semble être une façon plus propre de le faire, si plus de deux variantes de calcul. Ou si l'on souhaite intégrer le code dans la méthode, alors une énumération pour les différents cas que la méthode gère, et un commutateur.
ToolmakerSteve

1
@ToolmakerSteve true, bien qu'aujourd'hui vous utilisiez des lambdas en Java 8.
Peter Lawrey


11

Nouvelles interfaces fonctionnelles et références de méthode Java 8 à l'aide de l' ::opérateur.

Java 8 est capable de maintenir des références de méthode (MyClass :: new) avec des pointeurs " @ Functional Interface ". Il n'y a pas besoin du même nom de méthode, seule la même signature de 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 que nous avons ici?

  1. Interface fonctionnelle - il s'agit d'une interface, annotée ou non avec @FunctionalInterface , qui ne contient qu'une seule déclaration de méthode.
  2. Références de méthode - 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 LES AUDITEURS UNIQUEMENT ET UNIQUEMENT POUR CELA!

Parce que tous les autres pointeurs de fonction sont vraiment mauvais pour la lisibilité du code et pour la capacité de comprendre. Cependant, les références de méthode directes 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 de Java, vous devriez essayer les bibliothèques de goyave, qui ont des fonctionnalités et une syntaxe similaires, comme Adrian Petrescu l'a mentionné ci-dessus.

Pour plus de recherche, regardez Java 8 Cheatsheet

et merci à The Guy with The Hat pour le lien §15.13 du Java Language Specification .


7
" Parce que tous les autres ... sont vraiment mauvais pour la lisibilité du code " est une affirmation complètement sans fondement, et d'ailleurs fausse.
Lawrence Dol

9

La réponse de @ sblundy est excellente, mais les classes internes anonymes ont deux petits défauts, le principal étant qu'elles ont tendance à ne pas être réutilisables et le secondaire 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 qui effectue les calculs).

Lorsque vous instanciez une nouvelle classe, vous pouvez passer des paramètres dans cette classe qui peuvent agir comme des constantes dans votre équation - donc 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

plutôt que 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 comme:

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 stockerait simplement la constante dans la classe et l'utiliserait dans la méthode spécifiée dans l'interface.

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

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 poste vraiment ceci 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 "requis" parce que la première chose que le programmeur a fait était de devenir anonyme alors qu'il aurait dû utiliser une classe réelle et jamais repensé sa décision.


Hein? OP parle de différents calculs (algorithmes; logique); vous montrez différentes valeurs (données). Vous montrez un cas spécifique où la différence peut être incorporée dans une valeur, mais c'est une simplification injustifiable du problème posé.
ToolmakerSteve

6

Les bibliothèques Google Guava , qui deviennent très populaires, ont un objet Function et Predicate générique qu'elles ont intégré dans de nombreuses parties de leur API.


Cette réponse serait plus utile si elle donnait des détails sur le code. Prenez le code indiqué dans la réponse acceptée et montrez à quoi il ressemblerait en utilisant Function.
ToolmakerSteve

4

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


4

Une des choses qui me manque vraiment lors de la programmation en Java est les rappels de fonctions. Une situation où le besoin de ces derniers se présentait continuellement était dans les hiérarchies de traitement récursif où vous souhaitez effectuer une action spécifique pour chaque élément. Comme parcourir 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 réfléchie ne porte plus vraiment une énorme pénalité de performance. Et en plus de, disons, la copie d'un fichier d'un emplacement à un autre, le coût de l'invocation de la méthode réfléchie est insignifiant.

En y réfléchissant davantage, j'ai réalisé qu'un rappel dans le paradigme de POO nécessite de lier un objet et une méthode ensemble - entrez l'objet de rappel.

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


4

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 vais le mettre ici de toute façon.

J'avais besoin d'utiliser une méthode statique variable avec une entrée et une sortie connues (toutes deux doubles ). Alors, connaissant le package et le nom de la méthode, 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ù l'activité est une valeur double arbitraire. Je pense peut-être à faire cela 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 c'est. Trois lignes de code, pas de classes et d'interfaces nécessaires, c'est pas trop mal.


merci Rob d'avoir ajouté la codedémarque, j'étais trop impatient et stupide pour le trouver ;-)
yogibimbi

4

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

1
Pourquoi? C'est plus compliqué que les solutions précédemment proposées, qui nécessitent simplement une définition de fonction anonyme par alternative. Par alternative, vous créez une classe et une définition de fonction anonyme. Pire, cela se fait à deux endroits différents dans le code. Vous voudrez peut-être fournir une justification pour utiliser cette approche.
ToolmakerSteve


3

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

Si vous utilisez toujours une interface, vous devez toujours l'implémenter. Dans la gestion des événements, il n'y a vraiment pas de meilleur moyen d'enregistrer / de désinscrire de la liste des gestionnaires, mais pour les délégués où vous devez passer des fonctions et non le type de valeur, ce qui fait qu'une classe déléguée le gère pour surclasse une interface.


Pas une réponse utile, sauf si vous montrez les détails du code. Comment la création d'une classe Délégué aide-t-elle? Quel code est requis par alternative?
ToolmakerSteve

2

Aucune des réponses de Java 8 n'a donné un exemple complet et cohérent, alors voici.

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-la en fournissant à la fonction une expression lambda:

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


1

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

Utilisation: Vous devez utiliser une interface avec une seule méthode abstraite. Faites-en une instance (vous voudrez peut-être utiliser celui java SE 8 déjà fourni) 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

Avant Java 8, le substitut le plus proche des fonctionnalités de type 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 connue sous le nom d' 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 des références de méthode ici:

list.sort(MyClass::isBiggerThan);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.