Método Java Pass como parámetro

278

Estoy buscando una manera de pasar un método por referencia. Entiendo que Java no pasa los métodos como parámetros, sin embargo, me gustaría obtener una alternativa.

Me han dicho que las interfaces son la alternativa a pasar métodos como parámetros, pero no entiendo cómo una interfaz puede actuar como método por referencia. Si entiendo correctamente, una interfaz es simplemente un conjunto abstracto de métodos que no están definidos. No quiero enviar una interfaz que deba definirse cada vez porque varios métodos diferentes podrían llamar al mismo método con los mismos parámetros.

Lo que me gustaría lograr es algo similar a esto:

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
}

invocado como:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());
Yojimbo
fuente
En este momento, mi solución es pasar un parámetro adicional y usar una caja de interruptor dentro para seleccionar el método apropiado. Sin embargo, esta solución no se presta para reutilizar código.
Consulte también esta respuesta stackoverflow.com/a/22933032/1010868 para una pregunta similar
Tomasz Gawel,

Respuestas:

233

Editar : a partir de Java 8, las expresiones lambda son una buena solución, como han señalado otras respuestas . La respuesta a continuación fue escrita para Java 7 y versiones anteriores ...


Echa un vistazo al patrón de 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");
    }
}

Editar: como señala Pete Kirkham , hay otra forma de hacerlo usando un visitante . El enfoque del visitante es un poco más complicado: todos sus nodos deben ser conscientes del visitante con un acceptVisitor()método, pero si necesita atravesar un gráfico de objeto más complejo, vale la pena examinarlo.

Dan Vinton
fuente
2
@ Mac - ¡bien! este aparece una y otra vez en idiomas sin métodos de primera clase como la forma de facto de simularlos, por lo que vale la pena recordarlo.
Dan Vinton
77
Es el patrón de visitante (separa la acción de iterar sobre una colección de la función aplicada a cada miembro de la colección), no el patrón de comando (encapsula los argumentos para una llamada de método en un objeto). Específicamente, no está encapsulando el argumento: lo proporciona la parte de iteración del patrón de visitante.
Pete Kirkham
No, solo necesita el método de aceptación si combina la visita con el envío doble. Si tiene un visitante monomórfico, es exactamente el código que tiene arriba.
Pete Kirkham
En Java 8 podría ser como ex.operS (String :: toLowerCase, "STRING"). Vea el bonito artículo: studytrails.com/java/java8/…
Zon
Pete Kirkham es correcto: su código está implementando un patrón de visitante, no un patrón de comando (y esto es bueno, ya que es lo que OP necesita). Como dice Pete, no está encapsulando el argumento, por lo que no está haciendo comando: su comando La interfaz tiene una ejecución que toma un parámetro. Wikipedia no lo hace. Esto es fundamental para la intención del patrón de Comando. Como dice el primer párrafo "encapsule toda la información ... Esta información incluye el nombre del método, el objeto que posee el método y los valores de los parámetros del método ".
ToolmakerSteve
73

En Java 8, ahora puede pasar un método más fácilmente usando Expresiones de Lambda y Referencias de métodos. Primero, algunos antecedentes: una interfaz funcional es una interfaz que tiene un único método abstracto, aunque puede contener cualquier cantidad de métodos predeterminados (nuevos en Java 8) y métodos estáticos. Una expresión lambda puede implementar rápidamente el método abstracto, sin toda la sintaxis innecesaria necesaria si no utiliza una expresión lambda.

Sin expresiones lambda:

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

Con expresiones lambda:

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

Aquí hay un extracto del tutorial de Java sobre las expresiones Lambda :

Sintaxis de las expresiones lambda

Una expresión lambda consiste en lo siguiente:

  • Una lista separada por comas de parámetros formales entre paréntesis. El método CheckPerson.test contiene un parámetro, p, que representa una instancia de la clase Person.

    Nota : Puede omitir el tipo de datos de los parámetros en una expresión lambda. Además, puede omitir los paréntesis si solo hay un parámetro. Por ejemplo, la siguiente expresión lambda también es válida:

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
  • La ficha de flecha, ->

  • Un cuerpo, que consiste en una sola expresión o un bloque de enunciado. Este ejemplo usa la siguiente expresión:

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

    Si especifica una sola expresión, el tiempo de ejecución de Java evalúa la expresión y luego devuelve su valor. Alternativamente, puede usar una declaración de devolución:

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

    Una declaración de retorno no es una expresión; en una expresión lambda, debe encerrar las declaraciones entre llaves ({}). Sin embargo, no tiene que incluir una invocación de método nulo entre llaves. Por ejemplo, la siguiente es una expresión lambda válida:

    email -> System.out.println(email)

Tenga en cuenta que una expresión lambda se parece mucho a una declaración de método; puede considerar las expresiones lambda como métodos anónimos, métodos sin nombre.


Así es como puede "pasar un método" usando una expresión 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 clase Cse puede acortar incluso un poco más mediante el uso de referencias de métodos como esta:

class C {
    A a = new A();
    B b = new B();

