Comment créer un tableau Générique En Java?

en raison de la mise en œuvre de Java generics, vous ne pouvez pas avoir de code comme ceci:

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

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

Comment puis-je mettre en œuvre ceci tout en maintenant la sécurité du type?

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.

907
demandé sur Cœur 2009-02-09 20:30:44

29 réponses

je dois poser une question en retour: votre GenSet est-il "vérifié"ou" non vérifié"? Qu'est-ce que cela signifie?

  • coché : fort typage . GenSet sait explicitement quel type d'objets il contient (i.e. son constructeur a été explicitement appelé avec un argument Class<E> , et les méthodes vont jeter une exception quand ils sont passés 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];
        }
    }
    
  • non vérifié : faible typage . Aucune vérification de type n'est en fait effectuée sur les 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;
        }
    }
    

    noter que le type de composant du tableau doit être le effacement du paramètre de 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 ils ont été créés au moment de l'exécution, et ne peuvent donc pas fournir la sécurité de type à moins qu'un mécanisme explicite (vérification de type) ne soit implémenté.

593
répondu Varkhan 2015-04-06 04:19:53

Vous pouvez toujours faire ceci:

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

il s'agit de l'une des façons suggérées de mettre en œuvre une collection générique dans Java efficace; point 26 . Pas d'erreurs de type, pas besoin de lancer le tableau à plusieurs reprises. Toutefois , ce qui déclenche un avertissement parce qu'il est potentiellement dangereux, et doivent être utilisées avec prudence. Comme détaillé dans les commentaires, ce Object[] est maintenant masqué comme notre E[] type, et peut causer des erreurs inattendues ou ClassCastException s si utilisé de façon non sûre.

en règle générale, ce comportement est sûr tant que le tableau cast est utilisé en interne (par exemple pour sauvegarder une structure de données), et non retourné ou exposé au code client. Si vous avez besoin de retourner un tableau d'un type générique à un autre code, la réflexion Array classe que vous mentionnez est la bonne façon de faire.


à noter que dans la mesure du possible, vous aurez un temps beaucoup plus heureux de travailler avec List S plutôt que des tableaux si vous utilisez des génériques. Il est certain que parfois vous n'avez pas le choix, mais utiliser le cadre des collections est beaucoup plus robuste.

167
répondu dimo414 2015-07-31 00:12:32

Voici comment utiliser generics pour obtenir un tableau précis du type que vous recherchez tout en préservant la sécurité du type (par opposition aux autres réponses, qui vous donneront soit un tableau Object soit 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];  
    }  
}

qui se compile sans avertissements, et comme vous pouvez le voir dans main , pour n'importe quel type vous déclarez une instance de GenSet as, Vous pouvez assigner 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 dans le tableau sont du type correct.

il fonctionne en utilisant des littérales de classe comme des jetons de type runtime, comme discuté dans le tutoriels Java . Les littérales de classe sont traitées par le compilateur comme des instances de java.lang.Class . Pour en utiliser un, il suffit de suivre le nom d'une classe avec .class . Ainsi, String.class agit comme un Class objet représentant la classe String . Cela fonctionne également pour les interfaces, les enums, les tableaux de toutes les dimensions (par exemple String[].class ), les primitives (par exemple int.class ), et le mot-clé void (par exemple void.class ).

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

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

Inside the constructor, calling the method cast renvoie l'argument passé Object à la classe représentée par l'objet Class sur lequel la méthode a été appelée. Appeler la méthode statique newInstance dans java.lang.reflect.Array retourne comme un Object un tableau du type représenté par l'objet Class passé comme premier argument et de la longueur spécifiée par le int passé comme second argument. L'appel de la méthode getComponentType renvoie un objet Class représentant le type de composant du tableau représenté par l'objet Class sur lequel la méthode a été appelée (par exemple String.class pour String[].class , null si l'objet Class ne représente pas un tableau).

cette dernière phrase n'est pas tout à fait exacte. Appeler String[].class.getComponentType() retourne un Class objet représentant la classe String , mais son type 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

idem pour toute méthode dans Class qui retourne un objet Class .

en ce qui concerne 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 plâtre à T[] se traduira par un avertissement parce que le compilateur ne peut pas garantir la sécurité de type dans ce cas.


Edit concernant les Oing commentaires:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}
56
répondu gdejohn 2017-05-23 12:34:45

C'est la seule réponse est de type sécurisé

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}
34
répondu irreputable 2015-04-06 04:11:51

