Metodo Java Pass come parametro


278

Sto cercando un modo per passare un metodo per riferimento. Capisco che Java non passa metodi come parametri, tuttavia, vorrei ottenere un'alternativa.

Mi è stato detto che le interfacce sono l'alternativa ai metodi di passaggio come parametri, ma non capisco come un'interfaccia possa agire come metodo per riferimento. Se capisco correttamente un'interfaccia è semplicemente un insieme astratto di metodi che non sono definiti. Non voglio inviare un'interfaccia che deve essere definita ogni volta perché diversi metodi potrebbero chiamare lo stesso metodo con gli stessi parametri.

Quello che vorrei realizzare è qualcosa di simile a questo:

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
}

invocato come:

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

in questo momento la mia soluzione è quella di passare un parametro aggiuntivo e utilizzare un caso switch all'interno per selezionare il metodo appropriato. Tuttavia, questa soluzione non si presta durante il riutilizzo del codice.

Vedi anche questa risposta stackoverflow.com/a/22933032/1010868 per una domanda simile
Tomasz Gawel

Risposte:


233

Modifica : a partire da Java 8, le espressioni lambda sono una buona soluzione come hanno sottolineato altre risposte . La risposta di seguito è stata scritta per Java 7 e precedenti ...


Dai un'occhiata al modello di comando .

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

Modifica: come sottolinea Pete Kirkham , c'è un altro modo di farlo usando un Visitatore . L'approccio del visitatore è un po 'più coinvolto - tutti i tuoi nodi devono essere consapevoli del visitatore con un acceptVisitor()metodo - ma se devi attraversare un grafico a oggetti più complesso, vale la pena esaminare.


2
@Mac - bene! questo viene ripetutamente nelle lingue senza metodi di prima classe come modo di fatto di simularli, quindi vale la pena ricordare.
Dan Vinton,

7
È il modello visitatore (separa l'azione di iterare su una raccolta dalla funzione applicata a ciascun membro della raccolta), non il modello di comando (incapsula gli argomenti per una chiamata di metodo in un oggetto). In particolare non stai incapsulando l'argomento: è fornito dalla parte dell'iterazione del modello visitatore.
Pete Kirkham,

No, devi solo accettare il metodo se stai combinando la visita con la doppia spedizione. Se hai un visitatore monomorfo, è esattamente il codice che hai sopra.
Pete Kirkham,

In Java 8 potrebbe essere come ex.operS (String :: toLowerCase, "STRING"). Vedi il bell'articolo: studytrails.com/java/java8/…
Zon,

Pete Kirkham è corretto: il tuo codice sta implementando il modello Visitatore, non il modello Comando (e questo è buono, poiché è ciò di cui ha bisogno OP.) Come dice Pete, non stai incapsulando l'argomento, quindi non stai facendo Comando - il tuo Comando l'interfaccia ha un'esecuzione che accetta un parametro. Wikipedia no. Questo è fondamentale per l'intento del modello di comando. Come dice il primo paragrafo "incapsulare tutte le informazioni ... Queste informazioni includono il nome del metodo, l'oggetto che possiede il metodo e i valori per i parametri del metodo ".
ToolmakerSteve

73

In Java 8, ora puoi passare un metodo più facilmente usando le espressioni Lambda e i riferimenti ai metodi. Innanzitutto, un po 'di background: un'interfaccia funzionale è un'interfaccia che ha uno e un solo metodo astratto, sebbene possa contenere qualsiasi numero di metodi predefiniti (nuovi in ​​Java 8) e metodi statici. Un'espressione lambda può implementare rapidamente il metodo astratto, senza tutta la sintassi non necessaria necessaria se non si utilizza un'espressione lambda.

Senza espressioni lambda:

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

Con espressioni lambda:

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

Ecco un estratto dal tutorial Java su Lambda Expressions :

Sintassi delle espressioni Lambda

Un'espressione lambda è composta da:

  • Un elenco separato da virgole di parametri formali racchiuso tra parentesi. Il metodo CheckPerson.test contiene un parametro, p, che rappresenta un'istanza della classe Person.

    Nota : è possibile omettere il tipo di dati dei parametri in un'espressione lambda. Inoltre, è possibile omettere le parentesi se esiste un solo parametro. Ad esempio, è valida anche la seguente espressione lambda:

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
  • Il token freccia, ->

  • Un corpo, che consiste in una singola espressione o in un blocco di istruzioni. Questo esempio usa la seguente espressione:

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

    Se si specifica una singola espressione, il runtime Java valuta l'espressione e quindi ne restituisce il valore. In alternativa, puoi utilizzare un'istruzione return:

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

    Un'istruzione return non è un'espressione; in un'espressione lambda, è necessario racchiudere le istruzioni tra parentesi graffe ({}). Tuttavia, non è necessario racchiudere tra parentesi graffe un metodo nullo. Ad esempio, la seguente è un'espressione lambda valida:

    email -> System.out.println(email)

Nota che un'espressione lambda assomiglia molto a una dichiarazione di metodo; puoi considerare le espressioni lambda come metodi anonimi, metodi senza nome.


Ecco come è possibile "passare un metodo" utilizzando un'espressione 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 Cpuò essere abbreviata ulteriormente con l'uso di riferimenti a metodi in questo modo:

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 deve essere ereditata dall'interfaccia?
Serob_b,

1
@Serob_b No. A meno che tu non voglia passarlo come riferimento di metodo (vedi ::operatore), non importa quale sia A. a.changeThing(component)può essere modificato in qualsiasi istruzione o blocco di codice desiderato, purché restituisca null.
The Guy with The Hat,

29

Usa l' java.lang.reflect.Methodoggetto e chiamainvoke


12
Non vedo perché no. La domanda è passare un metodo come parametro e questo è un modo molto valido per farlo. Questo può anche essere avvolto in un numero qualsiasi di modelli piuttosto belli per renderlo bello. E questo è generico come si ottiene senza la necessità di interfacce speciali.
Vinodh Ramasubramanian,

3
Hai digitato sicurezza in JavaScript fg? La sicurezza del tipo non è un argomento.
Danubian Sailor,

13
In che modo la sicurezza dei tipi non è un argomento quando il linguaggio in questione considera la sicurezza dei tipi uno dei suoi componenti più forti? Java è un linguaggio fortemente tipizzato e quel tipo di digitazione forte è uno dei motivi per cui lo sceglieresti su un altro linguaggio compilato.
Adam Parkin,

21
"La funzione di riflessione principale è stata originariamente progettata per strumenti di creazione di applicazioni basati su componenti. [...] Di norma, non è possibile accedere in modo riflettente agli oggetti nelle normali applicazioni in fase di esecuzione." Voce 53: Preferisce le interfacce alla riflessione, da Effective Java Second Edition. - Questa è la linea di pensiero dei creatori di Java ;-)
Wilhem Meignan,

