Wednesday 14 July 2010

Creating methods with named parameters and default values in Java

Have you ever had a situation where one of your methods had many different arguments, and it became difficult to keep track of them, or to provide multiple versions of the same method with different parameters, trying to cover all commoon combinations?

In such cases you might have envied languages like perl or python which allow you to specify default values for your parameters, and let the caller specify parameters by name, thus providing parameters in any order.

Now, technically Java can't do that kind of stuff, but with some imagination, the varargs parameter type, and the new import static, we can come very close.

Goal

Our goal is to create a single method that accepts all the following calls.
go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));
All the bits below fit within one class. The actual names of the enum, methods etc. are up to you.

Step 1

Create an enum for all the parameters that your method will accept. Give the enum an instance variable for the (optional) default value, and set it in the constructor.
static enum OptionName {
  min (0),
  max,
  prompt;

  private final Object dflt;
  private OptionName(Object dflt) {
    this.dflt = dflt;
  }
  private OptionName() {
    this.dflt = null;
  }
}

Step 2

Create a simple class that contains one enum and one object. Give it a constructor to set these two values.
public static class Option {
  private final OptionName name;
  private final Object value;
  private Option(OptionName name, Object value){
    this.name = name;
    this.value = value;
  }
}

Step 3

Create a set of static functions, one for each OptionName, each returning an instance of Option.
  public static Option min(int value) {
    return new Option(OptionName.min, value);
  }
  public static Option max(int value) {
    return new Option(OptionName.max, value);
  }
  public static Option prompt(String value) {
    return new Option(OptionName.prompt, value);
  }

Step 4

Create the method. The parameters should be a varargs array of Options. Immediately place the parameters into a Map. The following example first sets all the default values, then overwrites them with the passed-in values. Once that's done, you'll have a map keyed by the OptionName enum, which you can query for the values you'll actually use in your method.
public static void myMethod(Option... opts) {
  EnumMap om = new EnumMap(OptionName.class);
  // first set the defaults
  for ( OptionName on : OptionName.values() ) {
    om.put(on, on.dflt);
  }
  // then overwrite them with the values passed in.
  for ( Option op : opts ) {
    om.put(op.name, op.value);
  }
        
  Integer min=(Integer)om.get(OptionName.min);
  Integer max=(Integer)om.get(OptionName.max);
  String prompt = (String)om.get(OptionName.prompt);       
  // do something with min, max and prompt; remember 
  // to check for nulls.
}

Using it

In the calling class, use the "import static" syntax to import all the static methods we defined in this class.

You can then call myMethod using a comma-separated list of method class to those static methods that will serve as parameters.