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


1091

En raison de l'implémentation de génériques Java, vous ne pouvez pas avoir de code comme celui-ci:

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

Comment puis-je implémenter cela tout en maintenant la sécurité des types?

J'ai vu une solution sur les forums Java qui va comme ceci:

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

Mais je ne comprends vraiment pas ce qui se passe.


14
Avez-vous vraiment besoin d'utiliser un tableau ici? Qu'en est-il de l'utilisation d'une collection?
mat le

12
Oui, je pense aussi que les collections sont plus élégantes pour ce problème. Mais ceci est pour un devoir de classe et ils sont requis :(
tatsuhirosatou

3
Je ne comprends pas pourquoi j'ai besoin d'une réflexion ici. La grammaire Java est étrange: comme neuf java.util.HashMap <String, String> [10] n'est pas valide. new java.util.HashMap <long, long> (10) n'est pas valide. nouveau long [] [10] n'est pas valide, nouveau long [10] [] est valide. Ce genre de choses qui rend l'écriture d'un programme capable d'écrire un programme java est plus difficile qu'il n'y paraît.
homme de bronze

Réponses:


704

Je dois vous poser une question en retour: est-ce que vous GenSetêtes "coché" ou "non coché"? Qu'est-ce que ça veut dire?

  • Vérifié : frappe forte . GenSetsait explicitement quel type d'objets il contient (c'est-à-dire que son constructeur a été explicitement appelé avec un Class<E>argument, et les méthodes lèveront une exception quand on leur passera des arguments qui ne sont pas de type E. Voir Collections.checkedCollection.

    -> dans ce cas, vous devez écrire:

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> c, int s) {
            // Use Array native method to create array
            // of a type only known at run time
            @SuppressWarnings("unchecked")
            final E[] a = (E[]) Array.newInstance(c, s);
            this.a = a;
        }
    
        E get(int i) {
            return a[i];
        }
    }
  • Décoché : frappe faible . Aucune vérification de type n'est effectuée sur aucun des objets passés en argument.

    -> dans ce cas, vous devez écrire

    public class GenSet<E> {
    
        private Object[] a;
    
        public GenSet(int s) {
            a = new Object[s];
        }
    
        E get(int i) {
            @SuppressWarnings("unchecked")
            final E e = (E) a[i];
            return e;
        }
    }

    Notez que le type de composant du tableau doit être l' effacement du paramètre type:

    public class GenSet<E extends Foo> { // E has an upper bound of Foo
    
        private Foo[] a; // E erases to Foo, so use Foo[]
    
        public GenSet(int s) {
            a = new Foo[s];
        }
    
        ...
    }

Tout cela résulte d'une faiblesse connue et délibérée des génériques en Java: il a été implémenté en utilisant l'effacement, donc les classes "génériques" ne savent pas avec quel argument de type elles ont été créées au moment de l'exécution, et ne peuvent donc pas fournir de type- sécurité à moins qu'un mécanisme explicite (vérification de type) ne soit implémenté.


7
Quelle serait la meilleure option en termes de performances? J'ai besoin d'obtenir des éléments de ce tableau assez souvent (dans une boucle). Une collection est donc probablement plus lente, mais laquelle de ces deux est la plus rapide?
user1111929

3
Et si le type générique est borné, le tableau de sauvegarde doit être du type englobant.
Mordechai

5
@AaronDigulla Juste pour clarifier ce n'est pas l'affectation, mais l'initialisation d'une variable locale. Vous ne pouvez pas annoter une expression / instruction.
kennytm

1
@Varkhan Existe-t-il un moyen de redimensionner ces tableaux à partir de l'implémentation de classe. Par exemple, si je veux redimensionner après un débordement comme ArrayList. J'ai recherché l'implémentation d'ArrayList qu'ils ont Object[] EMPTY_ELEMENTDATA = {}pour le stockage. Puis-je utiliser ce mécanisme pour redimensionner sans connaître le type à l'aide de génériques?
JourneyMan

2
Pour ceux qui veulent faire une méthode avec un type générique (ce que je cherchais), utilisez ceci:public void <T> T[] newArray(Class<T> type, int length) { ... }
Daniel Kvist

