28

In this code construct:

public MyClass(Integer... numbers) {
    do_something_with(numbers[]);
}

is it possible to require that numbers contains at least one entry in such a way, that this is checked at compile-time? (At run-time, of course, I can just check numbers.length.)

Clearly I could do this:

public MyClass(Integer number, Integer... more_numbers) {
    do_something_with(number, more_numbers[]);
}

but this isn't going to be very elegant.

The reason I would like to do this is to make sure that a sub-class does not simply forget to call this constructor at all, which will default to a call to super() with no numbers in the list. In this case, I would rather like to get the familiar error message: Implicit super constructor is undefined. Must explicitly invoke another constructor.

Could there be another way to achieve the same, like some @-annotation that marks this constructor as non-implicit?

Markus A.
  • 12,349
  • 8
  • 52
  • 116
  • 4
    No you cannot do that.. A var-arg is nothing but a different representation of `array`.. So, just as you cannot make your array to take at least one element.. Same applies to var-args.. – Rohit Jain Oct 05 '12 at 18:10
  • 2
    RuntimeException, because not all programming errors can be found at compile time. – Tim Bender Oct 05 '12 at 18:13
  • That's a language limitation. You cannot do that... – caarlos0 Oct 05 '12 at 18:44
  • 1
    How about the @-annotation (last sentence)? Is there such a thing? – Markus A. Oct 05 '12 at 20:33
  • i like ``MyClass(Integer number, Integer... more_numbers)``, just I make (in very short) ``do_something_with(number + more_numbers)`` and make do_something_with private – Alexo Po. Jan 18 '22 at 12:22

7 Answers7

25

I think the best approach to have at least 1 argument is to add one like this:

public MyClass (int num, int... nums) {
    //Handle num and nums separately
    int total = num;
    for(i=0;i<nums.length;i++) {
        total += nums[i];
    }
    //...
}

Adding an argument of the same type along with varargs will force the constructor to require it (at least one argument). You then just need to handle your first argument separately.

Ivan V
  • 3,024
  • 4
  • 26
  • 36
11

I suppose one incredibly hacky way to do this is to create a no-args method and mark it as deprecated. Then compile with these two flags: -Xlint:deprecation -Werror. This will cause any use of a deprecated method to be a compile time error.

edit (a long time after the initial answer):

A less hacky solution would be to ditch the MyClass(Integer... numbers) constructor and replace it with MyClass(Integer[] numbers) (and add a private no-args constructor). It stops the compiler from being able to implicitly use the super class constructor, but without any args, and gives you a compile time error message.

