Méthode Java Pass en tant que paramètre


278

Je cherche un moyen de passer une méthode par référence. Je comprends que Java ne passe pas les méthodes comme paramètres, cependant, je voudrais obtenir une alternative.

On m'a dit que les interfaces sont l'alternative au passage de méthodes en tant que paramètres, mais je ne comprends pas comment une interface peut agir comme une méthode par référence. Si je comprends bien, une interface est simplement un ensemble abstrait de méthodes qui ne sont pas définies. Je ne veux pas envoyer une interface qui doit être définie à chaque fois car plusieurs méthodes différentes pourraient appeler la même méthode avec les mêmes paramètres.

Ce que je voudrais accomplir est quelque chose de similaire à ceci:

public void setAllComponents(Component[] myComponentArray, Method myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { //recursive call if Container
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } //end if node
        myMethod(leaf);
    } //end looping through components
}

invoquées telles que:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());

en ce moment ma solution est de passer un paramètre supplémentaire et d'utiliser un boîtier de commutation à l'intérieur pour sélectionner la méthode appropriée. Cependant, cette solution ne se prête pas à la réutilisation de code.

Voir aussi cette réponse stackoverflow.com/a/22933032/1010868 pour une question similaire
Tomasz Gawel

1
Voir aussi java-delegates ,
quel est le

Réponses:


233

Edit : à partir de Java 8, les expressions lambda sont une bonne solution comme l' ont souligné d' autres réponses . La réponse ci-dessous a été écrite pour Java 7 et versions antérieures ...


Jetez un œil au modèle de commande .

// NOTE: code not tested, but I believe this is valid java...
public class CommandExample 
{
    public interface Command 
    {
        public void execute(Object data);
    }

    public class PrintCommand implements Command 
    {
        public void execute(Object data) 
        {
            System.out.println(data.toString());
        }    
    }

    public static void callCommand(Command command, Object data) 
    {
        command.execute(data);
    }

    public static void main(String... args) 
    {
        callCommand(new PrintCommand(), "hello world");
    }
}

Edit: comme le souligne Pete Kirkham , il existe une autre façon de le faire en utilisant un visiteur . L'approche visiteur est un peu plus impliquée - vos nœuds doivent tous être attentifs aux visiteurs avec une acceptVisitor()méthode - mais si vous devez parcourir un graphique d'objet plus complexe, cela vaut la peine d'être examiné.


2
@Mac - bon! celui-ci revient encore et encore dans les langages sans méthodes de première classe comme moyen de facto de les simuler, donc ça vaut la peine de s'en souvenir.
Dan Vinton

7
C'est le modèle visiteur (séparez l'action d'itération sur une collection de la fonction appliquée à chaque membre de la collection), pas le modèle de commande (encapsulez les arguments d'un appel de méthode dans un objet). En particulier, vous n'encapsulez pas l'argument - il est fourni par la partie itération du modèle de visiteur.
Pete Kirkham

Non, vous n'avez besoin de la méthode d'acceptation que si vous combinez visite et double expédition. Si vous avez un visiteur monomorphe, c'est exactement le code que vous avez ci-dessus.
Pete Kirkham

En Java 8 pourrait ressembler à ex.operS (String :: toLowerCase, "STRING"). Voir le bel article: studytrails.com/java/java8/…
Zon

Pete Kirkham a raison: votre code implémente le modèle Visitor, pas le modèle Command (et c'est bien, car c'est ce dont OP a besoin.) Comme le dit Pete, vous n'encapsulez pas l'argument, donc vous ne faites pas Command - votre commande l'interface a un exécution qui prend un paramètre. Wikipédia ne le fait pas. Ceci est fondamental pour l'intention du modèle de commande. Comme le premier paragraphe dit "encapsule toutes les informations ... Ces informations incluent le nom de la méthode, l'objet qui possède la méthode et les valeurs des paramètres de la méthode ."
ToolmakerSteve