225

Tu peux le faire:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

C'est l'un des moyens suggérés pour implémenter une collection générique dans Java efficace; Point 26 . Aucune erreur de type, pas besoin de transtyper le tableau à plusieurs reprises. Cependant, cela déclenche un avertissement car il est potentiellement dangereux et doit être utilisé avec prudence. Comme détaillé dans les commentaires, cela se Object[]fait maintenant passer pour notre E[]type et peut provoquer des erreurs inattendues ou ClassCastExceptions s'il est utilisé de manière non sécurisée.

En règle générale, ce comportement est sûr tant que le tableau de cast est utilisé en interne (par exemple pour sauvegarder une structure de données), et non retourné ou exposé au code client. Si vous devez renvoyer un tableau d'un type générique vers un autre code, la Arrayclasse de réflexion que vous mentionnez est la bonne solution.


Il convient de mentionner que dans la mesure du possible, vous aurez beaucoup plus de plaisir à travailler avec des Lists plutôt qu'avec des tableaux si vous utilisez des génériques. Certes, parfois, vous n'avez pas le choix, mais l'utilisation du cadre de collections est beaucoup plus robuste.


47
Cela ne fonctionnera pas si le tableau est traité comme un tableau typé de n'importe quel type, comme String[] s=b;dans la test()méthode ci-dessus . C'est parce que le tableau de E n'est pas vraiment, c'est Object []. Cela importe si vous voulez, par exemple un List<String>[]- vous ne pouvez pas utiliser un Object[]pour cela, vous devez en avoir un List[]spécifiquement. C'est pourquoi vous devez utiliser la création de tableau de classe <?> Reflétée.
Lawrence Dol

8
Le cas du coin / problème est si vous voulez faire, par exemple, public E[] toArray() { return (E[])internalArray.clone(); }quand internalArrayest tapé comme E[], et est donc en fait un Object[]. Cela échoue au moment de l'exécution avec une exception de conversion de type car un Object[]ne peut pas être affecté à un tableau de n'importe quel type E.
Lawrence Dol

17
Fondamentalement, cette approche fonctionnera tant que vous ne retournez pas le tableau, ne le passez pas ou ne le stockez pas à un endroit en dehors de la classe qui nécessite un tableau d'un certain type. Tant que vous êtes dans la classe, tout va bien car E est effacé. C'est "dangereux" parce que si vous essayez de le retourner ou quelque chose, vous ne recevez aucun avertissement qu'il est dangereux. Mais si vous faites attention, cela fonctionne.
newacct

3
C'est assez sûr. Dans E[] b = (E[])new Object[1];vous pouvez clairement voir que la seule référence au tableau créé est bet que le type deb est E[]. Par conséquent, il n'y a aucun risque que vous accédiez accidentellement au même tableau via une variable différente d'un type différent. Si au lieu de cela, vous l'avez fait, Object[] a = new Object[1]; E[]b = (E[])a; vous devrez être paranoïaque quant à la façon dont vous l'utilisez a.
Aaron McDaid

5
Au moins dans Java 1.6, cela génère un avertissement: "
Cast non contrôlé

61

Voici comment utiliser les génériques pour obtenir un tableau du type précis que vous recherchez tout en préservant la sécurité du type (contrairement aux autres réponses, qui vous redonneront un Objecttableau ou entraîneront des avertissements au moment de la compilation):

import java.lang.reflect.Array;  

public class GenSet<E> {  
    private E[] a;  

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
    }  

    public static void main(String[] args) {  
        GenSet<String> foo = new GenSet<String>(String[].class, 1);  
        String[] bar = foo.a;  
        foo.a[0] = "xyzzy";  
        String baz = foo.a[0];  
    }  
}

Cela se compile sans avertissements, et comme vous pouvez le voir main, pour n'importe quel type dont vous déclarez une instance GenSetas, vous pouvez l'affecter aà un tableau de ce type, et vous pouvez affecter un élément de aà une variable de ce type, ce qui signifie que le tableau et les valeurs du tableau sont du type correct.

