La 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 aux méthodes passantes 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 qui ne sont pas définies. Je ne veux pas envoyer une interface qui doit pour être défini à chaque fois parce que plusieurs méthodes pourrait appeler la même méthode avec les mêmes paramètres.

ce que je voudrais accomplir est quelque chose de 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ée telle que:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());
211
demandé sur Yojimbo 2010-02-02 22:20:03

13 réponses

Edit : à partir de Java 8, lambda expressions sont une bonne solution comme autres réponses ont souligné. La réponse ci-dessous a été écrite pour Java 7 et plus tôt...


regardez le command pattern .

// 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: as Pete Kirkham points sur , il y a une autre façon de faire cela en utilisant un visiteur . L'approche du visiteur est un peu plus impliquée - vos noeuds ont tous besoin d'être conscients du visiteur avec une méthode acceptVisitor() -mais si vous avez besoin de traverser un graphe d'objet plus complexe alors il est intéressant d'examiner.

196
répondu 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 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 en 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 une lambda expression.

sans les expressions lambda:

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

avec les expressions lambda:

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

voici un extrait de le tutoriel Java sur Lambda Expressions :

syntaxe des expressions Lambda

une expression lambda se compose des termes suivants:

  • 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, ce qui représente une instance de la classe Personne.



    Note : Vous peut omettre le type de données des paramètres dans une expression lambda. Dans outre, vous pouvez omettre les parenthèses si il y a un seul paramètre. Par exemple, l'expression lambda suivante est aussi valable:

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

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

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

    si vous spécifiez une seule expression, alors L'exécution Java évalue l'expression et renvoie sa valeur. Alternativement, vous pouvez utiliser un État 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 inclure des instructions dans des accolades ({}). Cependant, vous n'avez pas pour enfermer une invocation de la méthode du vide dans des accolades. Par exemple, l' voici 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 anonyme méthodes de 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));
    }
}

Classe C peut être raccourcie même un peu plus par l'utilisation de références de méthode comme cela:

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
répondu The Guy with The Hat 2018-10-04 17:58:16

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

24
répondu Vinodh Ramasubramanian 2010-02-02 19:25:54

définissez D'abord une Interface avec la méthode que vous voulez passer comme paramètre

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

mettre en Œuvre une classe avec la méthode

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

// Appeler comme ça

Callable cmd = new Test();

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

public invoke( Callable callable ) {
  callable.call( 5 );
}
17
répondu 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 rechange" 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 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
}

que vous invoqueriez alors avec:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());
11
répondu user246091 2010-02-02 19:38:19

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

à savoir, cette nouvelle version apporte lambdas et des références de méthode à Java (avec nouveaux API , qui sont une autre solution valide à ce problème. Alors qu'ils ont toujours besoin d'une interface Aucun nouvel objet ne sont créé, et les classfiles supplémentaires n'ont pas besoin de polluer les répertoires de sortie en raison de la manipulation différente par le JVM.

les deux saveurs (lambda et méthode de référence) 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. Où lambda est acceptée, une méthode de référence est ainsi. 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 un simple interface invocation, jusqu'à ce que la lambda 1 se passe:

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

Ce sera de sortie:

Lambda reached!
lambda return
Les références à la méthode

sont similaires. Donnée:

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

et main:

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 les constructeurs . Pour le constructeur quelque chose qui s'apparente à 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 d'un tel outil d'une manière plus directe à visualiser.

11
répondu Andrey Akhmetov 2016-07-26 17:34:53

si vous n'avez pas besoin de ces méthodes pour retourner 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
       }
    }
}

alors utilisez-le comme:

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

depuis Java 8 Il y a une interface Function<T, R> ( docs ), qui a la 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 Component type comme entrée et ne retourne rien - Void . Dans ce cas, Function<T, R> n'est pas le meilleur choix, puisqu' il n'y a pas d'autoboxing de type vide. L'interface que vous recherchez s'appelle Consumer<T> ( docs ) avec la méthode

void accept(T t);

il 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 les 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 les types de paramètres d'entrée et R étant le type de retour. Il y a aussi BiConsumer<T, U> (deux arguments, pas de type retour). Malheureusement pour 3 et plus de 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
répondu JakubM 2017-10-05 14:26:37

utiliser le motif de L'Observateur (parfois appelé aussi motif de L'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
répondu EricSchaefer 2010-02-02 19:33:33

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

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}
1
répondu 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
répondu 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, je mets en œuvre getsearchtext méthode tout en appelant initialiser méthode.

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

Je ne pense pas que lambdas soit fait pour ça... Java n'est pas un langage de programmation fonctionnel et ne le sera jamais donc, nous ne passons pas les méthodes comme paramètres. Cela dit, rappelez-vous que Java est orienté objet et, avec cela à l'esprit, nous pouvons faire ce que nous voulons. La première idée est tout simplement de passer un "objet qui contient une méthode" comme paramètre. Donc chaque fois que vous avez besoin de "passer" la méthode juste passer une instance de cette classe. Notez que lorsque vous définissez la méthode vous devez ajouter comme un paramètre une instance de la classe qui contient la méthode. Cela devrait fonctionner mais ce n'est pas ce que nous voulons parce que 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 comme paramètre c'est parce que le comportement de la méthode devrait être dynamique. Ce que je veux dire c'est que le programmeur qui utilise vos classes devrait être capable de choisir ce que la méthode devrait retourner mais pas son type. Heureusement pour nous, Java a une solution belle et simple: des 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 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... quand vous créez une méthode abstraite vous devez aussi la 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 la déclariez statique, privée et autres choses) la version redéfinie de celle-ci. Ici est un exemple... Supposons que nous voulons appliquer une fonction à un tableau de nombres: donc, si nous voulons à la place de 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 facile grâce à des outils comme 'map',...). Notez qu'il y a rien de spécial dans 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 alors ce que nous voulons est une fonction qui applique une autre fonction à un tableau. En un mot, nous devons passer une méthode comme paramètre. Voici comment je le ferais= = >

1) DÉFINIR LA CLASSE ABSTRAITE D'EMBALLAGE ET LA MÉTHODE

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

2) DÉFINIR LA CLASSE AVEC LE APPLY_TO_ARRAY METHOD

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 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 avons besoin de passer un objet de fonction de type et puisque Testclass étend la classe de fonction que nous pouvons l'utiliser, la fonte est automatique.

0
répondu omar kahol 2018-07-06 15:52:42