web-dev-qa-db-fra.com

Tableaux génériques en Java

OK, j'ai fait des recherches sur le Web sur Google et je n'arrive pas à trouver de solution à mon problème. J'ai trouvé beaucoup de solutions, mais pas celles qui convenaient.

J'ai besoin de créer un tableau de génériques. Mais le type générique lui-même étend Comparable. Lorsque j'essaye ce qui suit:

public class Hash<T extends Comparable<String>> {
    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;
    }
}

Le problème est que l'objet ne peut pas être converti en générique qui étend Comparable. Y a-t-il un moyen de contourner ceci?

44
user131441

Les génériques et les tableaux ne se mélangent pas, fondamentalement. La réponse courte est que vous pouvez contourner ce problème. La réponse la plus longue est que vous ne devriez probablement pas et j'expliquerai pourquoi.

Vous pouvez utiliser Array.newInstance() comme ceci:

private Comparable[] hashtable;

...

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

mais vous ne pouvez pas créer un tableau de votre type paramétré.

Les tableaux sont covariant. Cela signifie qu'ils conservent le type de leurs éléments lors de l'exécution. Les génériques de Java ne le sont pas. Ils utilisent type erasure pour masquer fondamentalement le transtypage implicite en cours. Il est important de comprendre cela.

Ainsi, lorsque vous créez un tableau d'objets, vous ne pouvez pas le convertir en, disons, un tableau comparable (ou tout autre type) car ce n'est pas correct.

Pour vous donner un exemple. Avec les génériques, c'est parfaitement légal:

List<String> list = new ArrayList<String>();
List<Integer> list2 = (List<Integer>)list;
list.add(3);

C'est aussi pourquoi vous ne pouvez pas faire ça:

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

c'est-à-dire qu'au moment de l'exécution, il n'y a aucune connaissance de la classe de T. C'est pourquoi le code ci-dessus est plus souvent écrit comme:

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

car il n'y a pas de type d'exécution pour l'argument générique. Mais avec des tableaux:

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

Ce que vous devez faire dans ce cas (à mon humble avis), ce n'est pas utiliser des tableaux mais utiliser un ArrayList. En toute honnêteté, il y a très peu de raisons d'utiliser des tableaux sur un ArrayList et les génériques n'en sont qu'un exemple.

Pour une explication meilleure et plus complète, voir (excellent) FAQ sur les génériques Java :

Puis-je créer un tableau dont le type de composant est un type paramétré concret?

Non, car il n'est pas sûr pour le type.

Les tableaux sont covariants, ce qui signifie qu'un tableau de références de supertype est un supertype d'un tableau de références de sous-type. C'est, Object[] est un sur-type de String[] et un tableau de chaînes sont accessibles via une variable de référence de type Object[].

...

78
cletus

Les autres réponses ici préconisent généralement une meilleure approche pour cela (en particulier la recommandation d'utiliser une ArrayList à la place), mais une réponse simple dans ce cas spécifique pourrait être de faire:

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

(c'est-à-dire créer un tableau de type Raw Comparable au lieu d'Object)

Si vous encapsulez correctement tous les accès à ce tableau dans votre objet Hash, cela devrait fonctionner, mais (comme les autres réponses l'expliquent), vous pourriez vous rendre vulnérable.

16
matt

Le casting que vous tentez

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

échoue, car les éléments du tableau sont des instances de Object. L'objet n'étend pas Comparable<String>, Donc le transtypage (T []) échoue car T est défini comme:

T extends Comparable<String>

Pour résoudre ce problème:

  • Instanciez le tableau afin que ses éléments soient des instances d'une classe qui étend Comparable<String>
  • Remplacez hashTable d'un tableau (qui n'est pas un type générique) par un type de collection générique, par exemple List<T> hashTable = new ArrayList<T>(tableSize>)
4
Dónal

Vous rencontrez souvent des problèmes lorsque vous devez instancier quelque chose de type générique. Le moyen le plus simple de contourner ce problème est de transmettre la classe de effectivement stockée dans le constructeur. De cette façon, vous pouvez construire à partir du type réel. Essayez quelque chose comme ceci:

public class Hash<T extends Comparable<String>>
{
  Hash(int records, double load, Class<T> class)
  {
    tableSize = (int)(records / loadFactor);
    tableSize = findNextPrime(tableSize);

    hashTable = Java.lang.reflect.Array.newInstance(class, tableSize);
  }

private T[] hashTable;
private int tableSize;

}
1
Chris Dail

Le casting forcé suggéré par d'autres personnes n'a pas fonctionné pour moi, jetant une exception au casting illégal.

Cependant, cette distribution implicite a bien fonctionné:

Item<K>[] array = new Item[SIZE];

où Item est une classe que j'ai définie contenant le membre:

private K value;

De cette façon, vous obtenez un tableau de type K (si l'élément n'a que la valeur) ou tout type générique que vous souhaitez définir dans la classe Item.

0
vnportnoy