Метод Java Pass как параметр - PullRequest
239 голосов
/ 02 февраля 2010

Я ищу способ передать метод по ссылке. Я понимаю, что Java не передает методы в качестве параметров, однако я хотел бы получить альтернативу.

Мне сказали, что интерфейсы являются альтернативой передаче методов в качестве параметров, но я не понимаю, как интерфейс может действовать как метод по ссылке. Если я правильно понимаю, интерфейс - это просто абстрактный набор методов, которые не определены. Я не хочу отправлять интерфейс, который нужно определять каждый раз, потому что несколько разных методов могут вызывать один и тот же метод с одинаковыми параметрами.

Что я хотел бы сделать, это примерно так:

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
}

вызывается, например:

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

Ответы [ 15 ]

211 голосов
/ 02 февраля 2010

Редактировать : начиная с Java 8, лямбда-выражения являются хорошим решением, как указали другие ответы .Ответ ниже был написан для Java 7 и более ранних версий ...


Взгляните на шаблон команды .

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

Редактировать: как Пит Киркхэм указывает , есть еще один способ сделать это с помощью Visitor .Подход на основе посетителей немного более сложный - все ваши узлы должны быть осведомлены о посетителях с помощью метода acceptVisitor(), но если вам нужно пройти по более сложному графу объектов, стоит изучить.

59 голосов
/ 29 июля 2014

В Java 8 теперь вы можете легче передавать метод, используя Лямбда-выражения и Ссылки на методы. Сначала немного предыстории: функциональный интерфейс - это интерфейс, который имеет один-единственный абстрактный метод, хотя он может содержать любое количество стандартных методов (новинка в Java 8) и статических методов. Лямбда-выражение может быстро реализовать абстрактный метод без всего лишнего синтаксиса, необходимого, если вы не используете лямбда-выражение.

Без лямбда-выражений:

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

С лямбда-выражениями:

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

Вот выдержка из учебника Java по лямбда-выражениям :

Синтаксис лямбда-выражений

Лямбда-выражение состоит из следующего:

  • Список формальных параметров через запятую, заключенный в скобки. Метод CheckPerson.test содержит один параметр, p, который представляет экземпляр класса Person.

    Примечание : Вы может опускать тип данных параметров в лямбда-выражении. В Кроме того, вы можете опустить скобки, если есть только один параметр. Например, следующее лямбда-выражение также допустимо:

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
    
  • Жетон стрелки, ->

  • Тело, состоящее из одного выражения или блока операторов. В этом примере используется следующее выражение:

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

    Если вы укажете одно выражение, тогда среда выполнения Java оценивает выражение и затем возвращает его значение. С другой стороны, Вы можете использовать оператор возврата:

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

    Оператор возврата не является выражением; в лямбда-выражении вы должны заключать выражения в фигурные скобки ({}). Однако у вас нет заключить пустой вызов метода в фигурные скобки. Например, следующее допустимое лямбда-выражение:

    email -> System.out.println(email)
    

Обратите внимание, что лямбда-выражение очень похоже на объявление метода; Вы можете рассматривать лямбда-выражения как анонимные методы - методы без имени.


Вот как можно «передать метод» с помощью лямбда-выражения:

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

Класс C можно укоротить еще немного, используя ссылки на методы, например:

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

    public C() {
        b.setAllComponents(this.getComponents(), a::changeColor);
        b.setAllComponents(this.getComponents(), a::changeSize);
    }
}
24 голосов
/ 02 февраля 2010

Используйте объект java.lang.reflect.Method и вызовите invoke

17 голосов
/ 02 февраля 2010

Сначала определите интерфейс с методом, который вы хотите передать в качестве параметра

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

Реализация класса с помощью метода

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

// Вызывать так

Callable cmd = new Test();

Это позволяет вам передавать cmd в качестве параметра и вызывать вызов метода, определенный в интерфейсе

public invoke( Callable callable ) {
  callable.call( 5 );
}
12 голосов
/ 28 октября 2013

Хотя это еще не относится к Java 7 и ниже, я считаю, что мы должны смотреть в будущее и, по крайней мере, признать изменения , которые появятся в новых версиях, таких как Java 8.