Il fonctionne en utilisant des littéraux de classe comme jetons de type runtime, comme indiqué dans les didacticiels Java . Les littéraux de classe sont traités par le compilateur comme des instances de java.lang.Class. Pour en utiliser un, suivez simplement le nom d'une classe avec .class. Agit donc String.classcomme un Classobjet représentant la classe String. Cela fonctionne également pour les interfaces, les énumérations, les tableaux de n'importe quelle dimension (par exemple String[].class), les primitives (par exemple int.class) et le mot clé void(par exemple void.class).

Classlui-même est générique (déclaré comme Class<T>, où Treprésente le type que l' Classobjet représente), ce qui signifie que le type de String.classest Class<String>.

Ainsi, chaque fois que vous appelez le constructeur pour GenSet, vous passez un littéral de classe pour le premier argument représentant un tableau du GenSettype déclaré de l' instance (par exemple String[].classpour GenSet<String>). Notez que vous ne pourrez pas obtenir un tableau de primitives, car les primitives ne peuvent pas être utilisées pour les variables de type.

A l'intérieur du constructeur, l'appel de la méthode castrenvoie l' Objectargument passé cast à la classe représentée par l' Classobjet sur lequel la méthode a été appelée. L'appel de la méthode statique newInstancedans java.lang.reflect.Arrayrenvoie comme un Objecttableau du type représenté par l' Classobjet passé comme premier argument et de la longueur spécifiée par intpassé comme deuxième argument. Appel de la méthodegetComponentType renvoie un Classobjet représentant le type de composant de la matrice représentée par l' Classobjet sur lequel la méthode a été appelée (par exemple , String.classpour String[].class, nullsi l' Classobjet ne représente pas un tableau).

Cette dernière phrase n'est pas tout à fait exacte. L'appel String[].class.getComponentType()renvoie unClass objet représentant la classe String, mais son type ne l'est Class<?>pas Class<String>, c'est pourquoi vous ne pouvez pas faire quelque chose comme ce qui suit.

String foo = String[].class.getComponentType().cast("bar"); // won't compile

Il en va de même pour chaque méthode Classqui renvoie un Classobjet.

Concernant le commentaire de Joachim Sauer sur cette réponse (je n'ai pas assez de réputation pour le commenter moi-même), l'exemple utilisant le cast pour T[]entraînera un avertissement car le compilateur ne peut pas garantir la sécurité du type dans ce cas.


Modifier concernant les commentaires d'Ingo:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}

5
Cela ne sert à rien, ce n'est qu'une manière compliquée d'écrire de nouveaux String [...]. Mais ce qui est vraiment nécessaire, c'est quelque chose comme un statique public <T> T [] newArray (taille int) {...}, et cela n'existe tout simplement pas en java noir peut-il être simulé avec réflexion - la raison en est que les informations sur la façon dont un type générique est instancié n'est pas disponible au moment de l'exécution.
Ingo

4
@Ingo De quoi parlez-vous? Mon code peut être utilisé pour créer un tableau de tout type.
gdejohn

3
@Charlatan: Bien sûr, mais il en va de même pour le nouveau []. La question est: qui connaît le type et quand. Par conséquent, si vous n'avez qu'un type générique, vous ne pouvez pas.
Ingo

2
Je n'en doute pas. Le fait est que vous n'obtenez pas d'objet Class au moment de l'exécution pour le type générique X.
Ingo

2
Presque. J'admets que c'est plus que ce qui peut être réalisé avec new []. En pratique, cela fera presque toujours l'affaire. Cependant, il n'est toujours pas possible, par exemple, d'écrire une classe conteneur paramétrée avec E qui a une méthode E [] toArray () et qui retourne en effet un vrai tableau E []. Votre code ne peut être appliqué que s'il y a au moins un E-objet dans la collection. Donc, une solution générale est impossible.
Ingo

42

Ceci est la seule réponse qui est de type sûr

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}

J'ai dû le rechercher, mais oui, le deuxième argument "length" de Arrays#copyOf()est indépendant de la longueur du tableau fourni comme premier argument. C'est intelligent, bien qu'il paie le coût des appels vers Math#min()et System#arrayCopy(), qui ne sont strictement nécessaires pour faire ce travail. docs.oracle.com/javase/7/docs/api/java/util/…
seh