    public C() {
        b.setAllComponents(this.getComponents(), a::changeColor);
        b.setAllComponents(this.getComponents(), a::changeSize);
    }
}
El chico con el sombrero
fuente
¿La clase A necesita ser heredada de la interfaz?
Serob_b
1
@Serob_b No. A menos que desee pasarlo como referencia de método (ver ::operador), no importa qué sea A. a.changeThing(component)puede cambiarse a cualquier declaración o bloque de código que desee, siempre que devuelva nulo.
The Guy with The Hat
29

Usa el java.lang.reflect.Methodobjeto y llamainvoke

Vinodh Ramasubramanian
fuente
12
No veo por qué no. La pregunta es pasar un método como parámetro y esta es una forma muy válida de hacerlo. Esto también se puede envolver en cualquier cantidad de patrones bonitos para que se vea bien. Y esto es lo más genérico posible sin la necesidad de interfaces especiales.
Vinodh Ramasubramanian
3
¿Ha escrito seguridad en JavaScript fg? La seguridad de tipo no es un argumento.
Danubian Sailor
13
¿Cómo es que la seguridad de tipos no es un argumento cuando el lenguaje en cuestión sostiene la seguridad de tipos como uno de sus componentes más fuertes? Java es un lenguaje fuertemente tipado, y ese tipeo fuerte es una de las razones por las que lo elegirías sobre otro lenguaje compilado.
Adam Parkin
21
"La función de reflexión central se diseñó originalmente para herramientas de creación de aplicaciones basadas en componentes. [...] Como regla, no se debe acceder a los objetos de forma reflexiva en aplicaciones normales en tiempo de ejecución". Punto 53: Prefiere interfaces a la reflexión, desde Effective Java Second Edition. - Esa es la línea de pensamiento de los creadores de Java ;-)
Wilhem Meignan
8
No es un uso justificable de reflexionar. Me horroriza ver todos los votos a favor. reflex nunca tuvo la intención de ser utilizado como un mecanismo de programación general; solo úselo cuando no haya otra solución limpia.
ToolmakerSteve
22

Desde Java 8 hay una Function<T, R>interfaz ( docs ), que tiene un método

R apply(T t);

Puede usarlo para pasar funciones como parámetros a otras funciones. T es el tipo de entrada de la función, R es el tipo de retorno.

En su ejemplo, debe pasar una función que tome el Componenttipo como entrada y no devuelva nada Void. En este caso Function<T, R>no es la mejor opción, ya que no hay autoboxing de tipo Void. La interfaz que está buscando se llama Consumer<T>( docs ) con método

void accept(T t);

Se vería así:

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

Y lo llamarías usando referencias de método:

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

Suponiendo que haya definido los métodos changeColor () y changeSize () en la misma clase.


Si su método acepta más de un parámetro, puede usar BiFunction<T, U, R>: T y U son tipos de parámetros de entrada y R son tipos de retorno. También hay BiConsumer<T, U>(dos argumentos, sin tipo de retorno). Desafortunadamente para 3 y más parámetros de entrada, debe crear una interfaz usted mismo. Por ejemplo:

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

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

Primero defina una interfaz con el método que desea pasar como parámetro

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

Implementar una clase con el método.

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

// Invocar así

Callable cmd = new Test();

Esto le permite pasar cmd como parámetro e invocar la llamada al método definida en la interfaz

public invoke( Callable callable ) {
  callable.call( 5 );
}
apilador
fuente
1
Es posible que no tenga que crear su propia interfaz, ya que Java ha definido muchas de ellas para usted: docs.oracle.com/javase/8/docs/api/java/util/function/…
slim
@slim Punto interesante, ¿qué tan estables son esas definiciones, están destinadas a ser utilizadas habitualmente como usted sugiere, o es probable que se rompan?
Manuel
@slim En realidad, los documentos responden que: "Las interfaces en este paquete son interfaces funcionales de uso general utilizadas por el JDK, y también están disponibles para ser utilizadas por el código de usuario".
Manuel
14

Si bien esto aún no es válido para Java 7 y versiones posteriores, creo que deberíamos mirar hacia el futuro y al menos reconocer los cambios que vendrán en nuevas versiones como Java 8.

Es decir, esta nueva versión trae referencias lambdas y de métodos a Java (junto con nuevas API , que son otra solución válida para este problema. Si bien aún requieren una interfaz, no se crean nuevos objetos, y los archivos de clase adicionales no necesitan contaminar directorios de salida debido a diferentes manejo por parte de la JVM.

Ambos sabores (referencia lambda y método) requieren una interfaz disponible con un único método cuya firma se utilice:

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

Los nombres de los métodos no serán importantes de aquí en adelante. Cuando se acepta una lambda, también se hace referencia a un método. Por ejemplo, para usar nuestra firma aquí:

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

Esta es solo una invocación de interfaz simple, hasta que se pasa la lambda 1 :

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

Esto generará:

Lambda reached!
lambda return

Las referencias de métodos son similares. Dado:

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

y principal:

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

la salida sería real static method. Las referencias de métodos pueden ser estáticas, de instancia, no estáticas con instancias arbitrarias e incluso constructores . Para el constructor se ClassName::newusaría algo parecido .

1 Algunos no lo consideran una lambda, ya que tiene efectos secundarios. Sin embargo, ilustra el uso de uno de una manera más sencilla de visualizar.

nanofaradio
fuente
12

La última vez que lo revisé, Java no es capaz de hacer de forma nativa lo que quieres; tienes que usar 'soluciones alternativas' para sortear tales limitaciones. Por lo que yo veo, las interfaces SON una alternativa, pero no una buena alternativa. Quizás quien te dijo que significaba algo como esto:

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 luego invocarías con:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());
usuario246091
fuente
6