./some_package/Child.java:7: error: constructor Parent in class Parent cannot be applied to given types;
    public Child(Integer[] args) {
                                 ^
  required: Integer[]
  found: no arguments
  reason: actual and formal argument lists differ in length

The cost is your calling syntax will become a bit more verbose:

new Child(new Integer[] {1, 2, 3});

You can of course write a helper functions to help with this eg.

public static Child newInstance(Integer... numbers) {
    return new Child(numbers);
}

@SafeVarargs
public static <T> T[] array(T... items) {
    return items;
}

and then:

Child c0 = Child.newInstance(1, 2, 3);
Child c1 = new Child(array(1, 2, 3));
Dunes
  • 37,291
  • 7
  • 81
  • 97
  • That is a very creative idea! I love it! Unfortunately there's two problems with it: If you don't explicitly call super(), Eclipse has nowhere to show you the deprecation warning, so unfortunately it doesn't get me the immediate red squares and squiggles I was hoping for. And, worse, I do use a couple of deprecated methods in other spots of my code, which I can't have error out on me. (Before I get a beating for using deprecated methods: These are the methods on Date, since GWT doesn't support Calendar.) – Markus A. Oct 05 '12 at 23:45
  • I'll set this as the answer. It's super-hacky and unfortunately not usable for me, but it's the truest answer to my question, i.e. catching at compile-time whether the call to super had no arguments. Also, thinking outside the box should be rewarded! ;) – Markus A. Oct 10 '12 at 03:46
  • The Date API deprecations are deprecated for good reason, in that some cases there produce erroneous output. Consider `Date` as a simple wrapper for a long and `Calendar` as way of adding times in units other than milliseconds together. Once you have the date you want in the calendar you can ask it to create a date that represents that time. Would definitely recommend re-factoring your code. – Dunes Oct 10 '12 at 10:15
  • That was an awesome answer.. Out of box thinking – Abdul Rahman K Nov 26 '15 at 13:41
  • "Out of the box" and out of the norm. Ivan V's solution below is typical. – Igor Urisman Oct 17 '18 at 23:38
8

The unique way to validate is verifies the params.

Validate the arguments :

if (numbers == null || numbers.length == 0 ) {
            throw new IllegalArgumentException("Your angry message comes here");
        }
thiago.lenz
  • 399
  • 4
  • 11
  • 3
    That's exactly the run-time check I wanted to avoid ;) – Markus A. Oct 05 '12 at 18:38
  • @Markus, neither can the following call be avoided at compile time: `MyClass m = new MyClass(null,null,null,null)` unless you change `Integer... numbers` into `int... numbers`. So I think throwing the IllegalArgumentException is elegant enough. – Tulains Córdova Oct 05 '12 at 18:45
  • I'm actually OK with nulls, and my real code doesn't use Integer either, it's just a simplified example. I only need to draw attention to the fact that there is a constructor that needs to be called. It's way too easy to just forget it in your code and there are some failure modes that can result from this that would be extremely hard to track down with a RuntimeException. – Markus A. Oct 05 '12 at 23:49
2

As stated in the comments, no, it doesn't seem possible to force a var arg to be of at least size 1.

The only compile time fix that I can think of is to simply require an array (Integer[]) as the argument to the constructor. Subclasses could still take a var arg in their constructor and any other users of the class would simply have to create an array from the desired arguments before calling the constructor.

Tim Bender
  • 20,112
  • 2
  • 49
  • 58
1
public MyClass(boolean ignore, Integer... numbers) {
    do_something_with(numbers[]);
}
irreputable
  • 44,725
  • 9
  • 65
  • 93
  • This could work. Not the cleanest, but definitely acceptable. Maybe I'd do Void ignore instead. If only I had an actual other parameter that I needed... :) – Markus A. Oct 05 '12 at 23:51
0

A really hacky way you can do that is to make a private version of that method with no parameters. That would at least prevent anyone outside this class from passing in one parameter at compile-time, but it won't provide a useful error message. But if it is super important at least one value is passed in, that will make it so.

private MyClass() {
    // This exception will be thrown only if you manage to do a "new MyClass()"
    // within your own class since it is private.
    throw new RuntimeException("Why did you do this?!?"); 
}
kurtzbot
  • 512
  • 6
  • 19
  • I've thought of this, but unfortunately it doesn't work... It will still call the other constructor with an empty list, since this one isn't visible. The only thing this prevents (with, again, a RuntimeException) is the class to call itself with no arguments. – Markus A. Oct 05 '12 at 18:51
  • Really? That is interesting. The default should be to ignore the `...` function if a more specific function is created. I find it strange that Java would try to differentiate based on whether it is public or private. I feel like that shouldn't happen, but I didn't make Java... – kurtzbot Oct 05 '12 at 18:52
0

I think it's not in the ̶f̶u̶n̶c̶t̶i̶o̶n̶ class itself, but when you call the ̶f̶u̶n̶c̶t̶i̶o̶n̶ class. Make sure your array has elements before calling the ̶f̶u̶n̶c̶t̶i̶o̶n̶ class with it.

Jordan
  • 1,969
  • 2
  • 18
  • 19
  • This could make sense at run time, but the question asks whether is possible to check the array numbers contains at least one element *at compile time* – Diego Pino Oct 05 '12 at 20:23