web-dev-qa-db-fra.com

Méthode Java Pass en tant que paramètre

Je cherche un moyen de passer une méthode par référence. Je comprends que Java ne passe pas les méthodes en tant que paramètres, cependant, j'aimerais obtenir une alternative. 

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

Ce que je voudrais accomplir est semblable à 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és tels que:

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

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


Jetez un coup d'oeil 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 Pete Kirkham fait remarquer , il existe un autre moyen de faire cela en utilisant un Visiteur . L'approche des visiteurs est un peu plus complexe - vos nœuds doivent tous être sensibles à la notion de visiteur avec une méthode acceptVisitor() - mais si vous devez parcourir un graphe d'objet plus complexe, nous vous conseillons de l'examiner.

204
Dan Vinton

En Java 8, vous pouvez désormais transmettre une méthode plus facilement à l'aide de expressions lambda et des références de méthode. Tout d’abord, un peu d’arrière-plan: une interface fonctionnelle est une interface qui n’a qu’une et une seule méthode abstraite, bien qu’elle puisse contenir un nombre quelconque de méthodes par défaut } (nouvelles en 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 de le tutoriel Java sur les expressions Lambda :

Syntaxe des expressions lambda

Une expression lambda comprend ce qui suit:

  • 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.

    Note: Vous peut omettre le type de données des paramètres dans une expression lambda. Dans De plus, vous pouvez omettre les parenthèses s’il n’ya 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 consiste en une seule expression ou 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. Sinon, 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 inclure des instructions entre accolades ({}). Cependant, vous n'avez pas pour inclure une invocation de méthode vide entre accolades. Par exemple, le 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: 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 C peut être encore plus courte en utilisant des références de méthodes 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);
    }
}
54

Utilisez l'objet Java.lang.reflect.Method et appelez invoke

24

Commencez par définir une interface avec la méthode que vous souhaitez transmettre en tant que 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 tant que paramètre et d’appeler l’appel de méthode défini dans l’interface.

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

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

À savoir, cette nouvelle version apporte lambdas et des références de méthodes à Java (avec de nouvelles API , qui constituent une autre solution valable à ce problème. Même si elles nécessitent toujours une interface, aucun nouvel objet n'est créé et des fichiers de classe supplémentaires. Il n'est pas nécessaire de polluer les répertoires de sortie en raison d'une gestion différente par la JVM. 

Les deux versions (lambda et method reference) 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. Là où un lambda est accepté, une référence à une méthode l'est aussi. 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’au lambda1 est 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éthodes 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::new serait utilisé.

1 Ce n'est pas considéré comme un lambda par certains, car il a des effets secondaires. Il illustre toutefois l’utilisation de l’un dans un mode plus simple à visualiser.

12
Andrey Akhmetov

La dernière fois que j'ai vérifié, Java n'est pas capable de faire ce que vous voulez de manière native. vous devez utiliser des solutions de rechange pour contourner ces limitations. Autant que je sache, les interfaces sont une alternative, mais pas une bonne alternative. Peut-être que celui qui vous a dit que ça voulait dire quelque chose comme ça:

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
}

Que vous invoqueriez ensuite avec:

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

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

Alors utilisez-le comme:

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

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

R apply(T t);

Vous pouvez l'utiliser pour transmettre des fonctions en tant que 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 transmettre une fonction qui utilise le type Component comme entrée et qui ne renvoie rien - Void. Dans ce cas, Function<T, R> n'est pas le meilleur choix, car il n'y a pas de liste déroulante automatique de type Void. L’interface que vous recherchez s’appelle Consumer<T> ( docs ) with method 

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'appelleriez en utilisant des références de méthodes:

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

En supposant que vous ayez 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 les types de paramètres d'entrée et R le type de retour. Il existe également BiConsumer<T, U> (deux arguments, aucun type de retour). Malheureusement pour 3 paramètres d'entrée et plus, vous devez créer une interface par vous-même. Par exemple:

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

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