73

Dans Java 8, vous pouvez désormais transmettre une méthode plus facilement à l'aide d' expressions Lambda et de références de méthode. Tout d'abord, quelques informations: une interface fonctionnelle est une interface qui a une et une seule méthode abstraite, bien qu'elle puisse contenir un nombre illimité de méthodes par défaut (nouveau dans Java 8) et de méthodes statiques. Une expression lambda peut rapidement implémenter la méthode abstraite, sans toute la syntaxe inutile nécessaire si vous n'utilisez pas d'expression lambda.

Sans expressions lambda:

obj.aMethod(new AFunctionalInterface() {
    @Override
    public boolean anotherMethod(int i)
    {
        return i == 982
    }
});

Avec des expressions lambda:

obj.aMethod(i -> i == 982);

Voici un extrait du tutoriel Java sur les expressions Lambda :

Syntaxe des expressions Lambda

Une expression lambda se compose des éléments suivants:

  • Une liste de paramètres formels séparés par des virgules entre parenthèses. La méthode CheckPerson.test contient un paramètre, p, qui représente une instance de la classe Person.

    Remarque : Vous pouvez omettre le type de données des paramètres dans une expression lambda. De plus, vous pouvez omettre les parenthèses s'il n'y a qu'un seul paramètre. Par exemple, l'expression lambda suivante est également valide:

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
  • Le jeton flèche, ->

  • Un corps, qui se compose d'une seule expression ou d'un bloc d'instructions. Cet exemple utilise l'expression suivante:

    p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25

    Si vous spécifiez une seule expression, le runtime Java évalue l'expression, puis renvoie sa valeur. Alternativement, vous pouvez utiliser une déclaration de retour:

    p -> {
        return p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25;
    }

    Une déclaration de retour n'est pas une expression; dans une expression lambda, vous devez mettre des instructions entre accolades ({}). Cependant, vous n'avez pas besoin de placer une invocation de méthode void entre accolades. Par exemple, ce qui suit est une expression lambda valide:

    email -> System.out.println(email)

Notez qu'une expression lambda ressemble beaucoup à une déclaration de méthode; vous pouvez considérer les expressions lambda comme des méthodes anonymes - des méthodes sans nom.


Voici comment vous pouvez "passer une méthode" en utilisant une expression lambda:

interface I {
    public void myMethod(Component component);
}

class A {
    public void changeColor(Component component) {
        // code here
    }

    public void changeSize(Component component) {
        // code here
    }
}
class B {
    public void setAllComponents(Component[] myComponentArray, I myMethodsInterface) {
        for(Component leaf : myComponentArray) {
            if(leaf instanceof Container) { // recursive call if Container
                Container node = (Container)leaf;
                setAllComponents(node.getComponents(), myMethodInterface);
            } // end if node
            myMethodsInterface.myMethod(leaf);
        } // end looping through components
    }
}
class C {
    A a = new A();
    B b = new B();

    public C() {
        b.setAllComponents(this.getComponents(), component -> a.changeColor(component));
        b.setAllComponents(this.getComponents(), component -> a.changeSize(component));
    }
}

La classe Cpeut être raccourcie encore un peu plus en utilisant des références de méthode comme ceci:

class C {
    A a = new A();
    B b = new B();

    public C() {
        b.setAllComponents(this.getComponents(), a::changeColor);
        b.setAllComponents(this.getComponents(), a::changeSize);
    }
}

La classe A doit-elle être héritée de l'interface?
Serob_b

1
@Serob_b Nope. À moins que vous ne vouliez le passer comme référence de méthode (voir ::opérateur), peu importe ce qu'est A. a.changeThing(component)peut être changé en n'importe quelle instruction ou bloc de code que vous voulez, tant qu'il renvoie void.
The Guy with The Hat

29

Utilisez l' java.lang.reflect.Methodobjet et appelezinvoke