А именно, эта новая версия приносит лямбды и ссылки на методы на Java (вместе с новыми API , которые являются еще одним допустимым решением этой проблемы. Хотя им все еще не требуется интерфейс, нет новых объектовсоздаются, и дополнительные файлы классов не должны загрязнять выходные каталоги из-за разной обработки JVM.

Для обоих вариантов (лямбда и ссылка на метод) требуется интерфейс, доступный для одного метода, чья сигнатура используется:

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

Имена методов здесь не будут иметь значения. Там, где принимается лямбда, также указывается ссылка на метод. Например, для использования нашей подписи здесь:

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

Это простопростой вызов интерфейса, до тех пор, пока не будет принята лямбда 1 :

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

Будет выведено:

Lambda reached!
lambda return

Ссылка на методRences похожи.Дано:

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

и главное:

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

, результат будет real static method. Ссылки на методы могут быть статическими, экземплярами, нестатическими с произвольными экземплярами и даже конструкторами .Для конструктора будет использоваться что-то похожее на ClassName::new.

1 Это не считается лямбда, поскольку имеет побочные эффекты.Однако это иллюстрирует использование одного из них более простым для визуализации способом.

11 голосов
/ 02 февраля 2010

В прошлый раз, когда я проверял, Java не может делать то, что вы хотите; Вы должны использовать «обходные пути», чтобы обойти такие ограничения. Насколько я понимаю, интерфейсы являются альтернативой, но не очень хорошей альтернативой. Возможно, тот, кто сказал вам, что имел в виду нечто подобное:

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
}

Который вы бы затем вызвали:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());
8 голосов
/ 05 октября 2017

Начиная с Java 8 существует интерфейс Function<T, R> ( документы ), который имеет метод

R apply(T t);

Вы можете использовать его для передачи функций в качестве параметров другим функциям. T - тип ввода функции, R - тип возврата.

В вашем примере вам нужно передать функцию, которая принимает тип Component в качестве ввода и ничего не возвращает - Void. В этом случае Function<T, R> не лучший выбор, так как нет автобокса типа Void. Интерфейс, который вы ищете, называется Consumer<T> ( docs ) с методом

void accept(T t);

Это будет выглядеть так:

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

И вы бы назвали это, используя ссылки на метод:

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

Предполагается, что вы определили методы changeColor () и changeSize () в одном классе.


Если ваш метод принимает более одного параметра, вы можете использовать BiFunction<T, U, R> - T и U - это типы входных параметров, а R - тип возврата. Существует также BiConsumer<T, U> (два аргумента, без возвращаемого типа). К сожалению, для 3 и более входных параметров вы должны создать интерфейс самостоятельно. Например:

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

    R apply(A a, B b, C c, D d);
}
6 голосов
/ 12 июня 2014

Если вам не нужны эти методы для возврата чего-либо, вы можете заставить их возвращать Runnable объекты.

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

Тогда используйте это как:

private void otherMethodName (Runnable arg){
    arg.run();
}
1 голос
/ 09 ноября 2018

Я не нашел достаточно явного примера того, как использовать java.util.function.Function для простого метода в качестве функции параметра. Вот простой пример:

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

Обычно у вас есть Foo объект с конструктором по умолчанию. method, который будет вызываться как параметр из parametrisedMethod типа Function<String, Foo>.

  • Function<String, Foo> означает, что функция принимает String в качестве параметра и возвращает Foo.
  • Foo::Method соответствует лямбда-подобию x -> Foo.method(x);
  • parametrisedMethod(Foo::method) можно рассматривать как x -> parametrisedMethod(Foo.method(x))
  • .apply("from a method") в основном делать parametrisedMethod(Foo.method("from a method"))

Который затем вернется в вывод:

>> I'm a Foo from a method

Пример должен работать как есть, затем вы можете попробовать более сложные вещи из приведенных выше ответов с различными классами и интерфейсами.

1 голос
/ 02 февраля 2010

В Java есть механизм для передачи имени и его вызова. Это часть механизма отражения. Ваша функция должна принимать дополнительный параметр класса Method.

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...