Метод 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());
19 ответов
Изменить: начиная с 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()
метод - но если вам нужно пройти по более сложному графу объектов, то это стоит изучить.
В 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);
}
}
Начиная с Java 8 есть Function<T, R>
интерфейс ( документы), который имеет метод
R apply(T t);
Вы можете использовать его для передачи функций в качестве параметров другим функциям. T - тип ввода функции, R - тип возврата.
В вашем примере вам нужно передать функцию, которая принимает Component
введите в качестве ввода и ничего не возвращает - Void
, В этом случае Function<T, R>
не лучший выбор, так как нет автобокса типа Void. Интерфейс, который вы ищете, называется Consumer<T>
( документы) с методом
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);
}
Сначала определите интерфейс с методом, который вы хотите передать в качестве параметра
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 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 Это не считается лямбда, поскольку имеет побочные эффекты. Это, однако, иллюстрирует использование одного более простым для визуализации способом.
В прошлый раз, когда я проверял, 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());
Если вам не нужны эти методы для возврата чего-либо, вы можете заставить их возвращать объекты Runnable.
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 и новее
Начиная с Java 8, вы можете обеспечить реализацию абстрактного метода функционального интерфейса (интерфейса, который имеет только один абстрактный метод) с использованием лямбда-выражения и передать его методу в качестве параметра.
@FunctionalInterface
interface ArithmeticFunction {
public int calcualate(int a, int b);
}
public class Main {
public static void main(String args[]) {
ArithmeticFunction addition = (a, b) -> a + b;
ArithmeticFunction subtraction = (a, b) -> a - b;
int a = 20, b = 5;
System.out.println(perform(addition, a, b));
// or
System.out.println(perform((x, y) -> a + b, a, b));
System.out.println(perform(subtraction, a, b));
// or
System.out.println(perform((x, y) -> a - b, a, b));
}
static int perform(ArithmeticFunction function, int a, int b) {
return function.calcualate(a, b);
}
}
Выход:
25
25
15
15
Узнайте больше об этом в Справочниках по методам.
Я не нашел достаточно четкого примера для меня о том, как использовать 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
в качестве параметра и вернуть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
Пример должен работать как есть, затем вы можете попробовать более сложные вещи из приведенных выше ответов с различными классами и интерфейсами.
Используйте шаблон 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()...
объявляет анонимный тип, реализующий интерфейс.
Я не эксперт по 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: Создайте два интерфейса, один с типом возврата, другой без. У Java есть подобные интерфейсы, но они мало практического использования, потому что они не поддерживают исключение.
public interface Do {
void run() throws Exception;
}
public interface Return {
R run() throws Exception;
}
- Пример того, как мы используем оба интерфейса для переноса вызова метода в транзакции. Обратите внимание, что мы передаем метод с фактическими параметрами.
//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));
В Java есть механизм для передачи имени и его вызова. Это часть механизма отражения. Ваша функция должна принимать дополнительный параметр класса Method.
public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}
Пример решения с отражением, переданный метод должен быть публичным
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);
}
}
Я ценю приведенные выше ответы, но мне удалось добиться того же поведения, используя метод ниже; идея, заимствованная из обратных вызовов 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());
}
Вот основной пример:
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
Чтобы передать ссылку на метод из класса A в класс B, а затем вызвать переданный метод в классе A из класса B, вы можете использовать функциональные интерфейсы и лямбда-выражения в Java. Вот пример.
Класс Б
Определите функциональный интерфейс в классе B, который соответствует сигнатуре метода, который вы хотите передать из класса A. Назовем его MethodInterface:
public interface MethodInterface {
void methodName();
}
Продолжая работать в классе B, создайте метод, который принимает экземпляр MethodInterface в качестве параметра и вызывает ссылку на метод:
public class B {
public void callMethod(MethodInterface method) {
method.methodName();
}
}
Класс А
В классе A определите метод, который вы хотите передать классу B. Убедитесь, что он соответствует сигнатуре MethodInterface:
public class A {
public void methodToPass() {
System.out.println("Method called from class B");
}
}
Продолжая работать в классе A, создайте экземпляр класса B и передайте ссылку на метод в метод MethodToPass, используя лямбда-выражение:
public class A {
public void methodToPass() {
System.out.println("Method called from class B");
}
public static void main(String[] args) {
A objA = new A();
B objB = new B();
objB.callMethod(objA::methodToPass);
}
}
В этом примере — это ссылка на метод, которая ссылается на метод класса A. Она передается вcallMethod
метод в классе B, который затем вызывает ссылку на метод, что приводит к выполнению в классе A.
Обратите внимание, что::
Оператор используется для создания ссылки на метод. СинтаксисobjA::methodToPass
эквивалентно созданию анонимного внутреннего класса, реализующегоMethodInterface
и звонкиmethodToPass
на предоставленном экземпляре класса A.
Я не думаю, что лямбды предназначены для этого... Java не является функциональным языком программирования и никогда не будет таким, мы не передаем методы в качестве параметров. При этом помните, что Java является объектно-ориентированной, и с учетом этого мы можем делать все, что захотим. Первая идея состоит в том, чтобы просто передать "объект, который содержит метод" в качестве параметра. Поэтому, когда вам нужно "передать" метод, просто передайте экземпляр этого класса. Обратите внимание, что когда вы определяете метод, вы должны добавить в качестве параметра экземпляр класса, который содержит метод. Это должно работать, но это не то, что мы хотим, потому что вы не можете переопределить метод, если у вас нет доступа к коду класса, а во многих случаях это невозможно; более того, я думаю, что если кому-то нужно передать метод в качестве параметра, то это потому, что поведение метода должно быть динамичным. Я имею в виду, что программист, использующий ваши классы, должен иметь возможность выбирать, что должен возвращать метод, но не его тип. К счастью для нас, у Java есть красивое и простое решение: абстрактные классы. Вкратце, абстрактные классы используются, когда вы знаете "сигнатуру" метода ", но не знаете его поведение... Вы можете обернуть имя и тип метода в абстрактный класс и передать экземпляр этого класса как параметр для метода... Подождите... разве это не то же самое, что и раньше? И можете ли вы иметь экземпляр абстрактного класса? Нет и нет... но также да... когда вы создаете абстрактный метод вы также должны переопределить его в классе, который расширяет абстрактный класс, и из-за динамического связывания Java Java всегда (если вы не объявите его статическим, закрытым и некоторыми другими вещами) будет использовать переопределенную версию этого. Вот пример... Предположим, мы хотим применить функцию к массиву чисел: поэтому, если мы хотим возвести в квадрат, ввод-вывод должен выглядеть следующим образом [1,2,3,4,...]->[1,4,9,16,...] (в функциональном языке программирования, таком как haskell, это легко сделать благодаря некоторым инструментам, таким как 'map',...). Обратите внимание, что в квадрате чисел нет ничего особенного, мы могли бы применить любую функцию, какую захотим т. Таким образом, код должен быть примерно таким [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 расширяет класс Function, мы можем его использовать, приведение происходит автоматически.