ChatGPT解决这个技术问题 Extra ChatGPT

Java Pass Method as Parameter

I am looking for a way to pass a method by reference. I understand that Java does not pass methods as parameters, however, I would like to get an alternative.

I've been told interfaces are the alternative to passing methods as parameters but I don't understand how an interface can act as a method by reference. If I understand correctly an interface is simply an abstract set of methods that are not defined. I don't want to send an interface that needs to be defined every time because several different methods could call the same method with the same parameters.

What I would like to accomplish is something similar to this:

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
}

invoked such as:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());
right now my solution is to pass an additional parameter and use a switch case inside to select the appropriate method. However, this solution does not lend while to code reuse.
See also this answer stackoverflow.com/a/22933032/1010868 for similar question

C
Community

Edit: as of Java 8, lambda expressions are a nice solution as other answers have pointed out. The answer below was written for Java 7 and earlier...

Take a look at the 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 out, there's another way of doing this using a Visitor. The visitor approach is a little more involved - your nodes all need to be visitor-aware with an acceptVisitor() method - but if you need to traverse a more complex object graph then it's worth examining.


@Mac - good! this one comes up again and again in languages without first-class methods as the de-facto way of simulating them, so it's worth remembering.
It's the visitor pattern (separate the action of iterating over a collection from the function applied to each member of the collection), not the command pattern (encapsulate the arguments for a method call into an object). You are specifically not encapsulating the argument - it is provided by the iteration part of the visitor pattern.
No, you only need the accept method if you're combining visiting with double dispatch. If you have a monomorphic visitor, it's exactly the code you have above.
In Java 8 could be like ex.operS(String::toLowerCase, "STRING"). See the nice article: studytrails.com/java/java8/…
... Or it could be considered Strategy pattern. That might make more sense to you. Strategy encapsulates an algorithm, but accepts parameters. Though when "visiting" all the leaves of a container, as OP does, Visitor pattern is what comes to mind - that's the traditional use of Visitor pattern. Anyway, you have a good implementation, and it could be considered either Strategy or Visitor.
T
The Guy with The Hat

In Java 8, you can now pass a method more easily using Lambda Expressions and Method References. First, some background: a functional interface is an interface that has one and only one abstract method, although it can contain any number of default methods (new in Java 8) and static methods. A lambda expression can quickly implement the abstract method, without all the unnecessary syntax needed if you don't use a lambda expression.

Without lambda expressions:

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

With lambda expressions:

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

Here is an excerpt from the Java tutorial on Lambda Expressions:

Syntax of Lambda Expressions A lambda expression consists of the following: A comma-separated list of formal parameters enclosed in parentheses. The CheckPerson.test method contains one parameter, p, which represents an instance of the Person class. Note: You can omit the data type of the parameters in a lambda expression. In addition, you can omit the parentheses if there is only one parameter. For example, the following lambda expression is also valid: p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 The arrow token, -> A body, which consists of a single expression or a statement block. This example uses the following expression: p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 If you specify a single expression, then the Java runtime evaluates the expression and then returns its value. Alternatively, you can use a return statement: p -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } A return statement is not an expression; in a lambda expression, you must enclose statements in braces ({}). However, you do not have to enclose a void method invocation in braces. For example, the following is a valid lambda expression: email -> System.out.println(email) Note that a lambda expression looks a lot like a method declaration; you can consider lambda expressions as anonymous methods—methods without a name.

Here is how you can "pass a method" using a lambda expression:

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

Class C can be shortened even a bit further by the use of method references like so:

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

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

Does class A need to be inherited form the interface?
@Serob_b Nope. Unless you want to pass it as a method reference (see :: operator), it doesn't matter what A is. a.changeThing(component) can be changed to any statement or code block you want, as long as it returns void.
J
JakubM

Since Java 8 there is a Function<T, R> interface (docs), which has method

R apply(T t);

