Ссылка на метод Java
У меня есть класс с этими методами:
public class TestClass
{
public void method1()
{
// this method will be used for consuming MyClass1
}
public void method2()
{
// this method will be used for consuming MyClass2
}
}
и классы:
public class MyClass1
{
}
public class MyClass2
{
}
и я хочу HashMap<Class<?>, "question">
, где я буду хранить (key: class, value: method) пары, подобные этому (класс "type" связан с методом)
hashmp.add(Myclass1.class, "question");
и я хочу знать, как добавить ссылки метода на HashMap (заменить "вопрос" ).
p.s. Я пришел из С#, где я просто пишу Dictionary<Type, Action>
:)
Ответы
Ответ 1
Это функция, которая, вероятно, будет Java 8. На данный момент самый простой способ сделать это - использовать отражение.
public class TestClass {
public void method(MyClass1 o) {
// this method will be used for consuming MyClass1
}
public void method(MyClass2 o) {
// this method will be used for consuming MyClass2
}
}
и назовите его с помощью
Method m = TestClass.class.getMethod("method", type);
Ответ 2
Теперь, когда Java 8 вышел, я подумал, что обновляю этот вопрос, как это сделать на Java 8.
package com.sandbox;
import java.util.HashMap;
import java.util.Map;
public class Sandbox {
public static void main(String[] args) {
Map<Class, Runnable> dict = new HashMap<>();
MyClass1 myClass1 = new MyClass1();
dict.put(MyClass1.class, myClass1::sideEffects);
MyClass2 myClass2 = new MyClass2();
dict.put(MyClass2.class, myClass2::sideEffects);
for (Map.Entry<Class, Runnable> classRunnableEntry : dict.entrySet()) {
System.out.println("Running a method from " + classRunnableEntry.getKey().getName());
classRunnableEntry.getValue().run();
}
}
public static class MyClass1 {
public void sideEffects() {
System.out.println("MyClass1");
}
}
public static class MyClass2 {
public void sideEffects() {
System.out.println("MyClass2");
}
}
}
Ответ 3
Method method = TestClass.class.getMethod("method name", type)
Ответ 4
Используйте интерфейсы вместо указателей на функции. Поэтому определите интерфейс, который определяет функцию, которую вы хотите вызвать, а затем вызовите интерфейс, как в примере выше. Для реализации интерфейса вы можете использовать анонимный внутренний класс.
void DoSomething(IQuestion param) {
// ...
param.question();
}
Ответ 5
Пока вы можете хранить объекты java.lang.reflect.Method
на своей карте, я бы посоветовал это: вам все равно нужно передать объект, который используется в качестве ссылки this
при вызове, а использование необработанных строк для имен методов может создавать проблемы в рефакторинге.
Канонический способ сделать это - извлечь интерфейс (или использовать существующий) и использовать анонимные классы для хранения:
map.add(MyClass1.class, new Runnable() {
public void run() {
MyClass1.staticMethod();
}
});
Я должен признать, что это намного более многословно, чем С# -вариант, но это обычная практика Java - например. при выполнении обработки событий с помощью Listeners. Однако другие языки, которые основаны на JVM, обычно имеют сокращенные обозначения для таких обработчиков. Используя интерфейс-подход, ваш код совместим с Groovy, Jython или JRuby, и он по-прежнему является типичным.
Ответ 6
Чтобы ответить на ваш прямой вопрос относительно использования Map
, ваши предлагаемые классы:
interface Question {} // marker interface, not needed but illustrative
public class MyClass1 implements Question {}
public class MyClass2 implements Question {}
public class TestClass {
public void method1(MyClass1 obj) {
System.out.println("You called the method for MyClass1!");
}
public void method2(MyClass2 obj) {
System.out.println("You called the method for MyClass2!");
}
}
Тогда ваш Map
будет:
Map<Class<? extends Question>, Consumer<Question>> map = new HashMap<>();
и заселен следующим образом:
TestClass tester = new TestClass();
map.put(MyClass1.class, o -> tester.method1((MyClass1)o)); // cast needed - see below
map.put(MyClass2.class, o -> tester.method2((MyClass2)o));
и используется так:
Question question = new MyClass1();
map.get(question.getClass()).accept(question); // calls method1
Вышеописанное работает нормально, но проблема в том, что нет способа подключить тип ключа к карте с типом его значения, т.е. вы не можете использовать generics для правильного ввода значения потребителя и так используйте ссылку на метод:
map.put(MyClass1.class, tester::method1); // compile error
почему вам нужно передать объект в лямбда для привязки к правильному методу.
Есть и другая проблема. Если кто-то создает новый класс Question, вы не знаете до тех пор, пока не увидите, что в нем нет записи для этого класса, и вам нужно написать код типа if (!map.containsKey(question.getClass())) { // explode }
для обработки этой возможности.
Но есть альтернатива...
Существует еще один шаблон, который дает вам время безопасности компиляции и означает, что вам не нужно писать какой-либо код для обработки "отсутствующих записей". Шаблон называется Double Dispatch (который является частью Visitor).
Он выглядит следующим образом:
interface Tester {
void consume(MyClass1 obj);
void consume(MyClass2 obj);
}
interface Question {
void accept(Tester tester);
}
public class TestClass implements Tester {
public void consume(MyClass1 obj) {
System.out.println("You called the method for MyClass1!");
}
public void consume(MyClass2 obj) {
System.out.println("You called the method for MyClass2!");
}
}
public class MyClass1 implements Question {
// other fields and methods
public void accept(Tester tester) {
tester.consume(this);
}
}
public class MyClass2 implements Question {
// other fields and methods
public void accept(Tester tester) {
tester.consume(this);
}
}
И использовать его:
Tester tester = new TestClass();
Question question = new MyClass1();
question.accept(tester);
или для многих вопросов:
List<Question> questions = Arrays.asList(new MyClass1(), new MyClass2());
questions.forEach(q -> q.accept(tester));
Этот шаблон работает, помещая обратный вызов в целевой класс, который может привязываться к правильному методу обработки этого класса для объекта this
.
Преимущество этого шаблона заключается в том, что создается другой класс Question, требуется реализовать метод accept(Tester)
, поэтому разработчик вопроса не забудет реализовать обратный вызов тестеру и автоматически проверяет, что тестеры могут обрабатывать новая реализация, например
public class MyClass3 implements Question {
public void accept(Tester tester) { // Questions must implement this method
tester.consume(this); // compile error if Tester can't handle MyClass3 objects
}
}
Также обратите внимание на то, как два класса не ссылаются друг на друга - они ссылаются только на интерфейс, поэтому общая развязка между тестерами и реализациями вопроса (что также упрощает модульное тестирование/mocking).
Ответ 7
Вы пробовали объект Method
? см:
Ответ 8
Вы отмечаете в комментарии к коду, что каждый метод использует объект определенного типа. Поскольку это обычная операция, Java уже предоставляет вам функциональный интерфейс Consumer
, который действует как способ взять объект определенного типа в качестве входных данных и сделать некоторые действия над ним (два слова до сих пор, о которых вы уже упоминали в вопросе: "потреблять" и "действие" ).
Таким образом, карта может содержать записи, в которых ключ представляет собой класс, такой как MyClass1
и MyClass2
, а значение является потребителем объектов этого класса:
Map<Class<T>, Consumer<T>> consumersMap = new HashMap<>();
Так как a Consumer
является функциональным интерфейсом, то есть интерфейсом только с одним абстрактным методом, его можно определить с помощью выражения лямбда:
Consumer<T> consumer = t -> testClass.methodForTypeT(t);
где testClass
- это экземпляр testClass
.
Поскольку эта лямбда ничего не делает, кроме вызова существующего метода methodForTypeT
, вы можете напрямую использовать ссылку :
Consumer<T> consumer = testClass::methodForTypeT;
Затем, если вы измените подписи методов testClass
на method1(MyClass1 obj)
и method2(MyClass2 obj)
, вы сможете добавить эти ссылки методов к карте:
consumersMap.put(MyClass1.class, testClass::method1);
consumersMap.put(MyClass2.class, testClass::method2);
Ответ 9
Ваш вопрос
Учитывая ваши классы с помощью некоторых методов:
public class MyClass1 {
public void boo() {
System.err.println("Boo!");
}
}
и
public class MyClass2 {
public void yay(final String param) {
System.err.println("Yay, "+param);
}
}
Затем вы можете получить методы с помощью отражения:
Method method=MyClass1.class.getMethod("boo")
При вызове метода вам необходимо передать экземпляр класса:
final MyClass1 instance1=new MyClass1();
method.invoke(instance1);
Сложим вместе:
public class Main {
public static void main(final String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
final Map<Class<?>,Method> methods=new HashMap<Class<?>,Method>();
methods.put(MyClass1.class,MyClass1.class.getMethod("boo"));
methods.put(MyClass2.class,MyClass2.class.getMethod("yay",String.class));
final MyClass1 instance1=new MyClass1();
methods.get(MyClass1.class).invoke(instance1);
final MyClass2 instance2=new MyClass2();
methods.get(MyClass2.class).invoke(instance2,"example param");
}
}
дает:
Boo!
Yay, пример param
Следите за следующими ошибками:
- hardcoded имя метода как строка - это очень трудно избежать
- это отражение, поэтому доступ к метаданным класса во время выполнения. Устранить множество исключений (в этом примере не обрабатывается)
- вам нужно указать не только имя метода, но и типы параметров, чтобы получить доступ к одному методу. Это связано с тем, что перегрузка метода является стандартной, и это единственный способ выбрать правильный перегруженный метод.
- следить при вызове метода с параметрами: проверка типа параметра компиляции отсутствует.
Альтернативный ответ
Я предполагаю, что вы ищете простой слушатель: т.е. способ вызова метода из другого класса косвенно.
public class MyClass1 implements ActionListener {
@Override
public void actionPerformed(final ActionEvent e) {
System.err.println("Boo!");
}
}
и
public class MyClass2 implements ActionListener {
@Override
public void actionPerformed(final ActionEvent e) {
System.err.println("Yay");
}
}
используя:
public class Main {
public static void main(final String[] args) {
final MyClass1 instance1=new MyClass1();
final MyClass2 instance2=new MyClass2();
final Map<Class<?>,ActionListener> methods=new HashMap<Class<?>,ActionListener>();
methods.put(MyClass1.class,instance1);
methods.put(MyClass2.class,instance2);
methods.get(MyClass1.class).actionPerformed(null);
methods.get(MyClass2.class).actionPerformed(null);
}
}
Это называется шаблоном прослушивателя. Я осмелился повторно использовать ActionListener из Java Swing, но на самом деле вы можете очень легко сделать своих собственных слушателей, объявив интерфейс с помощью метода. MyClass1, MyClass2 реализует этот метод, а затем вы можете вызвать его так же, как метод....
Нет отражений, нет жестко закодированных строк, беспорядка. (ActionListener позволяет передавать один параметр, который настроен для приложений GUI. В моем примере я просто передаю null.)
Ответ 10
Использование API reflaction
Метод methodObj = TestClass.class.getMethod( "имя метода", тип)