Search code examples
javatypechecking

Efficiently coding the management of parameters with variable types in Java


I am currently implementing a simulation in Java that requires an input of about 30 different parameters. Eventually, I want to be able to read these parameters from a file and also from a GUI, but I am just focusing on the file input for now. My simulation requires parameters that are of different types: Strings, ints and doubles and I currently have them as fields for the simulation e.g.

private String simName;
private int initialPopulationSize;
private double replacementRate;

Because these parameters are not all the same type I can't store them in an array and I have to read each one in separately using the same kind of code about 30 times. An example for three parameters:

//scanner set up and reading each line, looking for "(key)=(param)" regex matches
//if statement to check each param name against the key matched in file. Store param in that field if the name matches.
String key = m.group(1);
if (key.equals(PKEY_SIM_NAME)) {
    if (simNameSet) {
        throw new IllegalStateException("multiple values for simulation name");
    }
    this.simName = m.group(2);
    simNameSet = true;

} else if (key.equals(PKEY_INITIAL_SIZE)) {
    if (initialSizeSet) {
        throw new IllegalStateException("multiple values for initial population size");
    }
    this.initialPopulationSize = Integer.parseInt(m.group(2));
    initialPopulationSize = true;

 } else if (key.equals(PKEY_MUT_REPLACEMENT)) {
    if (replacementRateSet) {
        throw new IllegalStateException("multiple values for replacement rate");
    }
    this.replacementRate = Double.parseDouble(m.group(2));
    replacementRateSet = true;
 }
    //Add nauseum for each parameter.....

So I currently have a long and unweildly method for reading in parameters, and I will likely need to do the same again for reading from gui.

The best alternative I have thought of is to read everything into String fields first. That way I can write a simple few lines for reading in using a Map. Something like this (untested code):

//This time with a paramMap<String, String>, scanner set up as before
if (!paramMap.containsKey(key)) {
    paramMap.put(key, m.group(2));
}
else{
    throw new IllegalStateException("multiple values for initial population size");
}

However, this will be inconvenient when it comes to using these parameters from the Map, since I will have to cast the non-String params whenever and wherever I want to use them.

At this point I feel like this is my best approach. I want to know if anyone a little more experienced can come up with a better strategy for dealing with this kind of situation before I move on.


Solution

  • You can define a base Parameter class or interface like:

    interface Parameter {
        void parse(String s);
        Object getValue();
        ...
    }
    

    and a class for each type of parameter you want to have, e.g. IntParameter, DoubleParameter, StringParameter. Here's a sketch of an IntParameter:

    class IntParameter implements Parameter {
        private int value;
    
        public void parse(String s) {
            value = Integer.parseInt(s);
        }
    
        public Object getValue() {
            return value;
        }
        ...
    }
    

    Then you can store your parameters in a Map<String, Parameter> and populate from various sources, like command-line options or properties.


    A more type-safe but convoluted solution can be achieved if you don't store the value in parameter objects, but make parameters static objects that are used to access their values. This is illustrated on the following example:

    abstract class Parameter {
        private String name;
    
        public Parameter(String name) {
            this.name = name;
        }
    
        public abstract Object parse(String s);
    }
    
    class IntParameter extends Parameter {
        public static final IntParameter ANSWER = new IntParameter("answer");
        // Add more options here.
    
        public IntParameter(String name) { super(name); }
    
        public Object parse(String s) {
            return Integer.parseInt(s);
        }
    }
    
    class Parameters {
        private Map<Parameter, Object> params = new HashMap<Parameter, Object>();
    
        public int get(IntParameter p) {
            return (Integer)params.get(p);
        }
    
        public void put(IntParameter p, int value) {
            params.put(p, value);
        }
    
        public void putString(Parameter p, String value) {
            params.put(p, p.parse(value));
        }
    }
    

    This allows you to access parameter in a type safe manner:

    Parameters params = new Parameters();
    params.putString(IntParameter.ANSWER, "42"); // parse and store the value
    int value = p.get(IntParameter.ANSWER);
    

    The solution can be extended to other types.