Porque eu não consigo convert um Object[] que era Integer[] para seu tipo original

Eu estou estudando para a prova do OCA e me deparei com uma situação no mínimo estranha, segue o código abaixo:

List test = new ArrayList();
teste.add(0);
teste.add(0);
Object[] objectTest = test.toArray();
Interger[] newStringArray = (Interger[])objectTest

Quando eu uso o List.toArray e não passo o tipo ele me devolve um array de Objetos, porém em teoria o cast da última linha deveria acontecer, pois a classe String ela é uma filha da classe Objeto. O mais estranho é que se eu fizer o cast de uma String para Objeto e depois retornar ela para String funciona perfeitamente.
Porém neste caso não funciona, se alguém souber me explicar o porque a linguagem não permite este casting eu agradeceria.

1 curtida

Bom, vc disse que se vc fizer casting de uma String para Object e, em seguida, fazer o casting para String novamente as coisas funcionam perfeitamente e isso está correto.

String a = "abc";
Object b = a;
String c = (String) b;
System.out.println(c);

// mesma coisa com Integer
Integer d = 123;
Object e = d;
Integer f = (Integer) e;
System.out.println(f);

Mas vc já viu o que acontece se vc fizer o casting de uma String[] para Object[] e, em seguida fizer o casting para String[] novamente? O que acontece é que as coisas também funcionam perfeitamente.

String[] a = new String[] { "a", "b", "c" };
Object[] b = a;
String[] c = (String[]) b;
System.out.println(Arrays.toString(c));

// mesma coisa com Integer[]
Integer[] d = new Integer[] { 1, 2, 3 };
Object[] e = d;
Integer[] f = (Integer[]) e;
System.out.println(Arrays.toString(f));

Agora experimenta fazer como no código abaixo e verá uma ClassCastException.

Object a = new Object();
String b = (String) a;
System.out.println(b);

O mesmo ocorre com arrays.

Object[] a = new Object[] { "a", "b", "c" };
Object[] b = a;
String[] c = (String[]) b;
System.out.println(Arrays.toString(c));

Com estes exemplos eu quero que vc perceba que uma coisa é vc ter uma instância de String ou String[] e fazer castings indo para o tipo de uma superclasse e voltando para o tipo original. Outra coisa bem diferente é vc ter uma instância de Object ou Object[] e tentar fazer casting para o tipo de uma subclasse.

Tenha em mente que o array retornado pelo método toArray() é uma instância de Object[], pois é num Object[] que o ArrayList armazena seus elementos internamente.

Vc pode ver isso direto no código fonte. O método toArray()

… invoca o método copyOf() da classe Arrays que está aqui:

… e que, por sua vez, chama a versão de copyOf abaixo:

… que chama o newInstance() da classe Array e termina invocando o método nativo newArray().


Uma coisa interessante a se notar é que, embora vc não possa fazer casting de Object[] para Integer[], nada te impede de fazer casting do elementos do array.

Object[] a = new Object[] { 1, 2, 3 };
Integer[] b = new Integer[a.length];

for (int i = 0; i < a.length; i++) {
  b[i] = (Integer) a[i];
}

System.out.println(Arrays.toString(b));

Ou invocar vc mesmo o método Arrays#copyOf().

Object[] a = new Object[] { 1, 2, 3 };
Integer[] b = Arrays.copyOf(a, a.length, Integer[].class);
System.out.println(Arrays.toString(b));

Percebeu como faz sim sentido que seu exemplo gere erro?

3 curtidas

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.

1 curtida

Certo entendi a sua linha de raciocínio sobre isso, porém eu fui fazer um teste aqui e acaba deixando mais intrigado ainda, pois se você usar o Arrays.asList() no lugar do new ArrayList() o código compila sem erro nenhum erro.

Até faria sentido, se não acontecesse algo ainda mais bizarro que o que eu respondi aqui ao qual se você usa Arrays.asList() ele permite o casting.

Isso acontece porque Arrays.asList retorna uma lista cuja classe é diferente de java.util.ArrayList, veja:

List teste = Arrays.asList(1, 2, 3);
System.out.println(teste.getClass()); // class java.util.Arrays$ArrayList

Testei no JDK 1.8.0_111 e o retorno foi a classe java.util.Arrays$ArrayList (repare que não é o mesmo que java.util.ArrayList, essa é uma inner class que existe dentro da classe java.util.Arrays). Vale lembrar ainda que em outras versões do Java ela pode retornar uma classe diferente, afinal trata-se de um detalhe interno da implementação da linguagem.

Enfim, você pode ver o código desta inner class aqui, mas vendo por cima dá para perceber que o método toArray dela preserva o tipo original do array, conforme podemos comprovar neste teste:

List teste = Arrays.asList(1, 2, 3);
Object[] objectTest = teste.toArray();
System.out.println(objectTest); // [Ljava.lang.Integer;@232204a1

O tipo dos elementos do array é Integer, e como já vimos acima, neste caso o cast deste array de volta para Integer[] funciona sem problemas.

1 curtida

Vc pode me mostrar um exemplo de código? É que eu não entendi.

Ah, pela resposta do Hugo eu entendi.

É como ele disse, a class java.util.Arrays.ArrayList armazena os elementos internamente de forma diferente da classe java.util.ArrayList.

Na classe java.util.ArrayList ele cria um Object[] logo que a classe é contruída.

Já na java.util.Arrays.ArrayList ela simplemente guarda o array que vc passa pro método asList().