12
Je ne vois pas pourquoi pas. La question est de passer une méthode en paramètre et c'est une façon très valable de le faire. Cela peut également être enveloppé dans n'importe quel nombre de jolis motifs pour le rendre beau. Et c'est aussi générique que possible sans avoir besoin d'interfaces spéciales.
Vinodh Ramasubramanian

3
Avez-vous saisi la sécurité dans JavaScript fg? La sécurité des caractères n'est pas un argument.
Danubian Sailor

13
Comment la sécurité de type n'est-elle pas un argument lorsque le langage en question considère la sécurité de type comme l'une de ses composantes les plus fortes? Java est un langage fortement typé, et ce typage fort est l'une des raisons pour lesquelles vous le choisiriez sur un autre langage compilé.
Adam Parkin

21
"La fonction de réflexion principale a été initialement conçue pour les outils de création d'applications basés sur des composants. [...] En règle générale, les objets ne doivent pas être accessibles de manière réfléchie dans les applications normales lors de l'exécution." Point 53: Préférez les interfaces à la réflexion, à partir de Effective Java Second Edition. - C'est la ligne de pensée des créateurs de Java ;-)
Wilhem Meignan

8
Pas une utilisation justifiée de réfléchir. Je suis horrifié de voir tous les votes positifs. la réflexion n'a jamais été conçue pour être utilisée comme mécanisme général de programmation; utilisez-le uniquement lorsqu'il n'y a pas d'autre solution propre.
ToolmakerSteve

22

Depuis Java 8, il existe une Function<T, R>interface ( docs ), qui a une méthode

R apply(T t);

Vous pouvez l'utiliser pour passer des fonctions comme paramètres à d'autres fonctions. T est le type d'entrée de la fonction, R est le type de retour.

Dans votre exemple, vous devez passer une fonction qui prend le Componenttype en entrée et ne renvoie rien - Void. Dans ce cas, ce Function<T, R>n'est pas le meilleur choix, car il n'y a pas de boîte automatique de type Void. L'interface que vous recherchez s'appelle Consumer<T>( docs ) avec méthode

void accept(T t);

Cela ressemblerait à ceci:

public void setAllComponents(Component[] myComponentArray, Consumer<Component> myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { 
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } 
        myMethod.accept(leaf);
    } 
}

Et vous l'appeleriez en utilisant des références de méthode:

setAllComponents(this.getComponents(), this::changeColor);
setAllComponents(this.getComponents(), this::changeSize); 

En supposant que vous avez défini les méthodes changeColor () et changeSize () dans la même classe.


Si votre méthode accepte plusieurs paramètres, vous pouvez utiliser BiFunction<T, U, R>- T et U étant des types de paramètres d'entrée et R étant un type de retour. Il y en a aussi BiConsumer<T, U>(deux arguments, pas de type de retour). Malheureusement, pour 3 paramètres d'entrée et plus, vous devez créer vous-même une interface. Par exemple:

public interface Function4<A, B, C, D, R> {

    R apply(A a, B b, C c, D d);
}

19

Définissez d'abord une interface avec la méthode que vous souhaitez passer en paramètre

public interface Callable {
  public void call(int param);
}

Implémenter une classe avec la méthode

class Test implements Callable {
  public void call(int param) {
    System.out.println( param );
  }
}

// Invoque comme ça

Callable cmd = new Test();

Cela vous permet de passer cmd en paramètre et d'appeler l'appel de méthode défini dans l'interface

public invoke( Callable callable ) {
  callable.call( 5 );
}

1
Vous n'aurez peut-être pas à créer votre propre interface car java en a défini beaucoup pour vous: docs.oracle.com/javase/8/docs/api/java/util/function/…
slim

@slim Point intéressant, dans quelle mesure ces définitions sont-elles stables, sont-elles destinées à être utilisées normalement comme vous le suggérez, ou sont-elles susceptibles de se casser?
Manuel

