Function pointers/delegates in Java?
Asked Answered
D

10

21

For my Java game server I send the Action ID of the packet which basically tells the server what the packet is for. I want to map each Action ID (an integer) to a function. Is there a way of doing this without using a switch?

Dehydrogenase answered 28/12, 2008 at 4:22 Comment(3)
Overall, a switch is going to be better.Sociometry
Yeah, a switch is almost certainly going to be far faster, smaller, cleaner.Jolenejolenta
possible duplicate of What's the nearest substitute for a function pointer in Java?Casabonne
F
26

What about this one?

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();

(If you need to pass some arguments, define your own interface with a function having a suitable parameter, and use that instead of Runnable).

Fawcett answered 28/12, 2008 at 4:30 Comment(6)
I think a switch where functionA and functonB get called would be much clearer code. This just moves the mapping of IDs to functions into the section of code where you're loading the map, instead of where you're calling the function.Wynd
yes, i agree a switch would be cleaner. i would use a switch too. but i think you should tell it the original questioner (in the OP comments section) :)Fawcett
I put it in my answer instead.Wynd
The main reason not to use a switch is if the mapping is dynamic.Gullible
Jason, you can always have lists of handlers that are called that contain their switch. that's no less dynamic. i think it depends on the actual situation. is there much post processing? how many code can be shared? how much control should be retained into the main module?Fawcett
@litb: true. I'm just thinking of something like AddEventListener or something where you would register a function handler at runtime.Gullible
I
3

Another similar approach could be using Java 8's Suppliers:

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();
Insouciant answered 10/1, 2019 at 19:8 Comment(0)
K
3

You can use things in java.util.function (For Java 8+) to store a function. java.util.function docs

You have to go through this list and pick an appropriate function-class to use. I'll be using BiConsumer, which says:

Takes two inputs. Returns no result. Operates through side effects.

import java.util.function.BiConsumer;
import java.util.HashMap;
import java.util.Map;

public void init(){
  //<actionId, function<arg1, arg2>>
  Map<Integer, BiConsumer<String, String>> actionMap = new HashMap<>();
  actionMap.put(123, this::someMethod);

  //note (optional): check key first: map.containsKey(someActionId)
  //call someMethod("argument1", "argument2")
  actionMap.get(123).accept("argument1", "argument2");
}

public void someMethod(String a, String b){
  //do something here
}
Kenneth answered 22/6, 2020 at 14:1 Comment(0)
S
2

Java does not have first-class function pointers. In order to achieve similar functionality, you have to define and implement an interface. You can make it easier using anonymous inner classes, but it's still not very pretty. Here's an example:

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
Sometimes answered 28/12, 2008 at 4:30 Comment(1)
For more on "function pointers" in Java, see: https://mcmap.net/q/75829/-what-39-s-the-nearest-substitute-for-a-function-pointer-in-java/545127Casabonne
W
2

Java doesn't really have function pointers (we got anonymous inner classes instead). There's really nothing wrong with using a switch, though, as long as you're switching on value and not on type. Is there some reason you don't want to use a switch? It seems like you'll have to do a mapping between Action IDs and actions somewhere in your code, so why not keep it simple?

Wynd answered 28/12, 2008 at 4:34 Comment(4)
Yeah, my thought was the same. Myself I'd use the switch. I believe that behind the scenes switches are handled in a random-access manner rather than sequentially checking the cases like you'd have to do with if/else. Only thing is that the case indices should be consecutive I think...?Holcombe
They don't have to be consecutive. You can put the most likely cases first if you need to optimize. (Always measure before you optimize.)Wynd
A switch statement doesn't let classes in various parts of his code dynamically load callbacks. This prevents advanced features such as loading callbacks from configuration files at runtime and such. I'd recommend against switch statements in favor of a more powerful solution for this reason.Kodiak
A switch doesn't prevent that at all. You don't have to hard code the logic of the function in the body of each case. Just call the dynamically loaded functions.Wynd
I
1

Have you ever used Swing/AWT? Their Event hierarchy solves a similar problem. The way Java passes functions around is with an interface, for example

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

Then, if you want to map integers onto these objects, you could use something like a java.util.HashMap<Integer,ActionHandler> to manage that. The actual implementations can either go in anonymous classes (Java's best approximation of "lambda") or in proper classes somewhere. Here's the anonymous class way:

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) If you want to be even more abusive, you can initialize the HashMap like so:

HashMap<Integer,String> m = new HashMap<Integer,String>() {{
    put(0,"hello");
    put(1,"world");
}};
Intercellular answered 28/12, 2008 at 4:32 Comment(0)
P
1

Yeah, but using an interface mean you have to create an interface for each callback which means every function you want to pass it set. Creating a delegate class to handle this gives you (not a true function pointer) but the function to be passed and if you abuse a generic to be the return type, you don't have to cast which cuts the bottleneck down to almost nothing.

The c# delegate (MultiCastDelegate to be correct) gets the info from using method MethodInfo which is the same thing you would need to do for the Delegate class using java.lang.reflect.method. I posted my code for the Delegate(T) class on another form of this site dealing with this extact issue. I make it because (yes) being from C++ I need a better way for passing functions (especially Void) then having to create an interface for on function or more. Now I can choose the function filling in the parameter information for it. Voila`! Nice and usable with no noticeable lose in speed from JIT or JVM. And if I did having only learning java programming for only a week, any java programmer can do it.

Also, it serves very well when creating a base listener and a base interface to pass in the listener. No more having to write another listener because the name of the function has changed. Creating a delegate class has great advantages as is very useable and passable.

Prologize answered 2/3, 2011 at 7:5 Comment(0)
P
1

You could interface static methods. This method allows you to specify parameters too. Declare your interface...

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

And your map...

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

Then implement static methods that match the interface/params...

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();
}

You can then add those methods into your map...

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

and call them as follows...

RouteHandler handler = routes.get("/foo");
handler.handleRequest(exchange);
Plonk answered 13/10, 2017 at 8:40 Comment(0)
K
0

You can do this through the use of the chain of responsibility pattern.

It is a pattern that links different objects to together kind of like a linked list. i.e. Each object has a reference to next in the chain. The objects in the chain usually handles one specific behavior. The flow between the objects is very similar to the switch-case statement.

There are some gotchas, such as, it spreads your logic out, an excessively long chain can cause performance problems. But along with these gotchas you have the benefit of increased testability, and stronger cohesion. Also you are not limited to the using enum, byte, int short, and char expressions as the trigger for branching.

Knowable answered 28/12, 2008 at 5:14 Comment(4)
Sweet. I got voted down with no explanation. I guess the person didn't like the way I typed my response. Maybe what I said was wrong. My guess is the person has a problem with CoR. But I'm just guessing because there was no comment as to why.Knowable
What is this pattern? How does it solve/circumvent the problem of using a code to switch over multiple functions? An example, or at least a link, would have saved this answer. But yes, I didn't like the way you typed your response.Intercellular
Thank you. That is all I ask. If you have the guts to downvote someone have the guts to give a reason.Knowable
+1 to bring it back to zero. This isn't a BAD answer. It isn't the MOST helpful but it doesn't reduce the net knowledge in the universe. I mean, come on, Bill the Lizard is answering with the standard "your question is wrong. You should change it to match my answer."Gleich
S
0

Check the closures how they have been implemented in the lambdaj library. They actually have a behavior very similar to C# delegates:

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

Subalternate answered 12/9, 2009 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.