Méthode Java Pass comme 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 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éfini temps 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é tel que:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());
Author: Yojimbo, 2010-02-02

13 answers

Edit: de Java 8, les expressions lambda sont une solution sympa comme autres réponses l'ont souligné. 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 Pete Kirkham le souligne, il existe une autre façon de le faire en utilisant un Visitor. L'approche visiteur est un peu plus impliquée - vos nœuds doivent tous être conscients des visiteurs avec un acceptVisitor() méthode - mais si vous avez besoin de parcourir un graphe d'objet plus complexe, cela vaut la peine d'être examiné.

 196
Author: Dan Vinton, 2017-05-23 12:18:26

En Java 8, vous pouvez maintenant passer une méthode plus facilement en utilisant Lambda Expressions et des références de méthode. Tout d'abord, un arrière-plan: une interface fonctionnelle est une interface qui a une et une seule méthode abstraite, bien qu'elle puisse contenir n'importe quel nombre de méthodes par défaut (nouveau dans Java 8) et 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 lambda expressions:

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 est constituée des éléments suivants:

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

    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'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
    
  • La flèche jeton, ->

  • Un corps, qui consiste en une seule expression ou un bloc d'instruction. Cet exemple utilise l'expression suivante:

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

    Si vous spécifiez un expression, puis le runtime Java évalue l'expression, puis renvoie sa valeur. Alternativement, vous pouvez utiliser une instruction return:

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

    Une instruction return n'est pas une expression; dans une expression lambda, vous devez placer les instructions entre accolades ({}). Cependant, vous n'avez pas pour entourer une invocation de méthode void entre accolades. Par exemple, l' voici une expression lambda valide:

    email -> System.out.println(email)
    

Notez qu'une expression lambda ressemble beaucoup à une méthode déclaration; vous pouvez considérer les expressions lambda anonyme 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 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);
    }
}
 49
Author: The Guy with The Hat, 2018-10-04 17:58:16

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

 24
Author: Vinodh Ramasubramanian, 2010-02-02 19:25:54

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 comme paramètre et d'appeler l'appel de méthode défini dans l'interface

public invoke( Callable callable ) {
  callable.call( 5 );
}
 17
Author: stacker, 2011-08-23 09:59:41

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 de telles limitations. Pour autant que je le vois, les interfaces SONT une alternative, mais pas une bonne alternative. Peut-être celui qui vous a dit que cela signifiait 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
Author: user246091, 2010-02-02 19:38:19

Bien que cela ne soit pas encore valide pour Java 7 et ci-dessous, je pense que nous devrions regarder vers l'avenir et au moins reconnaître les changements à venir dans de nouvelles versions telles que Java 8.

À savoir, cette nouvelle version apporte lambdas et des références de méthode à Java (avec 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 classfiles supplémentaires n'ont pas besoin de polluer les répertoires de sortie en raison de manipulation différente par la JVM.

Les deux saveurs (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 des 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 lambda1 obtient passé:

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

Cette volonté sortie:

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::new serait utilisé.

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

 11
Author: Andrey Akhmetov, 2016-07-26 17:34:53

Si vous n'avez pas besoin de ces méthodes pour renvoyer quelque chose, vous pouvez les faire retourner des objets exécutables.

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

, Puis l'utiliser comme:

private void otherMethodName (Runnable arg){
    arg.run();
}
 6
Author: Smig, 2014-06-12 13:40:33

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 passer une fonction qui prend Component type en entrée et ne renvoie rien - Void. Dans ce cas, Function<T, R> n'est pas le meilleur choix, car il n'y a pas d'autoboxing de type Void. L'interface que vous recherchez s'appelle Consumer<T> (docs) avec la 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'appelleriez 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 plus d'un paramètre, 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 a aussi BiConsumer<T, U> (deux arguments, pas de type de retour). Malheureusement pour 3 et plusieurs paramètres d'entrée, 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);
}
 3
Author: JakubM, 2017-10-05 14:26:37

Utilisez le motif Observateur (parfois aussi appelé motif auditeur):

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
Author: EricSchaefer, 2010-02-02 19:33:33

Java a un mécanisme pour passer 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 méthode de classe.

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}
 1
Author: David Gruzman, 2010-02-02 19:47:16

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
Author: BullyWiiPlaza, 2016-08-30 09:49:40

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 getSearchTexttout en appelant la méthode initialize.

initialize(getMessageContactModelList(), new AutoCompleteCallable() {
          @Override
          public String call(Object model) throws Exception {
            return "custom string" + ((xxxModel)model.getTitle());
          }
        })
 0
Author: Sercan özen, 2017-08-11 11:15:36

Je ne pense pas que les lambdas soient faits pour cela... Java n'est pas un langage de programmation fonctionnel et ne le sera jamais, nous ne transmettons pas les méthodes comme paramètres. Cela étant dit, rappelez-vous que Java est orienté objet et dans cet esprit, nous pouvons faire ce que nous voulons. La première idée est de simplement passer un" objet qui contient une méthode " en tant que paramètre. Donc, chaque fois que vous devez "passer" la méthode, passez simplement une instance de cette classe. Notez que lorsque vous définissez la méthode vous devez ajouter comme paramètre un instance de la classe qui contient la méthode. Cela devrait fonctionner mais ce n'est pas ce que nous voulons car vous ne pouvez pas redéfinir la méthode à moins d'avoir accès au code de classe et ce n'est pas possible dans de nombreux cas; de plus, je pense que si quelqu'un doit passer une méthode en paramètre, c'est parce que le comportement de la méthode doit Ce que je veux dire, c'est que le programmeur qui utilise vos classes devrait pouvoir choisir ce que la méthode devrait renvoyer mais pas son type. Heureusement pour nous Java a une solution belle et simple: des cours abstraits. 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 de la méthode... Attendre... 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 la redéfinir dans une classe qui étend la classe abstraite et en raison de la liaison dynamique de java, Java utilisera toujours (sauf si vous la déclarez statique, privée et d'autres choses) la version redéfinie. Ici est un exemple... Supposons que nous souhaitions appliquer une fonction à un tableau de nombres: donc, si nous voulons quadriller l'entrée-sortie devrait ressembler à ceci [1,2,3,4,...]->[1,4,9,16,...] (dans un langage de programmation fonctionnel comme haskell c'est chose facile grâce à certains outils comme la 'carte',...). Notez qu'il n'y a rien de spécial à propos de la quadrature des nombres, nous pourrions appliquer n'importe quelle fonction que nous voulons. Donc, le code devrait être quelque chose comme ceci [args], f -> [f(args)]. Retour à java = > une fonction est juste une méthode donc ce que nous voulons est 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ÉFINIR LA CLASSE ABSTRAITE WRAPPER ET LA MÉTHODE

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

2) DÉFINIR 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 UN TESTEUR-CLASSE ET AVOIR DU PLAISIR

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 puisque Testclass étend la classe de fonction, nous pouvons l'utiliser, la distribution est automatique.

 0
Author: omar kahol, 2018-07-06 15:52:42