Como criar uma matriz genérica?

Eu não entendo a conexão entre genéricos e matrizes.

Eu posso criar referência de matriz com tipo genérico:

private E[] elements; //GOOD 

Mas não é possível criar object de matriz com tipo genérico:

 elements = new E[10]; //ERROR 

Mas funciona:

 elements = (E[]) new Object[10]; //GOOD 

Você não deve misturar matrizes e genéricos. Eles não vão bem juntos. Existem diferenças em como as matrizes e os tipos genéricos impõem a verificação de tipo. Dizemos que os arrays são reificados, mas os genéricos não são. Como resultado disso, você vê essas diferenças trabalhando com matrizes e genéricos.

Arrays são covariantes, Generics não são:

O que isso significa? Você deve estar sabendo agora que a seguinte atribuição é válida:

 Object[] arr = new String[10]; 

Basicamente, um Object[] é um tipo super de String[] , porque Object é um super tipo de String . Isso não é verdade com genéricos. Portanto, a seguinte declaração não é válida e não compilará:

 List list = new ArrayList(); // Will not compile. 

Razão de ser, os genéricos são invariantes.

Verificação de tipo de imposição:

Os genéricos foram introduzidos em Java para impor uma verificação de tipo mais forte em tempo de compilation. Assim, os tipos genéricos não possuem informações de tipo em tempo de execução devido ao apagamento de tipos . Portanto, uma List tem um tipo estático de List mas um tipo dynamic de List .

No entanto, os arrays carregam com eles as informações de tipo de tempo de execução do tipo de componente. Em tempo de execução, as matrizes usam a verificação do Array Store para verificar se você está inserindo elementos compatíveis com o tipo de matriz real. Então, o seguinte código:

 Object[] arr = new String[10]; arr[0] = new Integer(10); 

irá compilar bem, mas irá falhar em tempo de execução, como resultado de ArrayStoreCheck. Com genéricos, isso não é possível, pois o compilador tentará impedir a exceção de tempo de execução, fornecendo verificação de tempo de compilation, evitando a criação de referência como essa, como mostrado acima.

Então, qual é o problema com a Generic Array Creation?

A criação da matriz cujo tipo de componente é um parâmetro de tipo , um tipo parametrizado concreto ou um tipo parametrizado curinga limitado , é insegura quanto ao tipo .

Considere o código como abaixo:

 public  T[] getArray(int size) { T[] arr = new T[size]; // Suppose this was allowed for the time being. return arr; } 

Como o tipo de T não é conhecido no tempo de execução, o array criado é na verdade um Object[] . Então, o método acima em tempo de execução será parecido com:

 public Object[] getArray(int size) { Object[] arr = new Object[size]; return arr; } 

Agora, suponha que você chame esse método como:

 Integer[] arr = getArray(10); 

Aqui está o problema. Você acabou de atribuir um Object[] a uma referência de Integer[] . O código acima irá compilar bem, mas falhará no tempo de execução.

É por isso que a criação de matriz genérica é proibida.

Por que typecasting new Object[10] para E[] funciona?

Agora sua última dúvida, porque o código abaixo funciona:

 E[] elements = (E[]) new Object[10]; 

O código acima tem as mesmas implicações explicadas acima. Se você perceber, o compilador estará fornecendo a você um Aviso de Carga Não Verificada , pois você está digitalizando para uma matriz de tipo de componente desconhecido. Isso significa que o casting pode falhar no tempo de execução. Por exemplo, se você tiver esse código no método acima:

 public  T[] getArray(int size) { T[] arr = (T[])new Object[size]; return arr; } 

e você chama invocá-lo assim:

 String[] arr = getArray(10); 

isso falhará no tempo de execução com um ClassCastException. Então, não dessa maneira não vai funcionar sempre.

Que tal criar uma matriz do tipo List[] ?

A questão é a mesma. Devido ao tipo de eliminação, uma List[] não é nada além de uma List[] . Então, se a criação de tais matrizes permitisse, vamos ver o que poderia acontecer:

 List[] strlistarr = new List[10]; // Won't compile. but just consider it Object[] objarr = strlistarr; // this will be fine objarr[0] = new ArrayList(); // This should fail but succeeds. 

Agora, o ArrayStoreCheck no caso acima terá sucesso em tempo de execução, embora isso deva ter lançado um ArrayStoreException. Isso porque ambos List[] e List[] são compilados para List[] no tempo de execução.

Então, podemos criar uma matriz de tipos parametrizados curinga ilimitados?

Sim. A razão é que uma List< ?> É um tipo reificável. E isso faz sentido, pois não há nenhum tipo associado a todos. Portanto, não há nada a perder como resultado do apagamento do tipo. Portanto, é perfeitamente seguro criar um array desse tipo.

 List< ?>[] listArr = new List< ?>[10]; listArr[0] = new ArrayList(); // Fine. listArr[1] = new ArrayList(); // Fine 

O caso acima é bom, porque List< ?> É o tipo super de toda a instanciação do tipo genérico List . Portanto, não emitirá uma ArrayStoreException no tempo de execução. O caso é o mesmo com matriz de tipos brutos. Como os tipos brutos também são tipos reutilizáveis, você pode criar um array List[] .

Então, você pode criar apenas uma matriz de tipos reificáveis, mas não tipos não reificáveis. Note que, em todos os casos acima, a declaração de array é boa, é a criação de array com new operador, o que dá problemas. Mas, não faz sentido declarar um array desses tipos de referência, já que eles não podem apontar para nada além de null ( Ignorando os tipos ilimitados ).

Existe alguma solução alternativa para E[] ?

Sim, você pode criar o array usando o método Array#newInstance() :

 public  E[] getArray(Class clazz, int size) { @SuppressWarnings("unchecked") E[] arr = (E[]) Array.newInstance(clazz, size); return arr; } 

O typecast é necessário porque esse método retorna um Object . Mas você pode ter certeza de que é um casting seguro. Então, você pode até usar @SuppressWarnings nessa variável.

O problema é que, enquanto o tipo genérico de tempo de execução é apagado , o new E[10] seria equivalente ao new Object[10] .

Isso seria perigoso porque seria possível colocar na matriz outros dados além do tipo E É por isso que você precisa dizer explicitamente o tipo que deseja

  • criando matriz de objects e lançá-lo para E[] array, ou
  • usando Array.newInstance (Classe componentType, int length) para criar uma instância real do array do tipo passado em componentType argiment.

Aqui está a implementação de LinkedList#toArray(T[]) :

 public  T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; for (Node x = first; x != null; x = x.next) result[i++] = x.item; if (a.length > size) a[size] = null; return a; } 

Em suma, você só pode criar matrizes genéricas por meio de Array.newInstance(Class, int) onde int é o tamanho da matriz.

verificado:

 public Constructor(Class c, int length) { elements = (E[]) Array.newInstance(c, length); } 

ou desmarcado:

 public Constructor(int s) { elements = new Object[s]; }