You can use it to pass functions as parameters to other functions. T is the input type of the function, R is the return type.

In your example you need to pass a function that takes Component type as an input and returns nothing - Void. In this case Function<T, R> is not the best choice, since there is no autoboxing of Void type. The interface you are looking for is called Consumer<T> (docs) with method

void accept(T t);

It would look like this:

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

And you would call it using method references:

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

Assuming that you have defined changeColor() and changeSize() methods in the same class.

If your method happens to accept more than one parameter, you can use BiFunction<T, U, R> - T and U being types of input parameters and R being return type. There is also BiConsumer<T, U> (two arguments, no return type). Unfortunately for 3 and more input parameters, you have to create an interface by yourself. For example:

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

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

V
Vinodh Ramasubramanian

Use the java.lang.reflect.Method object and call invoke


I don't see why not. The question is to pass a method as a parameter and this a very valid way of doing it. This can also be wrapped in to any number of pretty looking pattern to make it look good. And this is as generic as it gets without the need for any special interfaces.
Have you type safety in JavaScript f.g.? Type safety is not an argument.
How is type safety not an argument when the language in question holds type safety up as one of its strongest components? Java is a strongly typed language, and that strong typing is one of the reasons you'd pick it over another compiled language.
"The core reflection facility was originally designed for component-based application builder tools. [...] As a rule, objects should not be accessed reflectively in normal applications at runtime." Item 53: Prefer interfaces to reflection, from Effective Java Second Edition. -- That's the line of thinking of the creators of Java ;-)
Not a justifiable use of reflect. I am horrified to see all the upvotes. reflect was never intended to be used as a general programming mechanism; only use it when there is no other clean solution.
W
WarrenFaith

First define an Interface with the method you want to pass as a parameter

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

Implement a class with the method

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

// Invoke like that

Callable cmd = new Test();

This allows you to pass cmd as parameter and invoke the method call defined in the interface

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

You may not have to make your own interface as java has defined a lot of them for you: docs.oracle.com/javase/8/docs/api/java/util/function/…
@slim Interesting point, how stable are those definitions, are they intended to be used customarily as you suggest, or are the likely to break?
@slim Actually, the docs answer that: "The interfaces in this package are general purpose functional interfaces used by the JDK, and are available to be used by user code as well."
s
shmosel

While this is not yet valid for Java 7 and below, I believe that we should look to the future and at least recognize the changes to come in new versions such as Java 8.

Namely, this new version brings lambdas and method references to Java (along with new APIs, which are another valid solution to this problem. While they still require an interface no new objects are created, and extra classfiles need not pollute output directories due to different handling by the JVM.

Both flavors(lambda and method reference) require an interface available with a single method whose signature is used:

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

Names of methods will not matter from here on. Where a lambda is accepted, a method reference is as well. For example, to use our signature here:

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

This is just a simple interface invocation, up until the lambda1 gets passed:

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

This will output:

Lambda reached!
lambda return

Method references are similar. Given:

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

and main:

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

the output would be real static method. Method references can be static, instance, non-static with arbitrary instances, and even constructors. For the constructor something akin to ClassName::new would be used.

1 This is not considered a lambda by some, as it has side effects. It does illustrate, however, the use of one in a more straightforward-to-visualize fashion.


u
user246091

Last time I checked, Java is not capable of natively doing what you want; you have to use 'work-arounds' to get around such limitations. As far as I see it, interfaces ARE an alternative, but not a good alternative. Perhaps whoever told you that was meaning something like this:

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
}

Which you'd then invoke with:

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

g
gotwo

If you don't need these methods to return something, you could make them return Runnable objects.

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

Then use it like:

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

simply, the clear approach, so call : otherMethodName(methodName(5));
I guess this works if you do not need the return type. Otherwise you would need to use interface as answered by Arvid Kumar Avinash above.
S
Sylhare

I didn't find any example explicit enough for me on how to use java.util.function.Function for simple method as parameter function. Here is a simple example:

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

