Creazione di un array generico in Java

1. Introduzione

Potremmo voler utilizzare gli array come parte di classi o funzioni che supportano i generici. A causa del modo in cui Java gestisce i generici, questo può essere difficile.

In questo tutorial, capiremo le sfide dell'uso dei generici con gli array. Quindi, creeremo un esempio di un array generico.

Vedremo anche dove l'API Java ha risolto un problema simile.

2. Considerazioni sull'utilizzo di array generici

Una differenza importante tra array e generics è il modo in cui applicano il controllo del tipo. In particolare, gli array memorizzano e controllano le informazioni sul tipo in fase di esecuzione. I generici, tuttavia, verificano la presenza di errori di tipo in fase di compilazione e non dispongono di informazioni sul tipo in fase di esecuzione.

La sintassi di Java suggerisce che potremmo essere in grado di creare un nuovo array generico:

T[] elements = new T[size];

Ma, se lo provassimo, otterremmo un errore di compilazione.

Per capire perché, consideriamo quanto segue:

public  T[] getArray(int size) { T[] genericArray = new T[size]; // suppose this is allowed return genericArray; }

Poiché un tipo generico T non associato si risolve in Object, il nostro metodo in fase di esecuzione sarà:

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

Quindi, se chiamiamo il nostro metodo e memorizziamo il risultato in un array String :

String[] myArray = getArray(5);

Il codice verrà compilato correttamente ma fallirà in fase di esecuzione con ClassCastException . Questo perché abbiamo appena assegnato un Object [] a un riferimento String [] . In particolare, un cast implicito da parte del compilatore non riuscirebbe a convertire Object [] nel nostro tipo richiesto String [] .

Sebbene non sia possibile inizializzare direttamente array generici, è comunque possibile ottenere l'operazione equivalente se il tipo preciso di informazioni è fornito dal codice chiamante.

3. Creazione di un array generico

Per il nostro esempio, consideriamo una struttura dati dello stack limitato MyStack , in cui la capacità è fissata a una certa dimensione. Inoltre, poiché vorremmo che lo stack funzionasse con qualsiasi tipo, una scelta di implementazione ragionevole sarebbe un array generico.

Per prima cosa, creiamo un campo per memorizzare gli elementi del nostro stack, che è un array generico di tipo E :

private E[] elements;

Secondo, aggiungiamo un costruttore:

public MyStack(Class clazz, int capacity) { elements = (E[]) Array.newInstance(clazz, capacity); }

Nota come usiamo java.lang.reflect.Array # newInstance per inizializzare il nostro array generico , che richiede due parametri. Il primo parametro specifica il tipo di oggetto all'interno del nuovo array. Il secondo parametro specifica quanto spazio creare per l'array. Poiché il risultato di Array # newInstance è di tipo Object , dobbiamo eseguirne il cast su E [] per creare il nostro array generico.

Dovremmo anche notare la convenzione di denominare un parametro di tipo clazz piuttosto che class, che è una parola riservata in Java.

4. Considerando ArrayList

4.1. Utilizzo di ArrayList al posto di un array

Spesso è più facile usare un ArrayList generico al posto di un array generico. Vediamo come possiamo cambiare MyStack per usare un ArrayList .

Per prima cosa, creiamo un campo per memorizzare i nostri elementi:

private List elements;

In secondo luogo, nel nostro costruttore di stack, possiamo inizializzare ArrayList con una capacità iniziale:

elements = new ArrayList(capacity);

Rende la nostra classe più semplice, poiché non dobbiamo usare la riflessione. Inoltre, non ci viene richiesto di passare un letterale di classe durante la creazione del nostro stack. Infine, poiché possiamo impostare la capacità iniziale di un ArrayList , possiamo ottenere gli stessi vantaggi di un array.

Pertanto, abbiamo solo bisogno di costruire array di generici in rare situazioni o quando ci interfacciamo con qualche libreria esterna che richiede un array.

4.2. Implementazione di ArrayList

È interessante notare che ArrayList stesso viene implementato utilizzando array generici. Diamo un'occhiata all'interno di ArrayList per vedere come.

Per prima cosa, vediamo il campo degli elementi dell'elenco:

transient Object[] elementData;

Nota ArrayList utilizza Object come tipo di elemento. Poiché il nostro tipo generico non è noto fino al runtime, Object viene utilizzato come superclasse di qualsiasi tipo.

Vale la pena notare che quasi tutte le operazioni in ArrayList possono utilizzare questo array generico poiché non è necessario fornire un array fortemente tipizzato al mondo esterno, ad eccezione di un metodo: toArray !

5. Creazione di un array da una raccolta

5.1. Esempio di LinkedList

Diamo un'occhiata all'uso di array generici nell'API Java Collections, dove costruiremo un nuovo array da una raccolta.

Per prima cosa, creiamo un nuovo LinkedList con un argomento di tipo String e aggiungiamo elementi ad esso:

List items = new LinkedList(); items.add("first item"); items.add("second item"); 

In secondo luogo, creiamo un array degli elementi che abbiamo appena aggiunto:

String[] itemsAsArray = items.toArray(new String[0]);

Per costruire il nostro array, List . Il metodo toArray richiede un array di input. Utilizza questo array esclusivamente per ottenere le informazioni sul tipo per creare un array di ritorno del tipo corretto.

Nel nostro esempio precedente, abbiamo usato new String [0] come array di input per costruire l' array String risultante .

5.2. Implementazione di LinkedList.toArray

Diamo uno sguardo a LinkedList.toArray , per vedere come è implementato in Java JDK.

Per prima cosa, diamo un'occhiata alla firma del metodo:

public  T[] toArray(T[] a)

Secondo, vediamo come viene creato un nuovo array quando richiesto:

a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);

Si noti come utilizza Array # newInstance per creare un nuovo array, come nel nostro esempio di stack precedente. Inoltre, si noti come il parametro a viene utilizzato per fornire un tipo a Array # newInstance. Infine, viene eseguito il cast del risultato di Array # newInstance a T [] crea un array generico.

6. Conclusione

In questo articolo, abbiamo prima esaminato le differenze tra array e generici, seguito da un esempio di creazione di un array generico. Quindi, abbiamo mostrato come l'utilizzo di un ArrayList possa essere più semplice rispetto all'utilizzo di un array generico. Infine, abbiamo anche esaminato l'uso di un array generico nell'API Collections.

Come sempre, il codice di esempio è disponibile su GitHub.