简体   繁体   中英

How to write a Java constructor for variable number of arguments of different types?

I have to write a constructor for a class Stamp. The constructor is supposed to take up to five arguments of types String, int, and double. So, the constructor would look like this:

public Stamp(String code, int year, String country, double value, int numberOfCopies){...}

The problem is, as the object of the class Stamp is being created, not all the parameters may be provided, ie, an object can be declared as

Stamp stamp = new Stamp("some_code", 1991, "Burkina Faso", 50, 1000);

as well as

Stamp stamp = new Stamp("some_code", 1991, "Burkina Faso");

and the constructor has to work in both cases, even if the list of arguments is truncated (in the latter case, some default values are assigned to value and numberOfCopies ). Of course, I can just write six constructors (for the possible number of parameters from 0 to 5, assuming that the parameters always follow the order described above, without mixing up), but there should be a smarter way. I know I can probably declare the constructor like

public Stamp(Object[] params){...}

and then cast the elements of params into corresponding classes. This will probably work but I will always have to check how many parameters are provided for the constructor using "if" conditions in order to decide whether to assign default values to variables if the corresponding parameters are not provided or use the provided values if those are given. This all seems pretty ugly. Thus, the question is brief: what is the way to build a constructor (or some other method) if the list of provided parameters varies in length and the types of parameters are different?

This may be a suitable use for a builder pattern

public class Stamp {

  public static class Builder {
    // default values
    private String code = "default code"
    private int year = 1900;
    // etc.

    public Builder withCode(String code) {
      this.code = code;
      return this;
    }
    public Builder withYear(int year) {
      this.year = year;
      return this;
    }
    // etc.

    public Stamp build() {
      return new Stamp(code, year, country, value, numberOfCopies);
    }
  }

  public Stamp(String code, int year, String country, double value,
      int numberOfCopies){...}
}

The construction process then becomes

Stamp s = new Stamp.Builder()
                .withCode("some_code")
                .withYear(1991)
              .build();

This way the arguments are no longer order-dependent - you could equivalently say

Stamp s = new Stamp.Builder()
                .withYear(1991)
                .withCode("some_code")
              .build();

Instead of messing up with Object[] go with constructor reuse.

Provide all possible constructors and use them internally

Just an ex;

public Stamp(String code, int year)
{
    this(code, "", year,0,0); //calling your main constructor with missed values.
}

No other work around as of now.

If the items themselves being passed didn't matter, you could use varargs:

public Stamp(Object... obj) {

}

However, given your example, it sounds like you want to add constructor overloading. Since all the data seems to have meaning and represent a specific thing, I would simply overload like so:

public Stamp(String code, int year) {
    this(code, year, "Burkina Faso");
}

public Stamp(String code, int year, String location) {
    //...
}

Indeed, as others stated, a possible solution would be to reuse constructors using this() calls.

If the order was variable and you wouldn't be able to tell which one is given and which one is not, then the Object[] would be insufficient. You would need a Map<String, Object> , and you would need to write explicit conversion for them. There might be a better way of doing this with Reflection too, but I haven't got that completely figured out yet.

So the "simple" approach would be that instead of

public Stamp(String code, int year, String country, double value, int numberOfCopies){...}

You would have

public enum StampProperties
{
    CODE("code"),
    YEAR("year"),
    COUNTRY("country"),
    VALUE("value"),
    NUMBER_OF_COPIES("numberOfCopies");

    private String identifier;

    private StampProperties(String identifier)
    {
        this.identifier = identifier;
    }

    public boolean c(String id)
    {
        return identifier.equals(id);
    }
}

public Stamp(Map<String, Object> params)
{
    for(String string : params.keySet())
    {
        mapProperty(params, string);
    }
}

private void mapProperty(Map<String, Object> params, String identifier)
{
    Object object = params.get(identifier);
    if(StampProperties.CODE.c(identifier))
    {
         this.code = (String) object;
    }
    else if(StampProperties.YEAR.c(identifier))
    {
         this.year = ((Integer) object).intValue();
    }
    else if...
}

But I really think the other solutions are a bit better, because they take less code, are inherently type-safe (less likely to mess up), and honestly, I'd go for the builder pattern stated by Ian Roberts , I really like that answer.

Use method overloading,java support this. Means you can write same method in same class with different parameters,just use copy paste and make multiple methods.:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM