Fluffy
Fluffy

Reputation: 28382

How should I implement a map of string to method in Java?

I have a list of XML tags and a method for each of them inside my class, getting that tag as an argument and doing its work. So all the methods get the same input and I want to loop through the list of tags, calling appropriate method each time.

In Python I've made it with a hash of strings (names of tags) to lambdas (self.methodName()) and I want to translate it to Java.

Now in Java, I can't (?) make such a hash and even can't use strings (tag names) in a switch statement (with each branch calling a certain method). The possibility of using 10 or so subsequent ifs seems horribly ugly and I'm looking for a better way to code that.

Upvotes: 2

Views: 262

Answers (6)

pgras
pgras

Reputation: 12780

Here is an example of the proposal of Bill K (if I understood it right)

public class Example {

    static interface TagHandler {
        void handle(String tag);
    }

    static final Map<String, Example.TagHandler> tagHandlers = new HashMap<String, Example.TagHandler>() {
        {
            put("tag_1", new Example.TagHandler() {
                public void handle(String tag) {
                    System.out.println("Handling tag_1: " + tag);
                }
            });

            put("tag_2", new Example.TagHandler() {
                public void handle(String tag) {
                    System.out.println("Handling tag_2: " + tag);
                }
            });
        }
    };

    public static void main(String[] args) {

        String[] tags = { "tag_1", "tag_2", "tag_1" };

        for (String tag : tags) {
            tagHandlers.get(tag).handle(tag);
        }
    }
}

Upvotes: 0

Stephen C
Stephen C

Reputation: 719269

What do people think of this?

public static enum Tags {
   TAG1, TAG2, TAG3
}

public class Stuff {
   ...
   switch (Tags.valueOf(str)) {
   case TAG1: handleTag1(); break;
   case TAG2: handleTag2(); break;
   case TAG3: handleTag3(); break;
   }
}

The upside is that this is concise and efficient (at least in this case). The downside is that it is not so good with mixed case tags and tags with Java non-identifier characters in them; e.g. "-". (You either have to abuse accepted Java style conventions for the enum member identifiers, or you have to add an explicit String-to-enum conversion method to the enum declaration.)

Using a switch statement for dispatching is evil in some peoples' book. But in this case, you need to compare what you are gaining with what you are loosing. And I'd be surprised if polymorphic dispatching would give a significant advantage over a switch statement in terms of extensibility and maintainability.

Upvotes: 1

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147164

An indirect answer: XML typically represents data, not instructions. So it is probably more useful to map parser handling onto fields. This is what JAXB does. I suggest using JAXB or similar.

Unless you have a huge amount to do, I would strongly advise against reflection in a statically typed language. A string of } else if (tag.equals("blah")) { (or with interning, } else if (tag == "blah") { isn't going to kill you. You can even map strings onto their enum namesakes, but that is a little reflectiony. Switch-on-string should be with us in JDK7.

Upvotes: -1

Bill K
Bill K

Reputation: 62789

Map string to a class instance by instantiating classes and saving them (probably in a hash). All the classes must implement the same interface of course.

You'll find that if you code this way a better structure starts to emerge from your code--for instance you might find that where before you might have used 2, 3 or 10 similar methods to do slightly different things, now the fact that you can pass data into your constructor allows you to do it all with one or two different classes instead.

This interface and the classes that implement it (for me at least) nearly always evolve into a full-featured set of classes that I needed all along but might not have recognized otherwise.

Somehow I never seem to regret writing code the "Hard" way, but nearly always regret when I choose the easier path.

Upvotes: 3

pstanton
pstanton

Reputation: 36670

you can invoke the method using reflection:

Class.getMethod

therefore you don't need a switch or a set of ifs.

Upvotes: 0

digiarnie
digiarnie

Reputation: 23415

I'd go with what Bill K suggested in regards to implementing the same interface. But if you have the issue of wanting to call methods with different names you could try using reflection and do something like this:

Method method = Foo.class.getDeclaredMethod("methodName", parametersTypes); // Get the method you want to call
Foo foo = new Foo();
method.invoke(foo, args); // invoke the method retrieved on the object 'foo' with the given arguments

Upvotes: 1

Related Questions