pour étendre à plus de dimensions, il suffit d'ajouter [] 's et les paramètres de dimension à newInstance() ( T est un paramètre de type, cls est un Class<T> , d1 à d5 sont des entiers):

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.

27
répondu Jason C 2014-01-12 18:55:29

en Java 8, nous pouvons faire une sorte de création de tableau Générique En utilisant une lambda ou une référence de méthode. C'est similaire à l'approche réflexive (qui passe un 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[]>) .

Ce pourrait aussi avant Java 8 utilisation anonyme des classes mais c'est de plus en plus lourde.

11
répondu Radiodef 2017-07-05 01:17:05

cette question est traitée au chapitre 5 (génériques) de Effective Java, 2e édition , point 25... Préfèrent les listes de tableaux

votre code fonctionnera, bien qu'il générera un avertissement non vérifié (que vous pourriez supprimer avec l'annotation suivante:

@SuppressWarnings({"unchecked"})

cependant, il serait probablement préférable d'utiliser une liste plutôt qu'un tableau.

il y a une discussion intéressante de ce bug / Fonctionnalité sur le site du projet OpenJDK .

10
répondu Jeff Olson 2016-08-29 16:56:59

Java generics fonctionne en vérifiant les types au moment de la compilation et en insérant les casts appropriés, mais 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 generics (qui était une décision de conception délibérée), mais qui signifie que vous ne pouvez pas normalement trouver ce que le type est au moment de l'exécution.

Le public Stack(Class<T> clazz,int capacity) constructeur vous oblige à passer un objet de Classe au moment de l'exécution, ce qui signifie classe l'information est disponible à l'exécution pour coder qui en a besoin. Et la forme Class<T> signifie que le compilateur vérifiera que l'objet de classe que vous passez est précisément l'objet de classe 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 array du type approprié dans votre constructeur, ce qui signifie que le type des objets que vous stockez dans votre collection aura leurs types vérifié au point qu'ils sont ajoutés à la collection.

7
répondu Bill Michell 2009-02-11 10:07:35

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

Generics est utilisé pour la vérification de type pendant le temps de compilation:

  • par conséquent, le but est de vérifier que ce qui entre Est ce dont vous avez besoin.
  • ce que vous retournez est ce dont le consommateur a besoin.
  • Vérifiez ceci:

enter image description here

Do ne vous inquiétez pas des avertissements de typographie lorsque vous écrivez une classe générique. Vous inquiéter lorsque vous l'utilisez.

6
répondu puneeth 2012-12-08 10:56:23

Qu'en est-il de cette solution?

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

ça marche et ça a l'air trop simple pour être vrai. Est-il un inconvénient?

5
répondu Benjamin M 2016-02-21 01:28:56

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 n'importe quel type d'objet en un tableau du même type.

4
répondu MatheusJardimB 2013-08-08 23:32:27

j'ai trouvé un moyen facile et rapide qui fonctionne pour moi. Notez que je ne l'ai utilisé que sur Java JDK 8. Je ne sais pas si ça marchera 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 dans main nous pouvons créer le tableau comme ceci:

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. la liste de tableaux et d'autres méthodes en Java.util.Classe d'ArrayList.

4
répondu Nik-Lz 2016-11-09 20:43:28

l'exemple utilise Java reflection pour créer un tableau. Faire cela n'est généralement pas recommandé, car il n'est pas typesafe. Au lieu de cela, ce que vous devez faire est juste utiliser une liste interne, et éviter le tableau du tout.

3
répondu Ola Bini 2009-02-09 17:33:58

j'ai fait ce code snippet pour instancier réflectivement une classe qui est passé pour un utilitaire de test automatisé simple.

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});
}

noter ce segment:

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

pour le tableau initialisant où tableau.newInstance(classe de tableau, la taille de la table) . La classe peut être à la fois primitive (int.de classe) et l'objet (de type Entier.classe.)

BeanUtils fait partie du printemps.

3
répondu Bobster 2012-08-31 10:39:28

vous n'avez pas besoin de passer l'argument de classe au constructeur. Essayez ceci.

static 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]
3
répondu saka1029 2018-02-21 05:31:38

en fait, une façon plus facile de le faire, est de créer un tableau d'objets et de le jeter à votre type désiré comme l'exemple suivant:

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

SIZE est une constante et T est un identificateur de type

2
répondu Pedram Esmaeeli 2015-06-12 09:53:54

