Array generici in Java

OK, sto navigando su google e non riesco a trovare alcuna soluzione al mio problema. Ho trovato molte soluzioni, ma non tutte quelle che si adattano.

Devo creare una serie di farmaci generici. Ma il tipo generico stesso estende Paragonabile. Quando provo il seguente:

public class Hash<T extends Comparable> { private T[] hashTable; private int tableSize; Hash(int records, double load) { tableSize = (int)(records / loadFactor); tableSize = findNextPrime(tableSize); hashTable = (T[])(new Object[tableSize]); //Error: Ljava.lang.Object; cannot be cast to [Ljava.lang.Comparable; } } 

Il problema è che l’object non può essere lanciato come un generico che estende Paragonabile. C’è un modo per aggirare questo?

Generici e matrici non si mescolano, fondamentalmente. La risposta breve è che puoi risolvere questo problema. La risposta più lunga è che probabilmente non dovresti e spiegherò perché.

Potresti usare Array.newInstance() questo modo:

 private Comparable[] hashtable; ... hashtable = (Comparable[])Array.newInstance(Comparable.class, tableSize); 

ma non è ansible creare un array del tipo parametrizzato.

Gli array sono covarianti . Ciò significa che mantengono il tipo dei loro elementi in fase di runtime. I generici di Java non lo sono. Usano la cancellazione di tipo per mascherare fondamentalmente il casting implicito che sta succedendo. È importante capirlo.

Quindi, quando crei una matrice di oggetti non puoi lanciarla su, diciamo, una matrice comparabile (o qualsiasi altro tipo) perché non è corretta.

Per darti un esempio. Con i generici questo è perfettamente legale:

 List list = new ArrayList(); List list2 = (List)list; list.add(3); 

È anche per questo che non puoi farlo:

 public  T newInstance(T t) { return new T(); // error! } 

cioè a runtime non c’è conoscenza della class di T. Questo è il motivo per cui il codice sopra riportato è più spesso scritto come:

 public  T newInstance(T t, Class clazz) { return clazz.newInstance(); } 

perché non è un tipo runtime per l’argomento generico. Ma con gli array:

 String arr[] = new String[10]; Integer arr2[] = (Integer[])arr; // error! 

Quello che dovresti fare in questo caso (imho) non è usare gli array ma usare un ArrayList . In tutta onestà, ci sono poche ragioni per usare gli array su un ArrayList e i generici sono solo un esempio di ciò.

Per una spiegazione migliore e più completa vedi le (eccellenti) FAQ di Java Generics :

Posso creare un array il cui tipo di componente è un tipo parametrico concreto?

No, perché non è sicuro dal tipo.

Le matrici sono covarianti, il che significa che una serie di riferimenti supertipi è un supertipo di una matrice di riferimenti di sottotipo. Ovvero, Object[] è un supertipo di String[] e un array di stringhe è accessibile tramite una variabile di riferimento di tipo Object[] .

Le altre risposte qui generalmente propongono un approccio migliore per questo (in particolare la raccomandazione di utilizzare un ArrayList), ma una risposta semplice in questo caso specifico potrebbe essere quella di fare:

 hashTable = (T[])(new Comparable[tableSize]); 

(cioè creare una matrice di tipo raw paragonabile anziché object)

Se incapsuli correttamente tutti gli accessi a questo array all’interno dell’object Hash, questo dovrebbe funzionare, ma (come spiegano le altre risposte) potresti lasciarti vulnerabile.

Il cast che stai tentando

 (T[])(new Object[tableSize]); 

fallisce, perché gli elementi nell’array sono istanze di Object. L’object non si estende Comparable , quindi il cast (T []) fallisce perché T è definito come:

 T extends Comparable 

Per risolvere questo problema:

  • Crea un’istanza della matrice in modo che gli elementi siano istanze di alcune classi che estendono il Comparable
  • Modifica hashTable da una matrice (che non è un tipo generico), ad un tipo di raccolta generico, ad esempio List hashTable = new ArrayList(tableSize>)

Spesso si verificano dei problemi quando è necessario creare un’istanza di un tipo generico. Il modo più semplice per aggirare questo è quello di passare la class di effettivamente verrà memorizzata nel costruttore. In questo modo puoi build dal tipo reale. Prova qualcosa del genere:

 public class Hash> { Hash(int records, double load, Class class) { tableSize = (int)(records / loadFactor); tableSize = findNextPrime(tableSize); hashTable = java.lang.reflect.Array.newInstance(class, tableSize); } private T[] hashTable; private int tableSize; } 

Il cast forzato suggerito da altre persone non ha funzionato per me, lanciando un’eccezione di casting illegale.

Tuttavia, questo cast implicito ha funzionato bene:

 Item[] array = new Item[SIZE]; 

dove Item è una class che ho definito contenente il membro:

 private K value; 

In questo modo si ottiene una matrice di tipo K (se l’elemento ha solo il valore) o qualsiasi tipo generico che si desidera definire nell’elemento di class.