问题
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?
回答1:
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));
回答2:
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.
回答3:
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");
}
回答4:
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.
回答5:
public MyClass(boolean ignore, Integer... numbers) {
do_something_with(numbers[]);
}
回答6:
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?!?");
}
回答7:
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.
来源:https://stackoverflow.com/questions/12751746/requiring-at-least-one-element-in-java-variable-argument-list