Метод передачі Java як параметр


277

Я шукаю спосіб передачі методу шляхом посилання. Я розумію, що 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 на подібне запитання
Tomasz Gawel

Відповіді:


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 - добре! ця мова знову і знову з'являється в мовах без першокласних методів як фактичного способу їх моделювання, тому варто пам’ятати.
Dan Vinton

7
Це шаблон відвідувача (відокремте дію ітерації над колекцією від функції, застосованої до кожного члена колекції), а не шаблон команди (інкапсулює аргументи для виклику методу в об’єкт). Ви конкретно не інкапсулюєте аргумент - це передбачено ітераційною частиною шаблону відвідувачів.
Піт Кіркхем

Ні, метод прийому вам потрібен лише в тому випадку, якщо ви поєднуєте відвідування з подвійною відправленням. Якщо у вас є мономорфний відвідувач, це саме той код, який ви маєте вище.
Піт Кіркхем,

У Java 8 може бути як ex.operS (String :: toLowerCase, "STRING"). Дивіться приємну статтю: studytrails.com/java/java8/…
Зон

Піт Кіркхем правильний: ваш код реалізує шаблон відвідувача, а не шаблон команди (і це добре, оскільки це потрібне ОП.) Як каже Піт, ви не інкапсулюєте аргумент, тому ви не виконуєте команду - ваша команда інтерфейс має виконати, який приймає параметр. У Вікіпедії немає. Це є основним для наміру схеми Command. Як зазначено в першому абзаці, "інкапсулює всю інформацію ... Ця інформація включає ім'я методу, об'єкт, який належить методу, та значення параметрів методу ".
ToolmakerSteve

73

У Java 8 тепер можна простіше передати метод, використовуючи Lambda вирази та посилання на методи. По-перше, деякий фон: функціональний інтерфейс - це інтерфейс, який має один і лише один абстрактний метод, хоча він може містити будь-яку кількість методів за замовчуванням (нові в Java 8) та статичних методів. Вираз лямбда може швидко реалізувати абстрактний метод, без усього зайвого синтаксису, якщо ви не використовуєте лямбда-вираз.

Без лямбда-виразів:

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

З лямбда-виразами:

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

Ось уривок з навчального посібника Java на тему Lambda Expressions :

Синтаксис лямбдаських виразів

Лямбда-вираз складається з наступного:

  • Список формальних параметрів, розділених комами, укладений у дужки. Метод 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);
    }
}

Чи потрібно успадковувати клас A з інтерфейсу?
Serob_b

1
@Serob_b Ні. Якщо ви не хочете передати його як посилання на метод (див. ::Оператор), не має значення, що таке A. a.changeThing(component)може бути змінено на будь-який заяву або блок коду, який ви хочете, доки він повернеться недійсним.
Хлопець з капелюхом

29

Використовуйте java.lang.reflect.Methodоб’єкт і дзвонітьinvoke


12
Я не бачу, чому ні. Питання полягає в тому, щоб передати метод як параметр, і це дуже коректний спосіб зробити це. Це також можна загорнути в будь-яку кількість симпатичного малюнка, щоб він виглядав добре. І це так само загально, як це виходить, не потребуючи жодних спеціальних інтерфейсів.
Вінод Рамасубраманіан

3
Ви ввели безпеку в JavaScript fg? Безпека типу - це не аргумент.
Дунайський матрос

13
Як безпека типу не є аргументом, коли мова, про яку йде мова, підтримує безпеку типу як одного з найсильніших компонентів? Java - це сильно набрана мова, і ця сильна введення тексту є однією з причин, коли ви вибрали її для іншої компільованої мови.
Адам Паркін

21
"Основна програма відображення спочатку була розроблена для інструментів побудови додатків на основі компонентів. [...] Як правило, до об'єктів не слід отримувати рефлекторний доступ у звичайних додатках під час виконання." Пункт 53: Віддавайте перевагу інтерфейсам рефлексії від Ефективного другого видання Java. - Ось така лінія думок творців Java ;-)
Вільхем Мейньян

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

22

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

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 Власне, документи відповідають: "Інтерфейси в цьому пакеті є функціональними інтерфейсами загального призначення, що використовуються 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об'єкт із конструктором за замовчуванням. A, methodякий буде називатися параметром, з parametrisedMethodякого є тип Function<String, Foo>.

  • Function<String, Foo>означає, що функція приймає Stringпараметр a і повертає 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
квітня

1

У Java є механізм передавати ім'я та викликати його. Це частина механізму відображення. Ваша функція повинна приймати додатковий параметр класу Метод.

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(getMessageContactModelList(), new AutoCompleteCallable() {
          @Override
          public String call(Object model) throws Exception {
            return "custom string" + ((xxxModel)model.getTitle());
          }
        })

Насправді це найкраща відповідь і правильний спосіб це зробити. Заслуговують більше +1
amdev

0

Скористайтеся шаблоном Observer (іноді його також називають шаблоном слухача):

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
Шаблон спостерігача полягає у відміні здатності реагувати на зміни. ОП хоче абстрагувати дії, зроблені на кожному елементі колекції, далеко від ітераційного коду над колекцією, що є схемою відвідувачів.
Піт Кіркхем

Шаблон спостерігача / слухача насправді такий же, як і шаблон команди. Вони відрізняються лише за наміром. Спостерігач йде про сповіщення, тоді як команда є заміною функцій / лямбдів першого класу. З іншого боку, відвідувач - це щось зовсім інше. Я не думаю, що це можна пояснити в кількох реченнях, тому, будь ласка, подивіться на 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. Я готовий до виправлення, хоча поки що добре (у виробництві).

Ідея полягає у використанні функції повернення функції в підписі, тобто вихід має бути статичним.

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

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.