Search code examples
javavariadic-functions

call empty varargs method, when it is overloaded with an empty method


Spring's UriComponentsBuilder has an empty build() method, as well as variadic build(Object... urivariables).

I want to call the second one with an empty array. Is there a better way to do so, then build(new Object[0])?

Just calling .build() goes for the first.


Solution

  • I want to call the second one with an empty array. Is there a better way to do so, than build(new Object[0])?

    More or less 'no'. - varargs is syntax sugar - bytecode wise there is absolutely no difference whatsoever between:

    void test(String... example) {}
    
    test(new String[1] {"Hello"});
    

    and

    void test(String... example) {}
    
    test("Hello");
    

    Both of those examples will allocate a new 1-sized string array, sets the first (and only) string slot in that array to refer to the string "Hello", and then calls the test method, passing a reference to the newly created array. It's just that in the second snippet, javac generates all that for you. (If in doubt, or to assuage curiosity, by all means play around with this and use javap -c -v to see the bytecode for yourself!)

    Hence, calling: build(new Object[0]) is bytecodewise identical to any hypothetical java construct that lets you explicitly pick the varargs overload of build() somehow (there is no such java construct. Point is, even if there was, it wouldn't matter).

    A sidestep into API design

    Given that java methods have their identities determined by both their name as well as their parameter types, but that most humans interacting with code consider only the name, it is really bad API design if confusion about which of a number of overloads is relevant. In other words, if you can call a non-obvious overload and the behaviour will be meaningfully different from other overloads it could feasibly be understood to call to your average java programmer, that is really bad API design.

    Take System.out.println for example. It has many overloads; String and Object are amongst them. Given that all strings are objects, it is entirely possible to call both overloads:

    String x = "Hello";
    System.out.println(x); // calls PrintStream.println(String)
    Object y = x;
    System.out.println(y); // calls PrintStream.println(Object)
    

    The above code calls 2 completely different methods, in both cases passing the exact same object.

    Fortunately, this is simply irrelevant, because these 2 different methods act identically when you do this. They both just print the string. Hence, which one of the two overloads is called whenever you see code that passes what you know to be a string object is an academic curiosity and nothing more.

    That aspect (between a bunch of overloads it doesn't matter which one you call when passing the same data to them, they all act identically) is important to good API design.

    Hence, generally, if there is both foo() as well as foo(T... something) in a given API, there is no difference between calling the no-args foo, and calling the varargs foo with an empty array (no actual arguments). If this API acts meaningfully differently, deal with it the same way you deal with any API that is horribly designed:

    • Consider using something else, or at least add 1 demerit to this library (move it up on the agenda for 'this should perhaps be replaced one day').
    • Grit your teeth and deal with it. Or...
    • Write a wrapper that undoes the mess.

    If this API does not act meaningfully differently, why do you care? Creating short-lived zero-length arrays has no measurable cost.

    One option available to you

    zero-length arrays are immutable. Their state cannot possibly be changed (arrays are fixed size and given that there are zero slots you can't 'set' anything on one of them). Hence, if you must, you can make a global constant. This isn't spaghettifying your code base (that only applies to globals that have non-constant state - hence, does not apply to zero-length arrays). There is no relevant performance benefit to doing this (yes, you avoid having to create zero-len arrays, but as mentioned before, small short-lived garbage isn't relevant, java GCs are designed to make that about as free as possible).

    Thus, care about this only if you feel it is 'cleaner'. That this:

    private static final Object[] NO_TEMPLATE_VALUES = new Object[0];
    
    uriComponentsBuilder.build(NO_TEMPLATE_VALUES);
    

    is more readable than:

    uriComponentsBuilder.build(new Object[0]);
    

    It probably is - generally if you're going out of your way to invoke a non-obvious overload, your code has become unreadable (after all, any reader will immediately wonder why in the blazes you are doing something so bizarre for no discernible purpose), and the code should therefore go out of its way to:

    • Clarify that is not some oversight - it is very much intentional.

    and preferably, also explain why you're doing it (explain in a comment what the difference is between build() and build(new Object[0]) and therefore why you've jumped through all these hoops).