8
Cela ne fonctionne pas s'il Es'agit d'une variable de type. Le varargs crée un tableau d'effacement de Equand Eest une variable de type, ce qui la rend peu différente de (E[])new Object[n]. Veuillez consulter http://ideone.com/T8xF91 . Ce n'est en aucun cas plus sûr que n'importe quelle autre réponse.
Radiodef

1
@Radiodef - la solution est de type vérifiable au moment de la compilation. notez que l'effacement ne fait pas exactement partie de la spécification de langue; la spécification est rédigée avec soin afin que nous puissions avoir une réification complète à l'avenir - et cette solution fonctionnerait également parfaitement au moment de l'exécution, contrairement aux autres solutions.
ZhongYu

@Radiodef - On peut se demander si l'interdiction de la création de tableaux génériques est une bonne idée. peu importe, le langage laisse une porte dérobée - vararg nécessite la création d'un tableau générique. C'est comme si la langue l'avait permis new E[]. Le problème que vous avez montré dans votre exemple est un problème d'effacement général, qui n'est pas propre à cette question et à cette réponse.
ZhongYu

2
@Radiodef - Il y a quelques différences. L'exactitude de cette solution est vérifiée par le compilateur; il ne repose pas sur le raisonnement humain de la distribution forcée. La différence n'est pas significative pour ce problème particulier. Certaines personnes aiment juste être un peu fantaisistes, c'est tout. Si quelqu'un est induit en erreur par le libellé d'OP, cela est clarifié par vos commentaires et les miens.
ZhongYu

33

Pour étendre à plus de dimensions, ajoutez simplement []les paramètres s et dimension à newInstance()( Test un paramètre de type, clsest un Class<T>, à d1travers des d5entiers):

T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);

Voir Array.newInstance()pour plus de détails.


4
+1 Il y a eu des questions sur la création de tableaux multidimensionnels qui sont fermées en tant que dupes de ce message - mais aucune réponse n'avait spécifiquement abordé cela.
Paul Bellora

1
@JordanC Peut-être; bien qu'il soit le même dans l'esprit que stackoverflow.com/a/5671304/616460 ; Je réfléchirai à la meilleure façon de gérer demain. J'ai sommeil.
Jason C

14

En Java 8, nous pouvons faire une sorte de création de tableau générique en utilisant une référence lambda ou une méthode. Ceci est similaire à l'approche réflexive (qui passe a Class), mais ici nous n'utilisons pas la réflexion.

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

Par exemple, il est utilisé par <A> A[] Stream.toArray(IntFunction<A[]>).

Cela pourrait également être fait avant Java 8 à l'aide de classes anonymes, mais c'est plus lourd.


Vous n'avez pas vraiment besoin d'une interface spéciale comme ArraySuppliercelle-ci, vous pouvez déclarer le constructeur as GenSet(Supplier<E[]> supplier) { ...et l'appeler avec la même ligne que vous.
Lii

4
@Lii Pour être le même que mon exemple, ce serait le cas IntFunction<E[]>, mais oui c'est vrai.
Radiodef

12

Ceci est couvert dans le chapitre 5 (Generics) de Effective Java, 2e édition , point 25 ... Préférez les listes de tableaux

Votre code fonctionnera, bien qu'il génère un avertissement non contrôlé (que vous pouvez supprimer avec l'annotation suivante:

@SuppressWarnings({"unchecked"})

Cependant, il serait probablement préférable d'utiliser une liste au lieu d'un tableau.

Il y a une discussion intéressante sur ce bug / fonctionnalité sur le site du projet OpenJDK .


8

Vous n'avez pas besoin de passer l'argument Class au constructeur. Essaye ça.

public class GenSet<T> {
    private final T[] array;
    @SuppressWarnings("unchecked")
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        Class<?> c = dummy.getClass().getComponentType();
        array = (T[])Array.newInstance(c, capacity);
    }
    @Override
    public String toString() {
        return "GenSet of " + array.getClass().getComponentType().getName()
            + "[" + array.length + "]";
    }
}

et

GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

résultat:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]

7