@slim En fait, les docs répondent que: "Les interfaces de ce package sont des interfaces fonctionnelles à usage général utilisées par le JDK, et peuvent également être utilisées par le code utilisateur."
Manuel

14

Bien que cela ne soit pas encore valable pour Java 7 et les versions antérieures , je pense que nous devons regarder vers l'avenir et au moins reconnaître les changements à venir dans les nouvelles versions telles que Java 8.

À savoir, cette nouvelle version apporte des lambdas et des références de méthode à Java (ainsi que de nouvelles API , qui sont une autre solution valide à ce problème. Bien qu'ils nécessitent toujours une interface, aucun nouvel objet n'est créé, et les fichiers de classe supplémentaires ne doivent pas polluer les répertoires de sortie en raison de différents prise en charge par la JVM.

Les deux versions (lambda et référence de méthode) nécessitent une interface disponible avec une seule méthode dont la signature est utilisée:

public interface NewVersionTest{
    String returnAString(Object oIn, String str);
}

Les noms de méthodes n'auront plus d'importance à partir de maintenant. Lorsqu'un lambda est accepté, une référence de méthode l'est également. Par exemple, pour utiliser notre signature ici:

public static void printOutput(NewVersionTest t, Object o, String s){
    System.out.println(t.returnAString(o, s));
}

Ceci est juste une simple invocation d'interface, jusqu'à ce que le lambda 1 soit passé:

public static void main(String[] args){
    printOutput( (Object oIn, String sIn) -> {
        System.out.println("Lambda reached!");
        return "lambda return";
    }
    );
}

Cela produira:

Lambda reached!
lambda return

Les références de méthode sont similaires. Donné:

public class HelperClass{
    public static String testOtherSig(Object o, String s){
        return "real static method";
    }
}

et principal:

public static void main(String[] args){
    printOutput(HelperClass::testOtherSig);
}

la sortie serait real static method. Les références de méthode peuvent être statiques, d'instance, non statiques avec des instances arbitraires et même des constructeurs . Pour le constructeur, quelque chose de semblable ClassName::newserait utilisé.

1 Ceci n'est pas considéré comme un lambda par certains, car il a des effets secondaires. Il illustre cependant l'utilisation de celui-ci d'une manière plus simple à visualiser.


12

La dernière fois que j'ai vérifié, Java n'est pas capable de faire nativement ce que vous voulez; vous devez utiliser des solutions de contournement pour contourner ces limitations. Pour moi, les interfaces SONT une alternative, mais pas une bonne alternative. Peut-être que celui qui vous a dit que cela signifiait quelque chose comme ceci:

public interface ComponentMethod {
  public abstract void PerfromMethod(Container c);
}

public class ChangeColor implements ComponentMethod {
  @Override
  public void PerfromMethod(Container c) {
    // do color change stuff
  }
}

public class ChangeSize implements ComponentMethod {
  @Override
  public void PerfromMethod(Container c) {
    // do color change stuff
  }
}

public void setAllComponents(Component[] myComponentArray, ComponentMethod myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { //recursive call if Container
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } //end if node
        myMethod.PerfromMethod(leaf);
    } //end looping through components
}

Avec quoi vous invoqueriez alors:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());

6

Si vous n'avez pas besoin de ces méthodes pour renvoyer quelque chose, vous pouvez leur faire renvoyer des objets Runnable.

private Runnable methodName (final int arg) {
    return (new Runnable() {
        public void run() {
          // do stuff with arg
        }
    });
}

Ensuite, utilisez-le comme:

private void otherMethodName (Runnable arg){
    arg.run();
}

2

Je n'ai trouvé aucun exemple suffisamment explicite pour moi sur la façon d'utiliser java.util.function.Functionune méthode simple comme fonction de paramètre. Voici un exemple simple:

import java.util.function.Function;

public class Foo {

  private Foo(String parameter) {
    System.out.println("I'm a Foo " + parameter);
  }

  public static Foo method(final String parameter) {
    return new Foo(parameter);
  }