passant une liste de valeurs...

public <T> T[] array(T... values) {
    return values;
}
2
répondu Rodrigo Asensio 2017-09-15 10:19:41

le casting forcé suggéré par d'autres personnes ne travaillait pas pour moi, jetant une exception de casting illégal.

Toutefois, l'implicite cast a bien fonctionné:

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

où élément est une classe I définie contenant le membre:

private K value;

de cette façon vous obtenez un tableau de type K (Si l'élément a seulement la valeur) ou n'importe quel type générique que vous voulez défini dans L'élément de classe.

1
répondu vnportnoy 2013-09-14 21:26:33

personne N'a répondu à la question de ce qui se passe dans l'exemple que vous avez posté.

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 ont dit génériques sont" effacés " au cours de la compilation. Ainsi, à l'exécution, une instance d'un generic ne sait pas quel est son type de Composant. La raison en est historique, Sun voulait ajouter des génériques sans briser l'interface existante (à la fois source et binaire).

"151940920 des" Tableaux d'autre part faire savoir leur type de composant à l'exécution.

cet exemple fonctionne autour du problème en ayant le code qui appelle le constructeur (qui connaît le type) passer un paramètre indiquant à la classe le type requis.

pour que la demande construise la classe avec quelque chose comme

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

et le constructeur sait maintenant (à l'exécution) Quel est le type de composant et peut utiliser cette information pour construire le tableau à travers la réflexion de l'API.

Array.newInstance(clazz, capacity);

finalement 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 savons).

ce style est un peu moche mais il peut parfois être la solution la moins mauvaise pour créer des types génériques qui ont besoin de connaître leur type de Composant à l'exécution pour quelque raison que ce soit (création de tableaux, ou création d'instances de leur type de Composant, etc.).

1
répondu plugwash 2015-10-18 14:54:58

j'ai trouvé une sorte de contourner ce problème.

la ligne ci-dessous lance 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 séparée, 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 les gens de la classe PersonList à travers un getter. La ligne ci-dessous vous donnera un tableau, qui a une 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'ici pas de problèmes.

1
répondu developer747 2016-10-19 12:57:33

vous pouvez créer un tableau D'objets et le jeter à E partout. Ce n'est pas très propre, mais ça devrait au moins marcher.

0
répondu Esko 2009-02-09 17:46:55

essayez ceci.

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>();
        }
    }
}
0
répondu David Bernard 2011-02-13 20:37:34

une solution facile, bien que compliquée à cela serait d'installer une deuxième classe" holder " à l'intérieur de votre classe principale, et de l'utiliser pour contenir vos données.

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
0
répondu StarMonkey 2012-04-05 00:10:45

peut-être sans rapport avec cette question mais pendant que j'obtenais le" generic array creation "erreur d'utilisation

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

je découvre les travaux suivants (et travaillé pour moi) avec @SuppressWarnings({"unchecked"}) :

 Tuple<Long, String>[] tupleArray = new Tuple[10];
0
répondu Mohsen Afshin 2013-08-21 16:11:35

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 une autre façon de créer un tel tableau, si la taille que vous vouliez était connue et petite, serait de simplement alimenter le nombre requis de "null"s dans la commande zeroArray?

bien qu'évidemment ce ne soit pas aussi polyvalent qu'utiliser le code createArray.

0
répondu Cambot 2014-07-09 13:36:35

vous pouvez utiliser un plâtre:

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

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}
0
répondu samir benzenine 2014-09-15 17:23:32

j'ai en fait trouvé une solution assez unique pour contourner l'incapacité d'initier un tableau Générique. Ce que vous devez faire est de créer une classe qui prend la variable générique T comme suit:

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

et ensuite dans votre classe array faites-le démarrer comme suit:

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

démarrer un new Generic Invoker[] va causer un problème sans contrôle, mais il ne devrait pas réellement y avoir de problèmes.

pour obtenir du tableau vous devriez appelez le tableau[i].variable comme ceci:

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

le reste, comme redimensionner le tableau peut être fait avec des tableaux.copyOf() comme suit:

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

et la fonction add 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++;
}
0
répondu Crab Nebula 2017-06-28 19:59:53
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}
-1
répondu Zubair Ibrhaim 2015-06-03 06:16:21

la création D'un tableau Générique est interdite en java mais vous pouvez le faire comme

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
-2
répondu Irfan Ul Haq 2018-04-19 13:45:59