Basically you have a Foo object with a default constructor. A method that will be called as a parameter from the parametrisedMethod which is of type Function<String, Foo>.

Function means that the function takes a String as parameter and return a Foo.

The Foo::Method correspond to a lambda like x -> Foo.method(x);

parametrisedMethod(Foo::method) could be seen as x -> parametrisedMethod(Foo.method(x))

The .apply("from a method") is basically to do parametrisedMethod(Foo.method("from a method"))

Which will then return in the output:

>> I'm a Foo from a method

The example should be running as is, you can then try more complicated stuff from the above answers with different classes and interfaces.


to use the apply call in android you need a minimum api 24
@InesBelhouchet Or use Java 8+ API desugaring: developer.android.com/studio/write/java8-support
C
Community

Java-8 onwards

Java 8 onwards, you can provide the implementation of the abstract method of a functional interface (an interface that has only one abstract method) using a lambda expression and pass the same to a method as a parameter.

@FunctionalInterface
interface ArithmeticFunction {
    public int calcualate(int a, int b);
}

public class Main {
    public static void main(String args[]) {
        ArithmeticFunction addition = (a, b) -> a + b;
        ArithmeticFunction subtraction = (a, b) -> a - b;

        int a = 20, b = 5;

        System.out.println(perform(addition, a, b));
        // or
        System.out.println(perform((x, y) -> x + y, a, b));

        System.out.println(perform(subtraction, a, b));
        // or
        System.out.println(perform((x, y) -> x - y, a, b));
    }

    static int perform(ArithmeticFunction function, int a, int b) {
        return function.calcualate(a, b);
    }
}

Output:

25
25
15
15

ONLINE DEMO

Learn more about it from Method References.


D
David Gruzman

Java do have a mechanism to pass name and call it. It is part of the reflection mechanism. Your function should take additional parameter of class Method.

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

S
Stan Sokolov

I did not found any solution here that show how to pass method with parameters bound to it as a parameter of a method. Bellow is example of how you can pass a method with parameter values already bound to it.

Step 1: Create two interfaces one with return type, another without. Java has similar interfaces but they are of little practical use because they do not support Exception throwing.



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


    public interface Return {
        R run() throws Exception;
    }

Example of how we use both interfaces to wrap method call in transaction. Note that we pass method with actual parameters.



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

Another example shows how to pass a method that actually returns something



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


why would you use a C# connectionScope class in a solution to a Java question?
It is not C#. I created my own DB persistence layer that is using this class. Name is pure coincidence. I did not know C# uses a class with the same name.
E
EricSchaefer

Use the Observer pattern (sometimes also called Listener pattern):

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()... declares an anonymous type implementing the interface.


This is not the pattern you are looking for.
The observer pattern is about abstracting the ability to respond to a change. The OP wants to abstract the action taken at each item in a collection away from the code iterating over the collection, which is the visitor pattern.
The Observer/Listener pattern is actually the same as that Command pattern. They only differ in intention. The observer is about notification while the command is a substitute for first class functions/lambdas. The visitor on the other hand is something completly different. I don't think it can be explained in a couple of sentences so please have a look at en.wikipedia.org/wiki/Visitor_pattern
B
BullyWiiPlaza

Here is a basic example:

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

Output:

Do println
Do print

S
Sercan özen

I'm not a java expert but I solve your problem like this:

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

I define the parameter in my special Interface

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

Finally, I implement getSearchText method while calling initialize method.

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

Actually it's the best answer and the proper way to do it. Deserve more +1
z
zemiak

Example of solution with reflection, passed method must be public

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

J
Joe Mwa

I appreciate the answers above but I was able to achieve the same behavior using the method below; an idea borrowed from Javascript callbacks. I'm open to correction though so far so good (in production).

The idea is to use the return type of the function in the signature, meaning that the yield has to be static.

Below is a function that runs a process with a 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());
}