8
Non è un uso giustificato di riflett. Sono inorridito nel vedere tutti i voti. riflettere non è mai stato concepito per essere utilizzato come meccanismo generale di programmazione; usalo solo quando non c'è altra soluzione pulita.
ToolmakerSteve

22

Da Java 8 esiste Function<T, R>un'interfaccia ( docs ), che ha metodo

R apply(T t);

Puoi usarlo per passare le funzioni come parametri ad altre funzioni. T è il tipo di input della funzione, R è il tipo di ritorno.

Nel tuo esempio devi passare una funzione che accetta il Componenttipo come input e non restituisce nulla - Void. In questo caso Function<T, R>non è la scelta migliore, poiché non esiste un box automatico di tipo Void. L'interfaccia che stai cercando si chiama Consumer<T>( docs ) con metodo

void accept(T t);

Sarebbe così:

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

E lo chiamereste usando i metodi di riferimento:

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

Supponendo di aver definito i metodi changeColor () e changeSize () nella stessa classe.


Se il tuo metodo accetta più di un parametro, puoi usare BiFunction<T, U, R>- T e U come tipi di parametri di input e R come tipo di ritorno. C'è anche BiConsumer<T, U>(due argomenti, nessun tipo restituito). Sfortunatamente per 3 e più parametri di input, devi creare un'interfaccia da solo. Per esempio:

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

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

19

Per prima cosa definisci un'interfaccia con il metodo che vuoi passare come parametro

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

Implementare una classe con il metodo

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

// Invoca così

Callable cmd = new Test();

Ciò consente di passare cmd come parametro e invocare la chiamata del metodo definita nell'interfaccia

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

1
Potrebbe non essere necessario creare la tua interfaccia poiché java ne ha definite molte per te: docs.oracle.com/javase/8/docs/api/java/util/function/…
slim

@slim Punto interessante, quanto sono stabili quelle definizioni, sono intese per essere utilizzate abitualmente come suggerisci, o è probabile che si rompano?
Manuel,

@slim In realtà, i documenti rispondono che: "Le interfacce di questo pacchetto sono interfacce funzionali di uso generale utilizzate da JDK e sono disponibili per essere utilizzate anche dal codice utente."
Manuel,

14

Anche se questo non è ancora valido per Java 7 e versioni precedenti , credo che dovremmo guardare al futuro e almeno riconoscere i cambiamenti in arrivo in nuove versioni come Java 8.

