Search code examples
javareflectionjavafxvariadic-functionsdefault-constructor

Varargs-Constructor cannot act as default constructor using reflection


FXML-View:

....
    <GridPane...>
        <PersonController... />
    </GridPane...>
....

Class PersonController:

public PersonController(Person... personsToExclude) {
    FXMLLoader.load("PersonControllerView.fxml")
    this.personsToExclude = personsToExclude;
}

This code sample leads to an excpetion, because the class cannot be invoked without a default constructor (by the FXMLLoader). Now my question: You CAN use this constructor as default constructor. You can create such an object like this:

PersonConstructor pc = new PersonConstructor(); // This calls the upper constructor

Why isn't reflection able to use this constructor too? Varargs appear to be arrays internally which will be null by default if no parameter was handed over.

Was this design decision solely made to reduce complexity (it actually does reduce it a little bit for the reader) or are there any other reasons why it is important to still have a "real" default constructor?


Solution

  • If Oliver Charlesworth is correct, your question is really:

    Why doesn't Class#newInstance() work when the class doesn't have a zero-args constructor but does have a constructor accepting a single varargs argument?

    If so, I don't think we can properly answer it unless there's a quote from the design process around adding varargs to Java.

    We can speculate. My speculation is: Simplicity

    1. newInstance() is older than varargs. Until varargs were added to the language, there was no ambiguity: It could only call the nullary (zero-arguments) constructor. So they may have felt that extending it to handle constructors accepting one varargs argument was code bloat and/or scope creep for the method. After all, if you really want to do that, you can look up the relevant constructor and call that instead.

    2. Alternately, they may have felt that newInstance's documentation ruled out calling such a constructor. Let's look at the JavaDoc for newInstance():

      Creates a new instance of the class represented by this Class object. The class is instantiated as if by a new expression with an empty argument list. The class is initialized if it has not already been initialized.

      Note that this method propagates any exception thrown by the nullary constructor, including a checked exception...

      The first paragraph supports the idea that it could call a constructor with just one varargs argument. The second paragraph, though, mentions a "nullary" constructor specifically (though in passing). A "nullary" constructor is a zero-argument constructor, specifically, not just a constructor that can be called with no arguments.

    3. Doing so would markedly complicate newInstance(), because rather than just looking for a constructor that accepts no arguments, it would need to look through all the constructors for one that accepts a single argument where isVarArgs is true.

    4. Changing newInstance() to do what you're suggesting adds a new error mode: It's perfectly possible for your PersonConstructor class to have more than one constructor that can be called via new PersonConstructor():

      public class PersonConstructor
      {
          public PersonConstructor(String... args) {
          }
      
          public PersonConstructor(Person... args) {
          }
      }
      

      So which one should newInstance call? It can't decide, so it would have to throw, throwing a new error it hadn't thrown prior to the addition of varargs to the language.

    All in all, if I were on the team making the decision, I would also have had newInstance() only consider true nullary constructors and not constructors accepting a single varargs argument. (I would also have updated the JavaDoc to say that. :-) )