Método Java Pass como Parámetro


Estoy buscando una manera de pasar un método por referencia. Entiendo que Java no pasa 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 un 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 necesita ser definida cada tiempo 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 tales como:

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

13 answers

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


Echa un vistazo al patrón de comandos .

// 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: como Pete Kirkham señala, hay otra forma de hacer esto usando un Visitante. El enfoque del visitante está un poco más involucrado: todos sus nodos deben ser conscientes del visitante con un acceptVisitor() método - pero si necesita recorrer un gráfico de objetos más complejo, vale la pena examinarlo.

 196
Author: Dan Vinton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:26

En Java 8, ahora puede pasar un método más fácilmente usando Expresiones Lambda y Referencias de Métodos. Primero, algunos antecedentes: una interfaz funcional es una interfaz que tiene un solo método abstracto, aunque puede contener cualquier número de métodos predeterminados (nuevo 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 lambda expresiones:

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 de el tutorial Java sobre Expresiones Lambda :

Sintaxis de las expresiones Lambda

Una expresión lambda consiste en lo siguiente:

  • Una lista separada por comas de parámetros formales encerrada entre paréntesis. La persona de control.el método de ensayo contiene un parámetro, p, que representa una instancia de la Persona clase.

    Nota: Usted puede omitir el tipo de datos de los parámetros en una expresión lambda. En 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
    
  • El símbolo de flecha, ->

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

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

    Si especifica una sola la expresión, luego 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 sentencia return no es una expresión; en una expresión lambda, debe encerrar las sentencias entre llaves ({}). Sin embargo, usted no tiene para encerrar una invocación del método void entre llaves. Por ejemplo, el a continuación se muestra una expresión lambda válida:

    email -> System.out.println(email)
    

Tenga en cuenta que una expresión lambda se parece mucho a un método declaración; 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 C se puede acortar incluso un poco más por el uso de referencias de métodos como así:

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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-04 17:58:16

Use el objeto java.lang.reflect.Method y llame a invoke

 24
Author: Vinodh Ramasubramanian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-02 19:25:54

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

/ / Invoca 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 );
}
 17
Author: stacker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-23 09:59:41

La última vez que lo comprobé, Java no es capaz de hacer de forma nativa lo que quieres; tienes que usar 'soluciones alternativas' para sortear tales limitaciones. En mi opinión, las interfaces SON una alternativa, pero no una buena alternativa. Tal vez quienquiera que te dijo eso estaba significando 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());
 11
Author: user246091,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-02 19:38:19

Aunque esto todavía 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 lambdasy referencias de métodos a Java (junto con nuevas API, que son otra solución válida para este problema. Mientras que todavía requieren una interfaz no se crean nuevos objetos, y extra classfiles necesidad de no contaminar los directorios de salida debido a diferente manejo por parte de la JVM.

Ambos sabores (lambda y method reference) requieren una interfaz disponible con un solo método cuya firma se utiliza:

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

Los nombres de los métodos no importarán de aquí en adelante. Cuando se acepta una lambda, también se acepta una referencia de 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));
}

Esto es solo una simple invocación de interfaz, hasta que la lambda1 se aprueba:

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

Esto hará salida:

Lambda reached!
lambda return

Las referencias de los 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 usaría algo parecido a ClassName::new.

1 Esto no se considera una lambda por algunos, ya que tiene efectos secundarios. Sin embargo, ilustra el uso de uno en una forma más sencilla de visualizar moda.

 11
Author: Andrey Akhmetov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-26 17:34:53

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

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

Entonces úsalo como:

private void otherMethodName (Runnable arg){
    arg.run();
}
 6
Author: Smig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-12 13:40:33

Desde Java 8 hay una interfaz Function<T, R> (docs ), que tiene el 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 necesita pasar una función que tome Component type 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 el método

void accept(T t);

Se vería así: {[15]]}

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étodos:

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 es tipo de retorno. También hay BiConsumer<T, U> (dos argumentos, sin tipo de retorno). Desafortunadamente para 3 y más parámetros de entrada, tienes que crear una interfaz por ti mismo. Por ejemplo:

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

    R apply(A a, B b, C c, D d);
}
 3
Author: JakubM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-05 14:26:37

Use el patrón del Observador (a veces también llamado patrón del oyente):

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 implementando la interfaz.

 1
Author: EricSchaefer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-02 19:33:33

Java tiene un mecanismo para pasar el nombre y llamarlo. Es parte del mecanismo de reflexión. Su función debe tomar parámetro adicional del método de clase.

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}
 1
Author: David Gruzman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-02 19:47:16

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
 0
Author: BullyWiiPlaza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-30 09:49:40

No soy un experto en Java, pero resuelvo su problema de esta manera:

@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());
          }
        })
 0
Author: Sercan özen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-11 11:15:36

No creo que las lambdas estén hechas para esto... Java no es un lenguaje de programación funcional y nunca lo será, por lo tanto, no pasamos métodos como parámetros. Dicho esto, recuerde que Java está orientado a objetos y con eso en mente podemos hacer lo que queramos. La primera idea es simplemente pasar un "objeto que contiene un método" como parámetro. Así que cada vez que necesite 'pasar' el método simplemente pase una instancia de esa clase. Tenga en cuenta que cuando defina el método debe agregar como parámetro un instancia de la clase que contiene el método. Esto debería funcionar, pero no es lo que queremos porque no se puede redefinir el método a menos que se tenga acceso al código de la clase y eso no es posible en muchos casos; además, creo que si alguien necesita pasar un método como parámetro es porque el comportamiento del método debe ser dinámico. Lo que quiero decir es que el programador que usa tus clases debería ser capaz de elegir lo que el método debe devolver, pero no su tipo. Por suerte para nosotros Java tiene una solución hermosa y simple: clases abstractas. Las clases abstractas, en pocas palabras, se utilizan cuando se conoce la "firma" de un método " pero no su comportamiento... Puede envolver el nombre y el tipo del método en una clase abstracta y pasar una instancia de esa clase como parámetro para el método... Esperen... no es lo mismo que antes? ¿Y puedes tener una instancia de una clase abstracta? No y No... pero también sí... cuando creas un método abstracto también TIENES QUE redefinirlo en una clase que extiende la clase abstracta y debido al enlace dinámico de java, Java siempre (a menos que lo declare estático, privado y algunas otras cosas) usará la versión redefinida de la misma. He aquí un ejemplo... Supongamos que queremos aplicar una función a una matriz de números: así que si queremos cuadrar la entrada-salida debería verse así [1,2,3,4,...]->[1,4,9,16,...] (en un lenguaje de programación funcional como haskell esto es fácil gracias a algunas herramientas como'map',...). Tenga en cuenta que no hay nada especial sobre la cuadratura de números, podríamos aplicar cualquier función que queramos. Así que el código debería ser algo como esto [args], f -> [f(args)]. Volver a java = > una función es sólo un método por lo que lo que queremos es una función que aplica otra función a una matriz. En pocas palabras, necesitamos pasar un método como parámetro. Así es como lo haría= = >

1) DEFINIR LA CLASE ABSTRACTA WRAPPER Y EL MÉTODO

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

2) DEFINIR CLASE CON EL MÉTODO 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) CREAR UN TESTER-CLASE Y TENER UN POCO DE DIVERSIÓN

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

Tenga en cuenta que necesitamos pasar un objeto de tipo Function y como Testclass extiende la clase de Función podemos usarlo, el cast es automático.

 0
Author: omar kahol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-06 15:52:42