Vale a dire, questa nuova versione porta lambda e riferimenti a metodi su Java (insieme alle nuove API , che sono un'altra valida soluzione a questo problema. Mentre richiedono ancora un'interfaccia, non vengono creati nuovi oggetti e i file di classe extra non devono inquinare le directory di output a causa di differenti gestione da parte della JVM.

Entrambi i sapori (lambda e riferimento al metodo) richiedono un'interfaccia disponibile con un singolo metodo la cui firma viene utilizzata:

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

I nomi dei metodi non importeranno da qui in poi. Quando viene accettata una lambda, viene indicato anche un riferimento al metodo. Ad esempio, per usare la nostra firma qui:

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

Questa è solo una semplice chiamata all'interfaccia, fino a quando lambda 1 non viene superato:

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

Questo produrrà:

Lambda reached!
lambda return

I riferimenti ai metodi sono simili. Dato:

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

e principale:

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

l'output sarebbe real static method. I riferimenti ai metodi possono essere statici, istanza, non statici con istanze arbitrarie e persino costruttori . Per il costruttore ClassName::newsarebbe usato qualcosa di simile .

1 Questo non è considerato un lambda da alcuni, in quanto ha effetti collaterali. Illustra, tuttavia, l'uso di uno in un modo più semplice da visualizzare.


12

L'ultima volta che ho controllato, Java non è in grado di fare nativamente quello che vuoi; è necessario utilizzare "soluzioni alternative" per aggirare tali limitazioni. A mio avviso, le interfacce SONO un'alternativa, ma non una buona alternativa. Forse chiunque ti abbia detto che significava qualcosa del genere:

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
}

Che poi invocheresti con:

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

6

Se non hai bisogno di questi metodi per restituire qualcosa, potresti farli restituire oggetti eseguibili.

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

Quindi usalo come:

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

2

Non ho trovato alcun esempio abbastanza esplicito per me su come utilizzare java.util.function.Functionper il metodo semplice come funzione parametro. Qui c'è un semplice esempio:

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

Fondamentalmente hai un Foooggetto con un costruttore predefinito. A methodche verrà chiamato come parametro dal parametrisedMethodquale è di tipo Function<String, Foo>.

  • Function<String, Foo>significa che la funzione accetta un Stringparametro as e restituisce a Foo.
  • Il Foo::Methodcorrispondono ad un lambda comex -> Foo.method(x);
  • parametrisedMethod(Foo::method) potrebbe essere visto come x -> parametrisedMethod(Foo.method(x))
  • L' .apply("from a method")è fondamentalmente a che fareparametrisedMethod(Foo.method("from a method"))

Che poi tornerà nell'output:

>> I'm a Foo from a method

L'esempio dovrebbe essere eseguito così com'è, quindi puoi provare cose più complicate dalle risposte sopra con classi e interfacce diverse.


per utilizzare la chiamata di applicazione in Android è necessario un minimo di api 24
Ines Belhouchet

1

Java ha un meccanismo per passare il nome e chiamarlo. Fa parte del meccanismo di riflessione. La tua funzione dovrebbe prendere un parametro aggiuntivo della classe Metodo.

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

1

Non sono un esperto di Java ma risolvo il tuo problema in questo modo:

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

Definisco il parametro nella mia interfaccia speciale

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

Infine, implemento il metodo getSearchText durante la chiamata di inizializzazione metodo di .

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

In realtà è la risposta migliore e il modo corretto di farlo.
Merita di

0

Usa il modello Observer (a volte chiamato anche modello 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()... dichiara un tipo anonimo implementando l'interfaccia.


8
Questo non è lo schema che stai cercando.
Pete Kirkham,

1
Il modello di osservatore riguarda l'astrazione della capacità di rispondere a un cambiamento. L'OP vuole astrarre l'azione intrapresa su ciascun elemento in una raccolta dal codice che scorre sulla raccolta, che è il modello visitatore.
Pete Kirkham,

Il modello Observer / Listener è in realtà lo stesso del modello Command. Differiscono solo nelle intenzioni. L'osservatore riguarda la notifica mentre il comando sostituisce le funzioni / lambdas di prima classe. Il visitatore d'altra parte è qualcosa di completamente diverso. Non credo che possa essere spiegato in un paio di frasi, quindi dai un'occhiata a en.wikipedia.org/wiki/Visitor_pattern
EricSchaefer

0

Ecco un esempio di 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);
    }
}

Produzione:

Do println
Do print

0

Non ho trovato alcuna soluzione qui che mostra come passare il metodo con parametri associati ad esso come parametro di un metodo. Il seguente è un esempio di come è possibile passare un metodo con valori di parametro già associati ad esso.

  1. Passaggio 1: creare due interfacce una con il tipo restituito, l'altra senza. Java ha interfacce simili ma sono di scarsa utilità pratica perché non supportano il lancio di eccezioni.


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


    public interface Return {
        R run() throws Exception;
    }
  1. Esempio di come utilizziamo entrambe le interfacce per racchiudere la chiamata del metodo nella transazione. Si noti che passiamo il metodo con parametri effettivi.


    //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 altro esempio mostra come passare un metodo che restituisce effettivamente qualcosa



        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

Esempio di soluzione con riflessione, il metodo passato deve essere pubblico

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

Apprezzo le risposte sopra ma sono stato in grado di ottenere lo stesso comportamento usando il metodo seguente; un'idea presa in prestito dai callback Javascript. Sono aperto alla correzione anche se finora (buono in produzione).

L'idea è di utilizzare il tipo restituito della funzione nella firma, il che significa che il rendimento deve essere statico.

Di seguito è una funzione che esegue un processo con un timeout.

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());
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.