Antes de explicar, vale notar que se for assim, funciona:
Integer[] numeros = {1, 2, 3};
Object[] objects = numeros;
Integer[] outrosNumeros = (Integer[]) objects;
System.out.println(Arrays.toString(outrosNumeros)); // [1, 2, 3]
O que de fato faz sentido: pode ser feito um cast de um array de Object para um array de Integer, já que um Object também pode ser casteado para um Integer. O código compila e roda sem problemas.
Mas por que isso não funciona?
List teste = new ArrayList();
teste.add(0);
teste.add(0);
Object[] objectTest = teste.toArray();
// ERRO: ClassCastException
Integer[] newStringArray = (Integer[]) objectTest;
Teoricamente, é a mesma coisa, certo? A princípio parece que sim, mas se olharmos por debaixo dos panos descobriremos a causa.
Um array guarda a informação do seu tipo, que você pode ver imprimindo o próprio array:
Integer[] numeros = {1, 2, 3};
System.out.println(numeros);
Object[] objects = numeros;
System.out.println(objects);
Integer[] outrosNumeros = (Integer[]) objects;
System.out.println(outrosNumeros);
A saída é:
[Ljava.lang.Integer;@6bc7c054
[Ljava.lang.Integer;@6bc7c054
[Ljava.lang.Integer;@6bc7c054
No caso, o [ indica que é um array, o L indica que é uma classe/interface e em seguida vem o nome da classe - este formato está descrito em mais detalhes aqui (o restante depois da @ é o hashcode do objeto, que não é relevante para esta explicação - só vale notar que é o mesmo em todas as linhas, afinal, trata-se do mesmo array).
Repare que mesmo que o array esteja guardado em uma variável do tipo Object[], ainda sim em runtime ele guarda a informação do tipo original dos seus elementos, que é Integer. Por isso ao fazer o cast de volta para Integer[] não dá erro ao executar o código.
Mas quando obtemos o array de um ArrayList, veja o que ocorre:
List teste = new ArrayList();
teste.add(0);
teste.add(0);
Object[] objectTest = teste.toArray();
System.out.println(objectTest);
Este código imprime:
[Ljava.lang.Object;@232204a1
Ou seja, o array retornado guarda a informação de que o tipo dos seus elementos é Object. Por isso ao fazer cast para Integer[] dá um ClassCastException, pois o tipo que está associado ao array é Object, e não Integer.
Agora se eu fizesse assim:
Object[] objectTest = teste.toArray(new Integer[0]);
System.out.println(objectTest);
Aí imprime [Ljava.lang.Integer;@232204a1, ou seja, o array passa a ter o tipo correto e tudo funciona.