web-dev-qa-db-fra.com

Comment créer un tableau générique?

Je ne comprends pas le lien entre les génériques et les tableaux.

Je peux créer une référence de tableau avec un type générique:

private E[] elements; //GOOD

Mais ne peut pas créer d'objet de tableau avec un type générique:

elements = new E[10]; //ERROR

Mais ça marche:

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

Vous ne devez pas confondre tableaux et génériques. Ils ne vont pas bien ensemble. Il existe des différences dans la manière dont les tableaux et les types génériques appliquent la vérification de type. Nous disons que les tableaux sont réifiés, mais pas les génériques. En conséquence, vous constatez que ces différences fonctionnent avec les tableaux et les génériques.

Les tableaux sont covariants, les génériques ne sont pas:

Qu'est-ce que cela signifie? Vous devez savoir maintenant que la tâche suivante est valide:

Object[] arr = new String[10];

Fondamentalement, un Object[] Est un super type de String[], Parce que Object est un super type de String. Ce n'est pas vrai avec les génériques. Donc, la déclaration suivante n'est pas valide et ne compilera pas:

List<Object> list = new ArrayList<String>(); // Will not compile.

Raison d'être, les génériques sont invariants.

Vérification de type:

Les génériques ont été introduits dans Java pour appliquer une vérification de type plus forte au moment de la compilation. En tant que tels, les types génériques ne disposent d'aucune information de type au moment de l'exécution en raison de type effacement . Donc , un List<String> a un type statique de List<String> mais un type dynamique de List.

Cependant, les tableaux contiennent les informations de type à l'exécution du type de composant. Au moment de l'exécution, les tableaux utilisent Array Store check pour vérifier si vous insérez des éléments compatibles avec le type de tableau réel. Donc, le code suivant:

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

compile correctement, mais échouera au moment de l'exécution à la suite de ArrayStoreCheck. Avec les génériques, cela n’est pas possible, car le compilateur essaiera d’empêcher l’exception d’exécution en fournissant une vérification de la compilation, en évitant la création d’une référence comme celle-ci, comme indiqué ci-dessus.

Alors, quel est le problème avec Generic Array Creation?

Création d'un tableau dont le type de composant est soit un type paramètre, un type concret paramétré ou un type paramétré de caractère générique lié, est type-unsafe.

Considérez le code comme ci-dessous:

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

Comme le type de T n'est pas connu au moment de l'exécution, le tableau créé est en réalité un Object[]. Donc, la méthode ci-dessus à l'exécution ressemblera à:

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

Maintenant, supposons que vous appeliez cette méthode comme:

Integer[] arr = getArray(10);

Voici le problème. Vous venez d'assigner un Object[] À une référence de Integer[]. Le code ci-dessus se compilera correctement, mais échouera au moment de l'exécution.

C'est pourquoi la création de tableaux génériques est interdite.

Pourquoi transtyper new Object[10] En E[] Fonctionne?

Maintenant votre dernier doute, pourquoi le code ci-dessous fonctionne:

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

Le code ci-dessus a les mêmes implications que celles expliquées ci-dessus. Si vous remarquez, le compilateur vous donnera un Avertissement de conversion non coché, car vous convertissez un tableau avec un type de composant inconnu. Cela signifie que la distribution peut échouer au moment de l'exécution. Par exemple, si vous avez ce code dans la méthode ci-dessus:

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

et vous appelez invoquer comme ça:

String[] arr = getArray(10);

cela échouera au moment de l'exécution avec une exception ClassCastException. Donc, non, cette façon ne fonctionnera pas toujours.

Qu'en est-il de la création d'un tableau de type List<String>[]?

Le problème est le même. En raison de la suppression du type, un List<String>[] N'est rien d'autre qu'un List[]. Donc, si la création de tels tableaux était autorisée, voyons ce qui pourrait arriver:

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

Maintenant, ArrayStoreCheck dans le cas ci-dessus réussira à l'exécution bien que cela aurait dû générer une exception ArrayStoreException. C'est parce que List<String>[] Et List<Integer>[] Sont compilés en List[] Au moment de l'exécution.

Pouvons-nous donc créer un tableau de types paramétrés de caractère générique non lié?

Oui. La raison étant, un List<?> Est un type réifiable. Et cela a du sens, car il n’ya aucun type associé. Il n’ya donc rien à perdre à la suite de l’effacement des caractères. Donc, il est parfaitement sûr de créer un tableau de ce type.

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

Le cas ci-dessus convient, car List<?> Est un super type de toutes les instanciations de type générique List<E>. Ainsi, il n’émettra pas d’exception ArrayStoreException au moment de l’exécution. La casse est la même avec le tableau de types bruts. Comme les types bruts sont aussi des types réifiables, vous pouvez créer un tableau List[].

Ainsi, vous pouvez créer uniquement un tableau de types réifiables, mais pas de types non reifiables. Notez que, dans tous les cas ci-dessus, la déclaration d'un tableau convient, c'est la création d'un tableau avec l'opérateur new qui donne des problèmes. Toutefois, il est inutile de déclarer un tableau de ces types de référence, car ils ne peuvent pointer que sur null ( Ignorer les types non limités).

Existe-t-il une solution de contournement pour E[]?

Oui, vous pouvez créer le tableau en utilisant la méthode Array#newInstance() :

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

    return arr;
}

Typecast est nécessaire car cette méthode retourne un Object. Mais vous pouvez être sûr que c'est un casting sûr. Donc, vous pouvez même utiliser @SuppressWarnings sur cette variable.

186
Rohit Jain

Voici l'implémentation de LinkedList<T>#toArray(T[]):

public <T> 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<E> x = first; x != null; x = x.next)
        result[i++] = x.item;

    if (a.length > size)
        a[size] = null;

    return a;
}

En bref, vous ne pouvez créer de tableaux génériques que par le biais de Array.newInstance(Class, int), où int est la taille du tableau.

3
Josh M

Le problème est que, pendant l'exécution le type générique est effacé so new E[10] serait équivalent à new Object[10].

Ce serait dangereux car il serait possible de mettre dans un tableau d'autres données que celles de E. C’est la raison pour laquelle vous devez indiquer explicitement le type souhaité par

3
Pshemo

vérifié :

public Constructor(Class<E> c, int length) {

    elements = (E[]) Array.newInstance(c, length);
}

ou décoché:

public Constructor(int s) {
    elements = new Object[s];
}
1
Melih Altıntaş