Question

J'ai une méthode d'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 ne dispose pas de pointeurs de fonction. Quelle est ma meilleure alternative?

Était-ce utile?

La solution

Classe interne anonyme

Dites que vous souhaitez qu'une fonction soit transmise 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 utilisant le pointeur accepterait simplement l'instance de StringFunction comme suit:

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: En Java 8, vous pouvez l'appeler avec une expression lambda:

ref.takingMethod(param -> bodyExpression);

Autres conseils

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

L'exemple de @ sblundy est bon.

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 constitue un moyen rapide mais néanmoins 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 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.

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 techniques pour réduire le nombre de paramètres "quot fixe". mais c'est généralement un compromis avec la sécurité de type. (Vous pouvez également remplacer le constructeur par votre objet fonction afin qu'il passe dans les paramètres. Il existe de nombreuses approches et certaines fonctionnent mieux dans certaines circonstances.)

Références de méthode à 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 int et renvoie un int . Math.max accepte également deux int 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 ce 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 l'abréviation de:

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

Pour plus d'informations, voir les :: (double-points) en Java 8 et Spécification du langage Java & # 167; 15.13 .

Vous pouvez également le faire (cela a du sens à certaines occasions RARE ). 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.

Il présente le "bénéfice". que vous pouvez ignorer les restrictions d'accès et appeler des méthodes privées (non indiqué 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 ait du sens, mais dans ces occasions-là, c’est un bon 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);
    }
}

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.

Vous pouvez également être intéressé par les travaux en cours sur 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

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

Java 8 est capable de gérer les références de méthode (MyClass :: new) avec " @ Functional Interface ". pointeurs. 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'avons-nous 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 - il ne s'agit que d'une syntaxe spéciale, comme 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 ÉCOUTEURS UNIQUEMENT ET SEULEMENT POUR CELA!

Parce que tous les autres pointeurs de fonction de ce type sont vraiment mauvais pour la lisibilité du code et pour la capacité à 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 de Java, vous devriez essayer Guava Libraries, dont les fonctionnalités et la syntaxe sont similaires à celles mentionnées par Adrian Petrescu ci-dessus.

Pour des recherches supplémentaires, consultez la feuille de mise en page de Java 8

.

et merci à Guy avec le chapeau pour le Lien vers la spécification du langage Java & # 167; 15.13 .

La réponse de @ sblundy est excellente, mais les classes internes anonymes ont deux petites failles: le principal est qu'elles ne sont pas 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 passer des paramètres qui peuvent servir de constantes dans votre équation. Si l'une de vos classes internes ressemble à ceci:

f(x,y)=x*y

mais vous en avez parfois 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 "passthrough" paramètre, 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 vous SAVEZ que votre fonction ne sera pas stockée / réutilisée, procédez comme suit:

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 intérieure anonyme - j'ai vu beaucoup de code redondant qui était "Required". parce que la première chose que le programmeur a faite a été de rester anonyme alors qu’il aurait dû utiliser une classe réelle et ne jamais repenser à sa décision.

Les bibliothèques de goyave de Google, qui deviennent très populaires, ont un générique Fonction et Prédicat est l'objet dans lequel ils ont travaillé dans de nombreuses parties de leur API.

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

Une des choses qui me manque le plus lorsque je programme en Java, ce sont les rappels de fonctions. Une des situations où la nécessité de les présenter continuellement se présentait était dans 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 qui sommeille 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 en plus de la copie d’un fichier d’un emplacement à un autre, par exemple, le coût de l’invocation de la méthode reflétée devient insignifiant.

Tandis que j'y pensais davantage, je me suis rendu compte qu'un rappel dans le paradigme de la POO nécessite de lier ensemble un objet et une méthode - entrez l'objet Callback.

Consultez ma solution basée sur la réflexion pour Rappels en Java . Gratuit pour toute utilisation.

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

Je devais utiliser une méthode statique variable avec une entrée et une sortie connues ( 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 appelé 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, ce n’est pas si mal.

Pour faire la même chose sans interface 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); } }

Découvrez le lambdaj

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

et en particulier sa nouvelle fonctionnalité 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

Wow, pourquoi ne pas simplement créer une classe Delegate, ce qui n’est pas si difficile étant donné ce que j’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 juste pour apprendre Java, j'ai besoin de pointeurs de fonction car ils sont très pratiques. Si vous êtes familier avec n'importe quelle classe traitant d'informations sur la méthode, vous pouvez le faire. Dans les bibliothèques java, java.lang.reflect.method.

Si vous utilisez toujours une interface, vous devez toujours la mettre en œuvre. En gestion d'événement, 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.

Aucune des réponses Java 8 n'a donné un exemple complet et cohérent, alors le 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-le en fournissant à la fonction une expression lambda:

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

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

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

voir Fonction de transfert avec comportement défini par paramètre en Java

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 qui a déjà été fournie avec Java SE 8) 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

Avant Java 8, la classe la plus proche é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, en Java 8, nous avons une alternative très soignée appelée lambda expression , qui peut être utilisé 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);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top