Function array in Java?

2.6k views Asked by At

Maybe I think to much in C, but I don't see a solution how to solve this properly in java. I get a response from my server which sends a string like this:

command params <xml...>

The client receives that string and extracts the command. Now I would like to call a function which knows how to handle the command. On the C side the solution is obvious. I implemented an array with the command name and associated function pointes, so I can simply loop through the array and call the function.

Is there some way to do this on Java as well? I don't know that I could call a function based on the name. So currently I see the following options:

  1. Do a series of if(command.euqals(COMMAND)
  2. For each command I could create a separate object which I can store in an array (very messy).
  3. Use reflection, so I can have a map with the function names vs. command names.

Are there any other options?

The if statements is not the best IMO, but at least it allows for compiler errors and typechecking. Using reflection is at least more elegant because I can loop and extend it more easily, but of course, it means that I can see only runtime errors if I misstype the names.

4

There are 4 answers

4
Jacob Raihle On BEST ANSWER

Your second idea is idiomatic. Use a Map<String, Runnable> to store the command names and corresponding code, then commands.get(commandName).run() to execute one.

Don't be afraid of creating classes! It may make your code start out more verbose, but it's much easier to write a class and never have to worry about it again than to do the same with a switch or if ... else if .... If your commands ever become more complicated than single methods (maybe toString(), undo()...), you'll be increasingly glad that you used polymorphism instead of conditionals.

1
Duffydake On

If you want to have only one file, you can make it with enum.

Try something like this :

    public class Command {

      public enum CommandName {

        UNDO,
        PRINT,
        RUN
      }

      public static void execute(String command, String[] params, String xml) {
        try {
          CommandName cname = CommandName.valueOf(command);

          switch (cname) {
            case UNDO:
               undo (params, xml);
              break;
            case PRINT:
              //
              break;
          }
        } catch (IllegalArgumentException iae) {
          // Unknown command
        }
      }

      public static void undo (String[] params, String xml) {
         // ....
      }
    }

Run the command :

    Command.execute(command, params, xml);
0
StuPointerException On

I've solved this type of problem in the past using the following approach, it might be suitable in your case:

Have a standard interface:

public interface Executable {
    public String getCommandName();
    public void execute(String[] params, String xml);
}

and x number of implementations:

public class SaveExecutable implements Executable {
    private static final String COMMAND_NAME = "SAVE";
    public String getCommandName() {
        return COMMAND_NAME;
    }

    public void execute(String[] params, String xml) {
         ...
    }
}

Then store the implementations in a HashMap for lookup:

HashMap<String, Executor> executors = new HashMap<>();
executors.put("SAVE", new SaveExecutable());

Then you can have a method to handle generic commands (validation and array trimming omitted):

public void handleCommand(String[] command) {
    executors.get(command[0]).execute(command);
}
0
Beezer On

I recently was shown a functional trick that simple though it is, is quite effective: "wrap" a function or set of functions with another function.

Below is an example of code I have written in order to show this trick. My example only serves out one function for one command, but it can easily be extended to a bunch of functions returned if needs be (hence the inclusion of the Guava Mutlimap type which can be used)...I return an Optional so when a command does not match, it "safely" returns a null; the Contact and ContacUnit are domain types which all extend an Organization type...this should then make the below code make sense.

package com.xxx.component;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.xxx.domain.Contact;
import com.xxx.domain.ContactUnit;
import com.xxx.domain.Organization;

import java.util.Optional;
import java.util.function.Function;

/**
 * This serves up the functions used for the domain
 * to validate itself.
 * Created by beezerbutt on 06/04/2017.
 */
public class MapSetDomainFunctionFactory {

    public static final Function<String, Optional<Organization>> toContactFromCwid = s-> Optional.ofNullable(s).map(Contact::new);
    public static final Function<String, Optional<Organization>> toContactUnitFromKey = s-> Optional.ofNullable(s).map(ContactUnit::new);

    public static final Function<String, Function<String, Optional<Organization>>> commandToFunctions = command -> {
        if (command.equalsIgnoreCase("toContactFromCwid")) {
            return MapSetDomainFunctionFactory.toContactFromCwid;
        } else {
            return null;
        }
    };
}

}

To make life easier, I am including the Domain class code:

/**
 * Created by beezerbutt on 06/04/2017.
 */
public class Contact implements Organization {
}
public class ContactUnit implements Organization {
}
public interface Organization {
}

Below is a snapshot of a Spock test I ran to prove that the code works: enter image description here