In addition to the other great answers here, there's a subtle runtime performance advantage (in addition to avoiding the array allocation), which is that the zero-arg and single-arg overloads return implementations that are optimized for representing empty and single-instances lists (respectively).
If we didn't have separate method overloads for these and only included a single varargs-based method, then that method would look something like this:
public static <E> ImmutableList<E> of(E... es) {
switch (es.length) {
case 0:
return emptyImmutableList();
case 1:
return singletonImmutableList(es[0]);
default:
return defaultImmutableList(es);
}
}
The performance of the switch case (or if-else checks) wouldn't be bad for most calls, but it's still unnecessary since can just have method overloads for each optimization, and the compiler always knows which overload to call. There's no burden placed on the client code, so it's an easy win.