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.