Метод Java Pass в качестве параметра
Я ищу способ передать метод по ссылке. Я понимаю, что 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());
13 ответов:
Edit: С 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"); } }Edit: как Пит Киркхем указывает, есть еще один способ сделать это с помощью гость. Подход посетителя-это немного больше вовлечено - все ваши узлы должны быть осведомлены о посетителях с помощью
acceptVisitor()метод-но если вам нужно пройти более сложный граф объектов, то его стоит изучить.
в Java 8, Теперь вы можете передать метод более легко с помощью Лямбда-Выражения и ссылки на методы. Во-первых, некоторые фона: функциональный интерфейс-это интерфейс, который имеет один и только один абстрактный метод, хотя он может содержать любое количество методы по умолчанию (новый в Java 8) и статические методы. Лямбда-выражение может быстро реализовать абстрактный метод, без всех ненужных синтаксиса, необходимых, если вы не используете лямбда-выражение выражение.
без лямбда-выражений:
obj.aMethod(new AFunctionalInterface() { @Override public boolean anotherMethod(int i) { return i == 982 } });С лямбда-выражениями:
obj.aMethod(i -> i == 982);
вот отрывок из учебник Java по лямбда-выражениям:
синтаксис лямбда-выражений
лямбда-выражение состоит из следующего:
-разделенный запятыми список формальных параметров, заключенный в круглые скобки. В CheckPerson.тест метод содержит один параметр, p, который представляет собой экземпляр класса человек.
Примечание: ты можно опустить тип данных параметров в лямбда-выражении. В кроме того, вы можете опустить скобки, если есть только один параметр. Например, также допустимо следующее лямбда-выражение: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)обратите внимание, что лямбда-выражение очень похоже на объявление метода ; лямбда-выражения можно рассматривать как анонимные методы-методы без имени.
вот как вы можете "передать метод", используя лямбда-выражение:
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); } }
сначала определите интерфейс с методом, который вы хотите передать в качестве параметра
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 ); }
последний раз, когда я проверял, 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());
пока это еще не действует для 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 это не считается лямбда некоторыми, так как он имеет сторону эффекты. Однако он иллюстрирует использование одного из них более простым для визуализации способом.
Если вам не нужны эти методы для возврата чего-то, вы можете заставить их возвращать запускаемые объекты.
private Runnable methodName (final int arg){ return new Runnable(){ public void run(){ // do stuff with arg } } }тогда используйте его как:
private void otherMethodName (Runnable arg){ arg.run(); }
С Java 8 есть
Function<T, R>интерфейс (docs), который имеет методR apply(T t);вы можете использовать его для передачи функций в качестве параметров для других функций. T-Тип ввода функции,R-тип возврата.
в вашем примере вам нужно передать функцию, которая принимает
Componentтипа в качестве входных данных и возвращает nothing -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); }
используйте шаблон наблюдателя (иногда также называемый шаблоном слушателя):
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()...объявляет анонимный тип, реализующий интерфейс.
у Java есть механизм для передачи имени и вызова его. Это часть механизма отражения. Ваша функция должна принимать дополнительный параметр метода класса.
public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled) { ... Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist); ... }
вот простой пример:
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
Я не эксперт 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()); } })
Я не думаю, что лямбды предназначены для этого... Java не является функциональным языком программирования и никогда не будет таким, мы не передаем методы в качестве параметров. При этом помните, что Java является объектно-ориентированным, и с учетом этого мы можем делать все, что захотим. Первая идея состоит в том, чтобы просто передать "объект, содержащий метод" в качестве параметра. Поэтому всякий раз, когда вам нужно "передать" метод, просто передайте экземпляр этого класса. Обратите внимание, что при определении метода необходимо добавить в качестве параметра экземпляр класса, который содержит метод. Это должно работать, но это не то, что мы хотим, потому что вы не можете переопределить метод, если у вас нет доступа к коду класса, и это невозможно во многих случаях; более того, я думаю, что если кому-то нужно передать метод в качестве параметра, это потому, что поведение метода должно быть динамическим. Я имею в виду, что программист, который использует ваши классы, должен иметь возможность выбрать, какой метод должен возвращать, но не его тип. К счастью для нас, Java имеет красивое и простое решение: абстрактные классы. Абстрактные классы, в двух словах, используются, когда вы знаете "подпись" метода", но не его поведение... Вы можете обернуть имя и тип метод в абстрактный класс, и передать экземпляр этого класса в качестве параметра для метода... Ждать... разве это не то же самое, что и раньше? А может у вас есть экземпляр абстрактного класса? Нет и нет... но и да... при создании абстрактного метода вы также должны переопределить его в классе, который расширяет абстрактный класс и из-за динамической привязки java Java всегда будет (если вы не объявите его статическим, частным и некоторыми другими вещами) использовать переопределенную версию. Вот вам пример... Предположим, что мы хотим применить функцию к массиву чисел: так что если мы хотим, чтобы квадрат ввода-вывода должен выглядеть так [1,2,3,4,...]- >[1,4,9,16,...] (в функциональном языке программирования, таком как haskell, это легко благодаря некоторым инструментам, таким как "карта"...). Обратите внимание, что в этом нет ничего особенного о квадрате чисел, мы могли бы применить любую функцию, которую мы хотим. Поэтому код должен быть примерно таким [args], f -> [f(args)]. Вернемся к java = > функция-это просто метод, поэтому мы хотим, чтобы функция применяла другую функцию к массиву. В двух словах, нам нужно передать методу в качестве параметра. Вот как бы я это сделал==>
1) ОПРЕДЕЛИТЕ АБСТРАКТНЫЙ КЛАСС ОБОЛОЧКИ И МЕТОД
public abstract class Function { public abstract double f(double x); }2) ОПРЕДЕЛИТЕ КЛАСС С ПОМОЩЬЮ МЕТОДА APPLY_TO_ARRAY
public class ArrayMap { public static double[] apply_to_array(double[] arr, Function fun) { for(int i=0; i<arr.length;i++) { arr[i]=fun.f(arr[i]); } return arr; } }3) СОЗДАТЬ ТЕСТЕР-КЛАСС И ПОВЕСЕЛИТЬСЯ
public class Testclass extends Function { public static void main(String[] args) { double[] myarr = {1,2,3,4}; ArrayMap.apply_to_array(myarr, new Testclass()); for (double k : myarr) { System.out.println(k); } } @Override public double f(double x) { return Math.log(x); } }обратите внимание, что нам нужно передать объект типа Function и поскольку Testclass расширяет класс функций, мы можем его использовать, приведение выполняется автоматически.
Comments