Указатели / делегаты функций в Java? - PullRequest
12 голосов
/ 28 декабря 2008

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

Ответы [ 9 ]

21 голосов
/ 28 декабря 2008

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

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 голосов
/ 28 декабря 2008

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

1 голос
/ 13 октября 2017

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

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);
1 голос
/ 02 марта 2011

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

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

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

1 голос
/ 28 декабря 2008

Вы когда-нибудь пользовались 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);

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

HashMap<Integer,String> m = new HashMap<Integer,String>() {{
    put(0,"hello");
    put(1,"world");
}};
1 голос
/ 28 декабря 2008

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
0 голосов
/ 10 января 2019

Другим аналогичным подходом может быть использование поставщиков 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();
0 голосов
/ 12 сентября 2009

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

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

0 голосов
/ 28 декабря 2008

Вы можете сделать это с помощью схемы цепочки ответственности.

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

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

...