Les génériques Java fonctionnent en vérifiant les types au moment de la compilation et en insérant les transtypages appropriés, mais en effaçant les types dans les fichiers compilés. Cela rend les bibliothèques génériques utilisables par du code qui ne comprend pas les génériques (ce qui était une décision de conception délibérée) mais ce qui signifie que vous ne pouvez pas normalement savoir quel est le type au moment de l'exécution.

Le Stack(Class<T> clazz,int capacity)constructeur public vous oblige à passer un objet Class au moment de l'exécution, ce qui signifie que les informations de classe sont disponibles au moment de l'exécution pour coder qui en a besoin. Et le Class<T>formulaire signifie que le compilateur vérifiera que l'objet Class que vous passez est précisément l'objet Class pour le type T. Pas une sous-classe de T, pas une superclasse de T, mais précisément T.

Cela signifie alors que vous pouvez créer un objet tableau du type approprié dans votre constructeur, ce qui signifie que le type des objets que vous stockez dans votre collection verra leurs types vérifiés au moment où ils sont ajoutés à la collection.


6

Salut bien que le fil soit mort, je voudrais attirer votre attention sur ceci:

Les génériques sont utilisés pour la vérification de type pendant la compilation:

  • Par conséquent, le but est de vérifier que ce dont vous disposez est ce dont vous avez besoin.
  • Ce que vous retournez est ce dont le consommateur a besoin.
  • Vérifiez ça:

entrez la description de l'image ici

Ne vous inquiétez pas des avertissements de transtypage lorsque vous écrivez une classe générique. Inquiétez-vous lorsque vous l'utilisez.


6

Et cette solution?

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

Cela fonctionne et semble trop simple pour être vrai. Y a-t-il un inconvénient?


3
Neat, mais ne fonctionne que si vous l'appelez «manuellement», c'est-à-dire passez les éléments individuellement. Si vous ne pouvez pas créer une nouvelle instance de T[], vous ne pouvez pas créer par programmation un T[] elemspour passer dans la fonction. Et si vous le pouviez, vous n'auriez pas besoin de la fonction.
orlade

5

Regardez aussi ce code:

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

Il convertit une liste de tout type d'objet en un tableau du même type.


Oui, vous retournez null, ce qui n'est pas le tableau vide attendu. C'est le mieux que vous puissiez faire, mais pas l'idéal.
Kevin Cox

Cela peut également échouer si le Listcontient plus d'un type d'objet, par exemple, toArray(Arrays.asList("abc", new Object()))lancera ArrayStoreException.
Radiodef

J'en ai utilisé une version simplifiée; La première chose que j'ai pu utiliser a fonctionné, même s'il est vrai que je n'ai pas essayé certaines des solutions les plus complexes. Pour éviter une forboucle et d'autres, j'ai utilisé Arrays.fill(res, obj);car je voulais la même valeur pour chaque index.
bbarker

5

J'ai trouvé un moyen rapide et facile qui fonctionne pour moi. Notez que je l'ai utilisé uniquement sur Java JDK 8. Je ne sais pas si cela fonctionnera avec les versions précédentes.

Bien que nous ne puissions pas instancier un tableau générique d'un paramètre de type spécifique, nous pouvons passer un tableau déjà créé à un constructeur de classe générique.

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

Maintenant, en général, nous pouvons créer le tableau comme suit:

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

Pour plus de flexibilité avec vos tableaux, vous pouvez utiliser une liste chaînée par exemple. ArrayList et d'autres méthodes trouvées dans la classe Java.util.ArrayList.


4

L'exemple utilise la réflexion Java pour créer un tableau. Cette opération n'est généralement pas recommandée, car elle n'est pas sécurisée. Au lieu de cela, ce que vous devez faire est simplement d'utiliser une liste interne et d'éviter du tout le tableau.


13
Le deuxième exemple (en utilisant Array.newInstance ()) est en fait de type sécurisé. Cela est possible car le type T de l'objet Class doit correspondre au T du tableau. Cela vous oblige essentiellement à fournir les informations que le runtime Java rejette pour les génériques.
Joachim Sauer

4

Passer une liste de valeurs ...

