SwadhyAI Notes

All Java notes

Java Varargs

›Short versionOpen if you only have a minute.
  • A varargs parameter uses an ellipsis, must be last, and is the only one of its kind.
  • Inside the method the parameter is an array. Loose arguments are packed into an array at the call.
  • A call with no trailing values passes an empty array. A lone null passes a null array reference.
  • Fixed-arity overloads are chosen before a varargs overload when both could accept the call.
  • Each loose call can allocate. A public method that already speaks collections often should take the collection.

A total needs two numbers, then three, then a handful that arrived from a form. Forcing every caller to allocate an array makes the small calls noisy. Forcing a fixed list of overloads stops at whatever count you wrote by hand.

A parameter is the compromise. The caller may pass nothing, one value, or many. The method always sees an . Knowing that the array is real, and knowing when that array is null, is the part that keeps the feature from becoming a surprise.

What the method actually receives

int... numbers is a parameter of type int[] with permission for the caller to write the elements separately. sum(1, 2, 3) packs those ints into an array and passes the reference, by value, like any other array argument. The loop adds elements. It does not special-case the ellipsis.

java
static int sum(int... numbers) {
    int total = 0;
    for (int number : numbers) {
        total += number;
    }
    return total;
}

System.out.println(sum());                 // 0
System.out.println(sum(1, 2, 3));          // 6
System.out.println(sum(new int[] {4, 5})); // 9

sum() passes an . Length is zero, the loop does not run, and the total stays 0. sum(new int[] { 4, 5 }) already has an int[], so that array is passed as it is. The call does not wrap it in a second array. Inside sum, those two situations look the same: a non-null int[] whose length you can trust.

A fixed parameter may come first. printAll("names", "Asha", "Mira") binds the label, then packs the names. printAll("empty") packs nothing, and items.length is 0.

java
static void printAll(String label, String... items) {
    System.out.println(label + " count = " + items.length);
}

The shape the compiler accepts

The variable-arity parameter is last, because everything after a fixed parameter has to belong to it. A second ellipsis would leave the call with no rule for which value goes to which array. Both of these declarations are compile errors.

java
// static void bad(String... messages, String level) {}
// static void bad(int... ids, String... names) {}

A is the sharp edge. sum(null) passes null as the int[], because null is a reference and the parameter’s type is an array type. The for-each then throws NullPointerException. That call is not an empty total. For String..., a one-element array containing a null string is written so the argument’s type is the element type, not the array type: printAll("row", (String) null). The cast keeps the compiler from treating the null as the whole array.

Overloads and the hidden array

prefers a fixed-arity method when one applies. show(5) runs the int method. show(5, 6) and show() run the varargs method, because no fixed method has that arity. Adding both overloads is useful when the one-argument case should do something different. It is confusing when both print the same kind of result and the reader has to remember which body ran.

java
static void show(int value) {
    System.out.println("single");
}

static void show(int... values) {
    System.out.println("varargs");
}

show(5);     // single
show(5, 6);  // varargs
show();      // varargs

The pack step is an . sum(1, 2, 3) creates an array the method did not name. In a logging helper or a small max, that cost is the point of the syntax. On a hot path that already holds the values in a list, the varargs form copies them into an array on every call. Take the list, or take int[] directly, so the caller controls the allocation.

Varargs also blur a public contract. Named fields can be validated one by one. A trailing String... cannot say which position is a title and which is a tag. Use the ellipsis when the values are genuinely a sequence of the same kind. When they are a record with roles, give them names.

sum(int... numbers) is called as sum(null), not sum(). The body immediately evaluates numbers.length. What happens?

The call passes a null array reference, because a single null matches the array type. numbers.length then throws NullPointerException. sum() is the call that passes a zero-length array and returns 0. Those two calls are easy to confuse at the keyboard and different at run time.