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


279

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

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

См. Также этот ответ stackoverflow.com/a/22933032/1010868 для аналогичного вопроса
— Томаш Гавель

Ответы:


233

Изменить : начиная с 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
@Mac - хорошо! этот снова и снова появляется в языках без первоклассных методов как фактический способ их моделирования, так что его стоит запомнить.
— Дэн Винтон

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

Нет, вам нужен только метод accept, если вы сочетаете посещение с двойной отправкой. Если у вас есть мономорфный посетитель, это именно тот код, который у вас есть выше.
— Пит Киркхам

В Java 8 может быть как ex.operS (String :: toLowerCase, "STRING"). Смотрите хорошую статью: studytrails.com/java/java8/…
— Zon

Пит Киркхем прав: ваш код реализует шаблон Visitor, а не шаблон Command (и это хорошо, поскольку это то, что нужно OP). Как говорит Пит, вы не инкапсулируете аргумент, поэтому вы не выполняете Command - ваша команда Интерфейс имеет выполнение, которое принимает параметр. Википедия нет. Это имеет основополагающее значение для намерения шаблона Command. Как говорится в первом абзаце, «инкапсулируйте всю информацию ... Эта информация включает в себя имя метода, объект, которому принадлежит метод, и значения параметров метода ».
— ToolmakerSteve

74

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

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

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

Нужно ли наследовать класс A от интерфейса?
— Serob_b

1
@Serob_b Нет. Если вы не хотите передать его как ссылку на метод (см. ::Оператор), не имеет значения, что такое A. a.changeThing(component)может быть изменен на любой оператор или блок кода, который вы хотите, если он возвращает void.
— Парень со шляпой

29

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


12
Я не понимаю, почему нет. Вопрос в том, чтобы передать метод в качестве параметра, и это очень правильный способ сделать это. Это также может быть добавлено в любое количество красивых шаблонов, чтобы они выглядели хорошо. И это настолько универсально, насколько это возможно, без необходимости каких-либо специальных интерфейсов.
— Винод Рамасубраманян,

3
Вы вводите безопасность в JavaScript FG? Тип безопасности не является аргументом.
— Дунайский моряк

13
Как безопасность типов не является аргументом, если рассматриваемый язык считает безопасность типов одним из самых сильных компонентов? Java является языком со строгой типизацией, и эта строгая типизация является одной из причин, по которой вы выбираете его вместо другого скомпилированного языка.
— Адам Паркин

21
«Средство базового отражения изначально было разработано для инструментальных средств компоновки приложений. [...] Как правило, объекты не должны отражаться в обычных приложениях во время выполнения». Пункт 53: Предпочитайте интерфейсы отражению от Effective Java Second Edition. - Это образ мыслей создателей Java ;-)
— Вильгельм Мейньян

8
Неоправданное использование отражения. Я с ужасом вижу все противники. отражение никогда не предназначалось для использования в качестве общего механизма программирования; используйте его только тогда, когда нет другого чистого раствора.
— ToolmakerSteve

22

Начиная с 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);
}

19

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

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

1
Вам, возможно, не придется создавать свой собственный интерфейс, так как java определил многие из них для вас: docs.oracle.com/javase/8/docs/api/java/util/function/…
— slim

@slim Интересно, насколько стабильны эти определения, предназначены ли они для обычного использования, как вы предлагаете, или они могут сломаться?
— Мануэль

@slim На самом деле, документы отвечают: «Интерфейсы в этом пакете являются функциональными интерфейсами общего назначения, используемыми JDK, и также доступны для использования пользовательским кодом».
— Мануэль

14

Хотя это еще не относится к 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

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

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 Это не считается лямбда, поскольку имеет побочные эффекты. Это, однако, иллюстрирует использование одного более простым для визуализации способом.


12

В прошлый раз, когда я проверял, 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());

6

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

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

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

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

2

Я не нашел достаточно четкого примера для меня, как использовать 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качестве параметра и возвращает a 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

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


чтобы использовать колл в Android, вам нужно минимум 24 api
— Ines Belhouchet

1

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

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

1

Я не эксперт по Java, но я решаю вашу проблему следующим образом:

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

Я определяю параметр в моем специальном интерфейсе

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

Наконец, я реализую метод getSearchText при вызове метода initialize .

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

На самом деле это лучший ответ и правильный способ сделать это. Заслуживают больше +1
— amdev

0

Используйте шаблон Observer (иногда его также называют шаблоном Listener):

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()... объявляет анонимный тип, реализующий интерфейс.


8
Это не тот шаблон, который вы ищете.
— Пит Киркхэм

1
Модель наблюдателя заключается в абстрагировании способности реагировать на изменения. OP хочет абстрагировать действие, выполняемое для каждого элемента в коллекции, от кода, выполняющего итерацию по коллекции, которая является шаблоном посетителя.
— Пит Киркхам

Шаблон Observer / Listener фактически совпадает с шаблоном Command. Они отличаются только намерением. Наблюдатель об уведомлении, в то время как команда заменяет функции первого класса / лямбды. Посетитель с другой стороны - это нечто совершенно иное. Я не думаю, что это можно объяснить парой предложений, поэтому, пожалуйста, взгляните на en.wikipedia.org/wiki/Visitor_pattern
— EricSchaefer

0

Вот основной пример:

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

Вывод:

Do println
Do print

0

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

  1. Шаг 1: Создайте два интерфейса, один с типом возврата, другой без. У Java есть подобные интерфейсы, но они имеют мало практического использования, потому что они не поддерживают исключение.


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


    public interface Return {
        R run() throws Exception;
    }
  1. Пример того, как мы используем оба интерфейса для переноса вызова метода в транзакции. Обратите внимание, что мы передаем метод с фактическими параметрами.


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

Другой пример показывает, как передать метод, который на самом деле возвращает что-то



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

0

Пример решения с отражением, переданный метод должен быть публичным

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

0

Я ценю ответы выше, но я смог добиться того же поведения, используя метод ниже; идея заимствована из обратных вызовов Javascript. Я открыт для исправления, хотя пока все хорошо (в производстве).

Идея состоит в том, чтобы использовать тип возвращаемого значения функции в сигнатуре, что означает, что yield должен быть статическим.

Ниже приведена функция, которая запускает процесс с таймаутом.

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());
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.