Si no necesita estos métodos para devolver algo, puede hacer que devuelvan objetos Runnable.

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

Luego úsalo como:

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

No encontré ningún ejemplo lo suficientemente explícito para mí sobre cómo usar java.util.function.Functionun método simple como función de parámetro. Aquí hay un ejemplo 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");
  }
}

Básicamente tiene un Fooobjeto con un constructor predeterminado. A methodque se llamará como un parámetro del parametrisedMethodcual es de tipo Function<String, Foo>.

  • Function<String, Foo>significa que la función toma un Stringparámetro y devuelve a Foo.
  • El Foo::Methodcorresponde a una lambda comox -> Foo.method(x);
  • parametrisedMethod(Foo::method) podría ser visto como x -> parametrisedMethod(Foo.method(x))
  • El .apply("from a method")es básicamente hacerparametrisedMethod(Foo.method("from a method"))

Que luego regresará en la salida:

>> I'm a Foo from a method

El ejemplo debería ejecutarse tal cual, luego puede probar cosas más complicadas de las respuestas anteriores con diferentes clases e interfaces.

Sylhare
fuente
para usar la llamada de solicitud en Android, necesita una API mínima 24
Ines Belhouchet
1

Java tiene un mecanismo para pasar el nombre y llamarlo. Es parte del mecanismo de reflexión. Su función debe tomar parámetros adicionales de la clase Método.

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

No soy un experto en Java, pero resuelvo tu problema así:

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

Defino el parámetro en mi interfaz especial

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

Finalmente, implemento el método getSearchText mientras llamo al método initialize .

initialize(getMessageContactModelList(), new AutoCompleteCallable() {
          @Override
          public String call(Object model) throws Exception {
            return "custom string" + ((xxxModel)model.getTitle());
          }
        })
Sercan özen
fuente
En realidad, es la mejor respuesta y la forma correcta de hacerlo. Merece más +1
amdev
0

Utilice el patrón de observador (a veces también llamado patrón de escucha):

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()... declara un tipo anónimo que implementa la interfaz.

EricSchaefer
fuente
8
Este no es el patrón que estás buscando.
Pete Kirkham
1
El patrón del observador consiste en abstraer la capacidad de responder a un cambio. El OP quiere abstraer la acción realizada en cada elemento de una colección lejos del código que itera sobre la colección, que es el patrón de visitante.
Pete Kirkham
El patrón de observador / oyente es en realidad el mismo que el patrón de comando. Solo difieren en intención. El observador trata sobre la notificación mientras que el comando es un sustituto de las funciones / lambdas de primera clase. El visitante, por otro lado, es algo completamente diferente. No creo que se pueda explicar en un par de oraciones, así que eche un vistazo a en.wikipedia.org/wiki/Visitor_pattern
EricSchaefer
0

Aquí hay un ejemplo básico:

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

Salida:

Do println
Do print
BullyWiiPlaza
fuente
0

No encontré ninguna solución aquí que muestre cómo pasar el método con parámetros vinculados a él como parámetro de un método. A continuación se muestra un ejemplo de cómo puede pasar un método con valores de parámetros ya vinculados a él.

  1. Paso 1: Cree dos interfaces, una con tipo de retorno y otra sin. Java tiene interfaces similares pero son de poco uso práctico porque no admiten el lanzamiento de excepciones.


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


    public interface Return {
        R run() throws Exception;
    }
  1. Ejemplo de cómo usamos ambas interfaces para ajustar la llamada al método en la transacción. Tenga en cuenta que pasamos el método con parámetros reales.


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

Otro ejemplo muestra cómo pasar un método que realmente devuelve algo



        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));
Stan Sokolov
fuente
0

Ejemplo de solución con reflexión, el método aprobado debe ser público

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);
    }
}
zemiak
fuente
0

Agradezco las respuestas anteriores, pero pude lograr el mismo comportamiento utilizando el método a continuación; Una idea tomada de las devoluciones de llamada de Javascript. Estoy abierto a la corrección, aunque hasta ahora todo bien (en producción).

La idea es utilizar el tipo de retorno de la función en la firma, lo que significa que el rendimiento debe ser estático.

A continuación se muestra una función que ejecuta un proceso con un tiempo de espera.

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());
}
Mwa Joe
fuente