public <T> T[] array(T... values) {
    return values;
}

3

J'ai fait cet extrait de code pour instancier de manière réfléchie une classe qui est passée pour un simple utilitaire de test automatisé.

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

Notez ce segment:

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

pour le tableau initiant où Array.newInstance (classe de tableau, taille du tableau) . La classe peut être à la fois primitive (int.class) et objet (Integer.class).

BeanUtils fait partie du printemps.


3

En fait, un moyen plus simple de le faire consiste à créer un tableau d'objets et à le caster selon le type souhaité, comme dans l'exemple suivant:

T[] array = (T[])new Object[SIZE];

SIZEest une constante et Test un identificateur de type


1

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.


1

Personne d'autre n'a répondu à la question de savoir ce qui se passe dans l'exemple que vous avez publié.

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

Comme d'autres l'ont dit, les génériques sont "effacés" lors de la compilation. Ainsi, lors de l'exécution, une instance d'un générique ne sait pas quel est son type de composant. La raison en est historique, Sun voulait ajouter des génériques sans casser l'interface existante (à la fois source et binaire).

Les tableaux, en revanche , connaissent leur type de composant lors de l'exécution.

Cet exemple contourne le problème en faisant passer le code qui appelle le constructeur (qui connaît le type) un paramètre indiquant à la classe le type requis.

Donc, l'application construirait la classe avec quelque chose comme

Stack<foo> = new Stack<foo>(foo.class,50)

et le constructeur sait maintenant (au moment de l'exécution) quel est le type de composant et peut utiliser ces informations pour construire le tableau via l'API de réflexion.

Array.newInstance(clazz, capacity);

Enfin, nous avons un cast de type parce que le compilateur n'a aucun moyen de savoir que le tableau retourné par Array#newInstance() est le type correct (même si nous le savons).

Ce style est un peu moche mais il peut parfois être la moins mauvaise solution pour créer des types génériques qui ont besoin de connaître leur type de composant au moment de l'exécution pour une raison quelconque (création de tableaux ou création d'instances de leur type de composant, etc.).


1

J'ai trouvé une sorte de solution à ce problème.

La ligne ci-dessous renvoie une erreur de création de tableau générique

List<Person>[] personLists=new ArrayList<Person>()[10];

Cependant, si j'encapsule List<Person>dans une classe distincte, cela fonctionne.

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

Vous pouvez exposer des personnes de la classe PersonList via un getter. La ligne ci-dessous vous donnera un tableau, qui a un List<Person>dans chaque élément. En d'autres termes, tableau de List<Person>.

PersonList[] personLists=new PersonList[10];

J'avais besoin de quelque chose comme ça dans un code sur lequel je travaillais et c'est ce que j'ai fait pour le faire fonctionner. Jusqu'à présent, aucun problème.


0

Vous pouvez créer un tableau d'objets et le convertir en E partout. Ouais, ce n'est pas une façon très propre de le faire mais cela devrait au moins fonctionner.


"Nous recherchons des réponses longues qui fournissent une explication et un contexte. Ne donnez pas seulement une réponse sur une seule ligne; expliquez pourquoi votre réponse est correcte, idéalement avec des citations. Les réponses sans explications peuvent être supprimées."
gparyani

BUt qui ne fonctionnera pas dans certains cas, comme si votre classe générique veut implémenter une interface comparable.
RamPrasadBismil

Bienvenue il y a sept ans, je suppose.
Esko

1
Cela ne fonctionnera pas si vous essayez de renvoyer le tableau du code générique à un appelant non générique. Il y aura une exception de classe-goût de classe.
plugwash

0

essaye ça.

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}

Je n'arrive pas à faire fonctionner votre code, d'où vient votre Elementclasse?

0

Une solution de contournement simple, quoique compliquée, consisterait à imbriquer une deuxième classe «titulaire» à l'intérieur de votre classe principale et à l'utiliser pour stocker vos données.

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

3
Cela ne fonctionne pas vraiment. new Holder<Thing>[10]est une création de tableau générique.
Radiodef

0

Peut-être sans rapport avec cette question, mais pendant que j'obtenais l' generic array creationerreur " " pour l'utilisation

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

