348 votes

Java passe la méthode comme paramètre

Je cherche un moyen de transmettre un paramètre par référence. Je comprends que Java ne transmet pas les méthodes en tant que paramètres, mais j'aimerais trouver 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 parce que 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és tels que :

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

Merci d'avance, Mac

2 votes

Pour l'instant, ma solution consiste à passer un paramètre supplémentaire et à utiliser un switch case à l'intérieur pour sélectionner la méthode appropriée. Cependant, cette solution ne se prête pas à la réutilisation du code.

2 votes

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

3 votes

262voto

Dan Vinton Points 11975

Jetez un coup d'œil à la schéma 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 souligne il existe une autre façon de procéder en utilisant un fichier Visiteur . L'approche par les visiteurs est un peu plus complexe - vos nœuds doivent tous être conscients de la présence de visiteurs grâce à un code d'accès. acceptVisitor() mais si vous devez traverser un graphe d'objets plus complexe, cela vaut la peine de l'examiner.

3 votes

@Mac - bien ! cette question revient souvent dans les langages sans méthodes de première classe comme moyen de facto de les simuler, donc il est bon de s'en souvenir.

9 votes

Il s'agit du modèle visiteur (séparer l'action d'itération sur une collection de la fonction appliquée à chaque membre de la collection), et non du modèle commande (encapsuler les arguments d'un appel de méthode dans un objet). Vous n'encapsulez pas spécifiquement l'argument - il est fourni par la partie itération du visitor pattern.

1 votes

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

97voto

En Java 8, vous pouvez maintenant passer une méthode plus facilement en utilisant Expressions lambda . Tout d'abord, un peu de contexte. Une interface fonctionnelle est une interface qui possède une et une seule méthode abstraite, bien qu'elle puisse contenir un nombre quelconque de méthodes abstraites. Méthodes par défaut (nouveau dans Java 8) et les 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 se compose des éléments suivants :

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

    Note : Vous pouvez omettre le type de données des paramètres dans une expression lambda. Sur En outre, 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 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 moteur d'exécution Java évalue l'expression et renvoie sa valeur. Alternativement, vous pouvez utiliser une instruction de retour :

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

    Une instruction de retour n'est pas une expression ; dans une expression lambda, vous devez mettre les instructions entre accolades ({}). Cependant, vous n'avez pas à d'entourer d'accolades une invocation de méthode void. Par exemple, l'expression suivante 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));
    }
}

L'exemple ci-dessus peut être encore plus court en utilisant la fonction :: opérateur .

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

Avertissement : je ne peux pas exécuter Java 8 sur mon ordinateur, car il n'est pas compatible avec Windows XP. Tous les exemples inclus dans cette réponse peuvent comporter des erreurs, car je n'ai pas été en mesure de les tester.

0 votes

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

1 votes

@Serob_b Nope. À moins que vous ne souhaitiez le passer comme une référence de méthode (cf. :: ), la nature de A n'a pas d'importance. a.changeThing(component) peut être transformée en n'importe quelle instruction ou bloc de code que vous voulez, tant qu'elle renvoie un résultat nul.

32voto

Utilisez le java.lang.reflect.Method et appeler invoke

13 votes

Je ne vois pas pourquoi pas. La question est de passer une méthode comme paramètre et c'est une façon très valable de le faire. Cette méthode peut également être enveloppée dans un certain nombre de modèles jolis pour la rendre plus attrayante. Et c'est aussi générique que possible, sans avoir besoin d'interfaces spéciales.

0 votes

La réponse à la question "pourquoi pas ?" est la sécurité des types (ou son absence).

3 votes

Connaissez-vous la sécurité de type en JavaScript, par exemple ? La sécurité de type n'est pas un argument.

21voto

stacker Points 34209

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

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

Implémentez une classe avec la méthode

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

// Invoke like that

Callable cmd = new Test();

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

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

3 votes

Vous n'aurez peut-être pas à créer votre propre interface, car Java en a défini un grand nombre pour vous : docs.oracle.com/javase/8/docs/api/java/util/function/

0 votes

@slim Point intéressant, quelle est la stabilité de ces définitions, sont-elles destinées à être utilisées de manière habituelle comme vous le suggérez, ou sont-elles susceptibles de se briser ?

0 votes

@slim En fait, les docs répondent à cela : "Les interfaces de ce paquet sont des interfaces fonctionnelles d'usage général utilisées par le JDK, et sont disponibles pour être utilisées par le code utilisateur également."

19voto

hexafraction Points 16201

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

En effet, cette nouvelle version apporte lambdas et les références de méthodes à Java (ainsi que nouvelles APIs qui constituent une autre solution valable à 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 d'une gestion différente par la JVM.

Les deux variantes (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));
}

C'est juste une simple invocation d'interface, jusqu'à la lambda 1 est adopté :

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

Cela produira un résultat :

Lambda reached!
lambda return

Les références des 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 pointeurs 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 lambda d'une manière plus simple à visualiser.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X