O vararg é, em grande parte, um truque de compilação.
Quando o compilador está gerando o bytecode, ele transforma isso aqui:
void metodo(int ... valores)
{
}
Nisso aqui:
void metodo(int [] valores)
{
}
E cria um flag nas informações de classe para dizer que o método em questão é um vararg.
Depois, na chamada do método, quando o compilador encontra isso aqui:
Ele automaticamente transforma para:
metodo(new int[] {1,2,3,4});
Dessa forma, o bytecode final pode ser facilmente compatibilizado para Java 4, que entende a segunda construção. Por isso, um método como:
Não é ambíguo. Já que um parâmetro como 2 ints é muito diferente de um array de ints. O compilador é esperto o suficiente para escolher o método mais próximo.