J'ai découvert les œuvres suivantes (et travaillé pour moi) avec @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];

Oui, ce n'est pas tout à fait lié, mais enraciné dans les mêmes problèmes (effacement, covariance du tableau). Voici un exemple de publication sur la création de tableaux de types paramétrés: stackoverflow.com/questions/9542076/…
Paul Bellora

0

Je me demande si ce code créerait un tableau générique efficace?

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

Edit: Peut-être qu'une autre façon de créer un tel tableau, si la taille dont vous aviez besoin était connue et petite, serait de simplement introduire le nombre requis de "null" dans la commande zeroArray?

Bien que ce ne soit évidemment pas aussi polyvalent que l'utilisation du code createArray.


Non, cela ne fonctionne pas. Le varargs crée l'effacement de Tquand Test une variable de type, c'est-à-dire zeroArrayrenvoie un Object[]. Voir http://ideone.com/T8xF91 .
Radiodef

0

Vous pouvez utiliser un casting:

public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}

Si vous allez suggérer cela, vous devez vraiment expliquer ses limites. N'exposez jamais aà l'extérieur de la classe!
Radiodef du

0

En fait, j'ai trouvé une solution assez unique pour contourner l'incapacité à lancer un tableau générique. Ce que vous devez faire est de créer une classe qui accepte la variable générique T comme ceci:

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

puis dans votre classe de tableau, commencez comme ceci:

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

commencer un new Generic Invoker[] entraînera un problème avec non coché mais il ne devrait pas y avoir de problème.

Pour obtenir du tableau, vous devez appeler le tableau [i] .variable comme ceci:

public T get(int index){
    return array[index].variable;
}

Le reste, tel que le redimensionnement du tableau peut être fait avec Arrays.copyOf () comme ceci:

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

Et la fonction d'ajout peut être ajoutée comme suit:

public boolean add(T element){
    // the variable size below is equal to how many times the add function has been called 
    // and is used to keep track of where to put the next variable in the array
    arrays[size] = new GenericInvoker(element);
    size++;
}

1
La question concernait la création d'un tableau du type du paramètre de type générique T, et non un tableau d'un type paramétré.
Sotirios Delimanolis

Cependant, il accomplit la même tâche et ne vous oblige pas à insérer une classe, ce qui rend votre collection personnalisée plus facile à utiliser.
Nébuleuse du Crabe

Quelle tâche ? C'est littéralement une tâche différente: un tableau d'un type paramétré vs un tableau d'un paramètre de type générique.
Sotirios Delimanolis

Il vous permet de créer un tableau à partir d'un type générique? Le problème d'origine était d'initialiser un tableau à l'aide d'un type générique qui, à l'aide de ma méthode, vous permet de le faire sans avoir à pousser l'utilisateur dans une classe ou à donner une erreur non vérifiée, comme essayer de convertir un objet en chaîne. Comme pour le froid, je ne suis pas le meilleur dans ce que je fais, et je ne suis pas allé à l'école pour la programmation, mais je pense que je mérite tout de même un peu d'entrée plutôt que d'être trompé par un autre enfant sur Internet.
Nébuleuse du crabe

Je suis d'accord avec Sotiros. Il y a deux façons de penser à la réponse. Soit c'est une réponse à une question différente, soit c'est une tentative de généraliser la question. Les deux sont faux / pas utiles. Les personnes qui recherchent des conseils sur la façon d'implémenter une classe de «tableau générique» cesseraient de lire en lisant le titre de la question. Et lorsqu'ils trouvent un Q avec 30 réponses, il est très peu probable qu'ils défilent jusqu'à la fin et lisent une réponse à vote zéro d'un nouvel arrivant.
Stephen C

0

Selon vnportnoy, la syntaxe

GenSet<Integer> intSet[] = new GenSet[3];

crée un tableau de références nulles, à remplir comme

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

qui est de type sûr.


-1
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}

Vous devez toujours ajouter une explication à votre code et expliquer pourquoi il résout la question publiée d'origine.
mjuarez

-1

La création de tableaux génériques n'est pas autorisée en Java, mais vous pouvez le faire comme

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.