Utilisez le modèle Observer (parfois également 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.

1
EricSchaefer

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

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

Je ne suis pas un expert en Java mais je résous votre problème de la manière suivante:

@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 getSearchText method en appelant initialize method.

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

Je ne pense pas que les lambdas soient destinés à cela ... Java n'est pas un langage de programmation fonctionnel et ne le sera jamais, nous ne transmettons pas les méthodes en tant que paramètres. Cela étant dit, rappelez-vous que Java est orienté objet et que, dans cet esprit, nous pouvons faire ce que nous voulons. La première idée est de simplement passer un "objet contenant une méthode" en tant que paramètre . Ainsi, chaque fois que vous devez "transmettre" la méthode, il suffit de passer une instance de cette classe. Notez que lorsque vous définissez la méthode, vous devez ajouter en tant que paramètre une instance de la classe qui contient la méthode. Cela devrait fonctionner mais ce n’est pas ce que nous souhaitons car vous ne pouvez pas redéfinir la méthode à moins d’avoir accès au code de classe, ce qui est impossible dans de nombreux cas. de plus, je pense que si quelqu'un a besoin de passer une méthode en paramètre, c'est parce que le comportement de la méthode doit être dynamique. Ce que je veux dire, c'est que le programmeur qui utilise vos classes devrait pouvoir choisir ce que la méthode devrait retourner mais pas son type. Heureusement pour nous, Java a une solution belle et simple: les classes abstraites. Les classes abstraites, en un mot, sont utilisées lorsque vous connaissez la "signature" d'une méthode "mais pas son comportement ... Vous pouvez envelopper le nom et le type de la méthode dans une classe abstraite et passer une instance de cette classe en tant que paramètre pour la méthode ... attendez ... n'est-ce pas la même chose qu'avant et pouvez-vous avoir une instance d'une classe abstraite? non et non ... mais aussi oui ... lorsque vous créez une méthode abstraite vous DEVEZ également le redéfinir dans une classe qui étend la classe abstraite et à cause de la liaison dynamique de Java, Java utilisera toujours (à moins que vous ne le déclariez statique, privé ou autre) la version redéfinie de celle-ci. Voici un exemple ... Supposons que nous voulions appliquer une fonction à un tableau de nombres: si nous voulons équilibrer l'entrée-sortie, elle devrait ressembler à ceci [1,2,3,4, ...] -> [1,4,9,16 , ...] (dans un langage de programmation fonctionnel tel que haskell, c'est facile grâce à des outils tels que "map", ...). Notez qu'il n'y a rien de spécial dans les nombres carrés, nous pourrions appliquer n'importe quelle fonction que nous souhaitons. t. Donc le code devrait être quelque chose comme ceci [args], f -> [f (args)]. Retour à Java => une fonction est juste une méthode, donc nous voulons une fonction qui applique une autre fonction à un tableau. En un mot, nous devons passer une méthode en tant que paramètre. Voici comment je le ferais ==>

1) DÉFINITION DE LA CATÉGORIE DE RÉSERVOIR D'EMBALLAGE ET DE LA MÉTHODE

public  abstract class Function 
{
    public abstract double f(double x);
}

2) DÉFINITION DE LA CLASSE AVEC LA MÉTHODE APPLY_TO_ARRAY 

public class ArrayMap 
{
public static double[] apply_to_array(double[] arr, Function fun)
{
    for(int i=0; i<arr.length;i++)
    {
        arr[i]=fun.f(arr[i]);
    }
    return arr;
}
}

3) CRÉER UNE CLASSE DE TESTEUR ET S'AMUSER

public class Testclass extends Function
{
    public static void main(String[] args) 
    {
        double[] myarr = {1,2,3,4};
        ArrayMap.apply_to_array(myarr, new Testclass());
        for (double k : myarr)
        {
        System.out.println(k);
        }

    }

    @Override
    public double f(double x) 
    {

        return Math.log(x);
    }   
}

Notez que nous devons passer un objet de type Function et que, puisque Testclass étend la classe Function, nous pouvons l’utiliser, la conversion est automatique. 

0
omar kahol

Je n'ai trouvé aucun exemple assez explicite sur la façon d'utiliser Java.util.function.Function pour une méthode simple en tant que paramètre fonction. 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");
  }
}

En gros, vous avez un objet Foo avec un constructeur par défaut. Une method qui sera appelée en tant que paramètre de la parametrisedMethod qui est de type Function<String, Foo>.

  • Function<String, Foo> signifie que la fonction prend une String en paramètre et renvoie une Foo.
  • Le Foo::Method correspond à un lambda comme x -> Foo.method(x);
  • parametrisedMethod(Foo::method) pourrait être vu comme x -> parametrisedMethod(Foo.method(x))
  • La .apply("from a method") est fondamentalement à faire parametrisedMethod(Foo.method("from a method"))

Ce qui reviendra ensuite dans la sortie:

>> I'm a Foo from a method

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

0
Sylhare

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

Sortie:

Do println
Do print
0
BullyWiiPlaza

Je n'ai trouvé aucune solution ici qui montre comment passer une méthode avec des paramètres liés en tant que paramètre d'une méthode. Voici 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 possède des interfaces similaires, mais leur utilisation est peu pratique car elles ne prennent pas en charge le lancement d'exception.


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


    public interface Return {
        R run() throws Exception;
    }

  1. Exemple d'utilisation des deux interfaces pour encapsuler un appel de méthode. Notez que nous passons 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 renvoie effectivement 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
Stan Sokolov