Подтвердить что ты не робот

Ссылка на метод 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>:)

4b9b3361

Ответ 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).

Ответ 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( "имя метода", тип)