Home > OS >  How to call the base constructor of an object from overloaded varargs constructor?
How to call the base constructor of an object from overloaded varargs constructor?

Time:01-19

I have a simple class with a constructor of all available fields and i want to add an overloaded constructor with varargs to allow the creating of objects even if some fields are unknown.

class FooClass {
    int id;
    String first;
    String second;
    String third;

    FooClass(final int id, final String first, final String second, final String third) {
        this.id = id;
        this.first = first;
        this.second = second;
        this.third = third;
    }

    FooClass(final int id, final String... myStrings){
        if(myStrings.length == 1) {
            this.id     = id;
            this.first  = myStrings[0];
        } else if (myStrings.length == 2) {
            this.id     = id;
            this.first  = myStrings[0];
            this.second = myStrings[1];
        } else {
            this.id     = id;
            this.first  = myStrings[0];
            this.second = myStrings[1];
            this.third  = myStrings[2];
        }
    }
}

I wanted to refactor the second constructor to avoid repeating this.id, this.first ... as follows, but I am getting a compile error

FooClass(final int id, final String... myStrings){
        if(myStrings.length == 1) {
            this(id, myStrings[0], null, null);
        } else if (myStrings.length == 2) {
            this(id, myStrings[0], myStrings[1], null);
        } else {
            this(id, myStrings[0], myStrings[1], myStrings[2]);
        }
    }

The error is:

Call to 'this()' must be first statement in constructor body

What is the correct way to call the base constructor from the varargs constructor with null values? Or a better way to implement what I described above?

CodePudding user response:

In Java, a call to this or super MUST be the first line of the constructor.

class FooClass {
    int id;
    String first;
    String second;
    String third;

    FooClass(final int id, final String first) {
        this(id, first, null);
    }
    
    FooClass(final int id, final String first, final String second) {
        this(id, first, second, null);
    }
    
    FooClass(final int id, final String first, final String second, final String third) {
        this.id = id;
        this.first = first;
        this.second = second;
        this.third = third;
    }
    
}

One approach to get similar functionality to varargs is to simply write multiple constructors that chain upwards with default values passed.

This could get cumbersome though if the number of parameters is high, but the example is only using 3 String values.

Here is what calling them would look like:

//Example calls
public static void main(String [] args) {
    FooClass foo1 = new FooClass(1, "foo1");
    FooClass foo2 = new FooClass(2, "foo1", "foo2");
    FooClass foo3 = new FooClass(3, "foo1", "foo2", "foo3");
}

CodePudding user response:

You can't. As the error says, this is available, but only if it is the first line (and therefore inherently you can only write one such call, total).

In theory you can do it, but whether it's clean - that's up for debate:

this(id, myStrings[0],
  myStrings.length > 1 ? myString[1] : null,
  myStrings.length > 2 ? myString[2] : null);

There is no general solution to it - you find a way to reduce the whole thing to a single this() invocation, utilizing only static methods and basic expressions, or, you can't do it. In this specific case there was a way to get there with basic expressions, but that option isn't always available.

CodePudding user response:

Obey the error message, by condensing the code into one line:

FooClass(final int id, final String... myStrings) {
    this(id, myStrings[0], myStrings.length == 1 ? null : myStrings[1], myStrings.length == 2 ? null : myStrings[2]);
}

The usual approach is a factory method, which works for arbitrarily complex code:

public static FooClass create(int id, String... myStrings) {
    if (myStrings.length == 1) {
        return FooClass(id, myStrings[0], null, null);
    } else if (myStrings.length == 2) {
        return FooClass(id, myStrings[0], myStrings[1], null);
    } else {
        return FooClass(id, myStrings[0], myStrings[1], myStrings[2]);
    }
}

CodePudding user response:

You can do it by writing static method to take care of extracting a value from the array.

private static String getValue(int index, String... strings) {
  if (strings == null || index >= strings.length) {
    return null;
  }
  return strings[index];
}

Then use this method with the all args constructor like this:

public class FooClass {

  //fields

  public FooClass(final int id, final String first, final String second, final String third) {
    //assign values
  }

  FooClass(final int id, final String... myStrings){
    this(id, getValue(0, myStrings), getValue(1, myStrings), getValue(2, myStrings));
  }

  private static String getValue(int index, String... strings) {
    ...
  }
}

It's debatable, if this is cleaner or not, i'd say it depends on the style adopted in the current project.

  • Related