Указатели функций/делегаты в Java?

Для моего игрового сервера Java я отправляю идентификатор действия пакета, который в основном сообщает серверу, для чего предназначен пакет. Я хочу сопоставить каждый идентификатор действия (целое число) с функцией. Есть ли способ сделать это без использования переключателя?

Ответ 1

Как насчет этого?

HashMap<Integer, Runnable> map = new HashMap<Integer, Runnable>();
map.put(Register.ID, new Runnable() { 
    public void run() { functionA(); }
});
map.put(NotifyMessage.ID, new Runnable() { 
    public void run() { functionB(); }
});
// ...
map.get(id).run();

(Если вам нужно передать некоторые аргументы, определите свой собственный интерфейс с функцией, имеющей подходящий параметр, и используйте это вместо Runnable).

Ответ 2

Java не имеет указателей на функции (вместо этого мы получили анонимные внутренние классы). Однако, действительно, нет ничего плохого в использовании переключателя, если вы включаете значение, а не тип. Есть ли причина, по которой вы не хотите использовать переключатель? Похоже, вам нужно будет делать сопоставление между идентификаторами действий и действиями где-то в вашем коде, поэтому почему бы не сохранить его простым?

Ответ 3

В Java нет указателей функций первого класса. Для достижения аналогичной функциональности вам необходимо определить и реализовать интерфейс. Вы можете упростить использование анонимных внутренних классов, но это все еще не очень красиво. Вот пример:

public interface PacketProcessor
{
    public void processPacket(Packet packet);
}

...

PacketProcessor doThing1 = new PacketProcessor()
{
    public void processPacket(Packet packet)
    {
        // do thing 1
    }
};
// etc.

// Now doThing1, doThing2 can be used like function pointers for a function taking a
// Packet and returning void

Ответ 4

Вы когда-нибудь использовали Swing/AWT? Их иерархия событий решает аналогичную проблему. Способ, которым Java проходит функции, связан с интерфейсом, например

public interface ActionHandler {
    public void actionPerformed(ActionArgs e);
}

Затем, если вы хотите отобразить целые числа на эти объекты, вы можете использовать что-то вроде java.util.HashMap<Integer,ActionHandler> для управления этим. Фактические реализации могут либо идти в анонимных классах (наилучшее приближение Java "лямбда" ), либо где-то в соответствующих классах. Здесь анонимный способ:

HashMap<Integer,ActionHandler> handlers;
handlers.put(ACTION_FROB, new ActionHandler() {
    public void actionPerformed(ActionArgs e) {
        // Do stuff
        // Note that any outer variables you intend to close over must be final.
    }
});
handlers.get(ACTION_FROB).actionPerformed(foo);

(edit) Если вы хотите быть еще более оскорбительным, вы можете инициализировать HashMap следующим образом:

HashMap<Integer,String> m = new HashMap<Integer,String>() {{
    put(0,"hello");
    put(1,"world");
}};

Ответ 5

Да, но с помощью интерфейса вы должны создать интерфейс для каждого обратного вызова, что означает каждую функцию, которую вы хотите передать ему. Создание класса делегата для обработки этого дает вам (а не истинный указатель функции), но функцию, которую нужно передать, и если вы злоупотребляете родовым типом возвращаемого типа, вам не нужно бросать то, что сокращает узкое место до почти ничего.

Делегат С# (MultiCastDelegate, который будет правильным) получает информацию из метода MethodInfo, который будет тем же самым, что вам нужно будет сделать для класса Delegate, используя java.lang.reflect.method. Я разместил свой код для класса Delegate (T) в другой форме этого сайта, посвященной этой полной проблеме. Я делаю это потому, что (да) из С++ мне нужен лучший способ для передачи функций (особенно Void), а затем для создания интерфейса для функции или более. Теперь я могу выбрать функцию, заполняющую информацию о параметрах для нее. Voila`! Приятный и удобный, без заметной потери скорости от JIT или JVM. И если бы я только изучал Java-программирование всего на неделю, любой Java-программист может это сделать.

Кроме того, он отлично работает при создании базового прослушивателя и базового интерфейса для передачи в слушателе. Больше не нужно писать другого слушателя, потому что имя функции изменилось. Создание класса делегата имеет большие преимущества, так как оно очень полезно и доступно.

Ответ 6

Вы можете сделать это, используя шаблон цепи ответственности.

Это шаблон, который связывает разные объекты вместе, как связанный список. т.е. каждый объект имеет ссылку на следующий в цепочке. Объекты в цепочке обычно обрабатывают одно конкретное поведение. Поток между объектами очень похож на оператор switch-case.

Есть некоторые gotchas, например, это расширяет вашу логику, чрезмерно длинная цепочка может вызвать проблемы с производительностью. Но наряду с этими ошибками вы получаете повышенную тестируемость и более сильное сцепление. Также вы не ограничены использованием выражений enum, byte, int short и char в качестве триггера для ветвления.

Ответ 7

Проверьте блокировки, как они были реализованы в библиотеке lambdaj. Они действительно имеют поведение, очень похожее на делегатов С#:

http://code.google.com/p/lambdaj/wiki/Closures

Ответ 8

Вы можете использовать статические методы. Этот метод позволяет также указать параметры. Объявите свой интерфейс...

public interface RouteHandler {
    void handleRequest(HttpExchange t) throws IOException;
}

И ваша карта...

private Map<String, RouteHandler> routes = new HashMap<>();

Затем реализуйте статические методы, соответствующие интерфейсу/параметрам...

public static void notFound(HttpExchange t) throws IOException {
    String response = "Not Found";

    t.sendResponseHeaders(404, response.length());
    OutputStream os = t.getResponseBody();
    os.write(response.getBytes());
    os.close();
}

Затем вы можете добавить эти методы в свою карту...

routes.put("/foo", CoreRoutes::notFound);

и назовите их следующим образом:

RouteHandler handler = routes.get("/foo");
handler.handleRequest(exchange);

Ответ 9

Другим подобным подходом может быть использование поставщиков Java 8:

Map<Integer, Supplier<T> suppliers = new HashMap();
suppliers.put(1, () -> methodOne());
suppliers.put(2, () -> methodTwo());

// ...

public T methodOne() { ... }
public T methodTwo() { ... }

// ...

T obj = suppliers.get(id).run();