  private static Function parametrisedMethod(Function<String, Foo> function) {
    return function;
  }

  public static void main(String[] args) {
    parametrisedMethod(Foo::method).apply("from a method");
  }
}

Fondamentalement, vous avez un Fooobjet avec un constructeur par défaut. Un methodqui sera appelé comme paramètre du parametrisedMethodqui est de type Function<String, Foo>.

  • Function<String, Foo>signifie que la fonction prend un Stringparamètre as et renvoie a Foo.
  • Le Foo::Methodcorrespond à un lambda commex -> Foo.method(x);
  • parametrisedMethod(Foo::method) pourrait être considéré comme x -> parametrisedMethod(Foo.method(x))
  • C'est .apply("from a method")essentiellement à faireparametrisedMethod(Foo.method("from a method"))

Qui retournera alors dans la sortie:

>> I'm a Foo from a method

L'exemple devrait fonctionner tel quel, vous pouvez ensuite essayer des choses plus compliquées à partir des réponses ci-dessus avec différentes classes et interfaces.


pour utiliser l'appel d'application dans Android, vous avez besoin d'une API minimum 24
Ines Belhouchet

1

Java a un mécanisme pour passer le nom et l'appeler. Cela fait partie du mécanisme de réflexion. Votre fonction doit prendre un paramètre supplémentaire de la classe Method.

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}

1

Je ne suis pas un expert java mais je résous votre problème comme ceci:

@FunctionalInterface
public interface AutoCompleteCallable<T> {
  String call(T model) throws Exception;
}

Je définis le paramètre dans mon interface spéciale

public <T> void initialize(List<T> entries, AutoCompleteCallable getSearchText) {.......
//call here
String value = getSearchText.call(item);
...
}

Enfin, j'implémente la méthode getSearchText lors de l'appel de la méthode initialize .

initialize(getMessageContactModelList(), new AutoCompleteCallable() {
          @Override
          public String call(Object model) throws Exception {
            return "custom string" + ((xxxModel)model.getTitle());
          }
        })

En fait, c'est la meilleure réponse et la bonne façon de le faire.
Mérite

0

Utilisez le modèle Observer (parfois aussi appelé modèle Listener):

interface ComponentDelegate {
    void doSomething(Component component);
}

public void setAllComponents(Component[] myComponentArray, ComponentDelegate delegate) {
    // ...
    delegate.doSomething(leaf);
}

setAllComponents(this.getComponents(), new ComponentDelegate() {
                                            void doSomething(Component component) {
                                                changeColor(component); // or do directly what you want
                                            }
                                       });

new ComponentDelegate()... déclare un type anonyme implémentant l'interface.


8
Ce n'est pas le motif que vous recherchez.
Pete Kirkham

1
Le modèle d'observateur consiste à faire abstraction de la capacité de répondre à un changement. L'OP souhaite faire abstraction de l'action entreprise au niveau de chaque article d'une collection, loin du code itérant sur la collection, qui est le modèle de visiteur.
Pete Kirkham

Le modèle Observer / Listener est en fait le même que le modèle Command. Ils ne diffèrent que par leur intention. L'observateur concerne la notification tandis que la commande remplace les fonctions / lambdas de première classe. Le visiteur d'autre part est quelque chose de complètement différent. Je ne pense pas que cela puisse être expliqué en quelques phrases alors jetez un œil à en.wikipedia.org/wiki/Visitor_pattern
EricSchaefer

0

Voici un exemple de base:

public class TestMethodPassing
{
    private static void println()
    {
        System.out.println("Do println");
    }

    private static void print()
    {
        System.out.print("Do print");
    }

    private static void performTask(BasicFunctionalInterface functionalInterface)
    {
        functionalInterface.performTask();
    }

    @FunctionalInterface
    interface BasicFunctionalInterface
    {
        void performTask();
    }

