Вопрос-ответ

Java Pass Method as Parameter

Java Передает метод в качестве параметра

Я ищу способ передать метод по ссылке. Я понимаю, что 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());
Переведено автоматически
Ответ 1

Редактировать: начиная с 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");
}
}

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

Ответ 2

В 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 вычислит выражение и затем вернет его значение. В качестве альтернативы,
    вы можете использовать оператор return:


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

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


    email -> System.out.println(email)


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



Вот как вы можете "передать метод", используя лямбда-выражение:

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);
}
}
Ответ 3

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

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);
}
Ответ 4

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

java