    public static void main(String[] arguments)
    {
        performTask(TestMethodPassing::println);
        performTask(TestMethodPassing::print);
    }
}

Production:

Do println
Do print

0

Je n'ai trouvé aucune solution ici qui montre comment passer une méthode avec des paramètres liés à elle comme paramètre d'une méthode. Ci-dessous est un exemple de la façon dont vous pouvez passer une méthode avec des valeurs de paramètre déjà liées.

  1. Étape 1: Créez deux interfaces, l'une avec le type de retour, l'autre sans. Java a des interfaces similaires mais elles sont de peu d'utilité pratique car elles ne prennent pas en charge le lancement d'exceptions.


    public interface Do {
    void run() throws Exception;
    }


    public interface Return {
        R run() throws Exception;
    }
  1. Exemple d'utilisation des deux interfaces pour encapsuler l'appel de méthode dans la transaction. Notez que nous passons la méthode avec les paramètres réels.


    //example - when passed method does not return any value
    public void tx(final Do func) throws Exception {
        connectionScope.beginTransaction();
        try {
            func.run();
            connectionScope.commit();
        } catch (Exception e) {
            connectionScope.rollback();
            throw e;
        } finally {
            connectionScope.close();
        }
    }

    //Invoke code above by 
    tx(() -> api.delete(6));

Un autre exemple montre comment passer une méthode qui retourne réellement quelque chose



        public  R tx(final Return func) throws Exception {
    R r=null;
    connectionScope.beginTransaction();
    try {
                r=func.run();
                connectionScope.commit();
            } catch (Exception e) {
                connectionScope.rollback();
                throw e;
            } finally {
                connectionScope.close();
            }
        return r;
        }
        //Invoke code above by 
        Object x= tx(() -> api.get(id));

0

Exemple de solution avec réflexion, la méthode passée doit être publique

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class Program {
    int i;

    public static void main(String[] args) {
        Program   obj = new Program();    //some object

        try {
            Method method = obj.getClass().getMethod("target");
            repeatMethod( 5, obj, method );
        } 
        catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            System.out.println( e ); 
        }
    }

    static void repeatMethod (int times, Object object, Method method)
        throws IllegalAccessException, InvocationTargetException {

        for (int i=0; i<times; i++)
            method.invoke(object);
    }
    public void target() {                 //public is necessary
        System.out.println("target(): "+ ++i);
    }
}

0

J'apprécie les réponses ci-dessus mais j'ai pu obtenir le même comportement en utilisant la méthode ci-dessous; une idée empruntée aux rappels Javascript. Je suis ouvert à la correction mais jusqu'ici tout va bien (en production).

L'idée est d'utiliser le type de retour de la fonction dans la signature, ce qui signifie que le rendement doit être statique.

Vous trouverez ci-dessous une fonction qui exécute un processus avec un délai d'expiration.

public static void timeoutFunction(String fnReturnVal) {

    Object p = null; // whatever object you need here

    String threadSleeptime = null;

    Config config;

    try {
        config = ConfigReader.getConfigProperties();
        threadSleeptime = config.getThreadSleepTime();

    } catch (Exception e) {
        log.error(e);
        log.error("");
        log.error("Defaulting thread sleep time to 105000 miliseconds.");
        log.error("");
        threadSleeptime = "100000";
    }

    ExecutorService executor = Executors.newCachedThreadPool();
    Callable<Object> task = new Callable<Object>() {
        public Object call() {
            // Do job here using --- fnReturnVal --- and return appropriate value
            return null;
        }
    };
    Future<Object> future = executor.submit(task);

    try {
        p = future.get(Integer.parseInt(threadSleeptime), TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        log.error(e + ". The function timed out after [" + threadSleeptime
                + "] miliseconds before a response was received.");
    } finally {
        // if task has started then don't stop it
        future.cancel(false);
    }
}

private static String returnString() {
    return "hello";
}

public static void main(String[] args) {
    timeoutFunction(returnString());
}
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.