1246 votes

Comment: générique de la création de la matrice

En raison de la mise en œuvre de Java génériques, vous ne pouvez pas avoir un 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 ce, tout en maintenant la sécurité de type?

J'ai vu une solution sur le forum 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 n'ai vraiment pas obtenir ce qu'il se passe. Quelqu'un peut-il aider?

784voto

Varkhan Points 6756

J'ai une question à poser: est votre GenSet "vérifié" ou "désactivé"? Qu'est-ce que cela signifie?

  • vérifié: typage fort. GenSet sait explicitement ce type d'objets qu'il contient (c'est à dire son constructeur a explicitement appelé avec un Class<E> argument, et les méthodes lèvera 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 cochée: le typage faible. Aucun type de vérification est effectué sur un objet passé 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 de la matrice devrait être l' 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];
        }
    
        ...
    }
    

Tous ces résultats sont issus d'un connu, et de propos délibéré, la faiblesse des génériques en Java: il a été mis en œuvre à l'aide de l'effacement, de manière "générique" classes ne sais pas quel type d'argument qu'ils ont été créés au moment de l'exécution, et par conséquent ne peuvent pas fournir la sécurité du type à moins que certains de mécanisme explicite (type-checking) est mis en œuvre.

272voto

dimo414 Points 7128

Un rapide test confirme ce faire, vous pouvez également:

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

Mon test:

public class ArrTest<E> {
  public static void main(String[] args){
    ArrTest<String> t = new ArrTest<String>();
    t.test("Hello World");
  }

  public void test(E a){
    E[] b = (E[])new Object[1];
    b[0] = a;
    System.out.println(b[0]);
  }
}

Pas d'avertissement, pas d'erreurs de type, pas besoin de convertir le tableau à plusieurs reprises. CEPENDANT c'est potentiellement dangereux, et doivent être utilisées avec prudence. Comme détaillé dans les commentaires, ce Object[] est désormais passer pour notre E[] type, et peuvent entraîner des erreurs inattendues ou ClassCastExceptions si elle est utilisée de manière non sûre.

En règle générale, ce problème est en sécurité tant que le casting tableau est utilisé en interne, et n'est pas retourné ou exposés à code client. Si vous avez besoin de retourner un tableau d'un type générique à un autre code, le reflet Array classe que vous mentionnez est la bonne façon de faire.


Utile de mentionner que, dans la mesure du possible, vous aurez une bien plus heureux temps de travail avec Lists plutôt que les tableaux si vous utilisez des médicaments génériques. Certes, parfois, vous n'avez pas le choix, mais en utilisant les collections cadre est bien plus robuste.

72voto

gdejohn Points 1106

Cela peut être un peu tard pour la durée de votre affectation, climatewarrior, mais pour ceux qui s'intéressent à l'utilisation de génériques pour obtenir un tableau d'précisément le type que vous cherchez, tout en préservant la sécurité de type (par opposition a d'autres suggestions, qui seront soit vous redonner un Object tableau ou d'entraîner des avertissements lors de la compilation), je pense que j'ai trouvé une solution.

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 compile sans mise en garde, et comme vous pouvez le voir en main, quel que soit le type de vous déclarer une instance d' GenSet comme, vous pouvez affecter a pour un tableau de ce type, et vous pouvez affecter un élément à partir d' a d'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 la classe des littéraux d'exécution jetons de type, tel que discuté dans la Java des Tutoriels. Classe littéraux sont traités 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. Donc, String.class des actes en tant que Class objet représentant la classe - String. Cela fonctionne aussi pour les interfaces, les énumérations, toutes les dimensions des tableaux (par exemple, String[].class), les primitives (par exemple, int.class), et le mot-clé void (c - void.class).

Il est intéressant de noter, Class lui-même est générique (déclarée Class<T>T représente le type de l' Class objet représentant), ce qui signifie que le type d' String.class est Class<String>.

Donc, chaque fois que vous appelez le constructeur de GenSet, vous passez dans une classe littéral pour le premier argument représente un tableau de l' GenSet de l'instance de type déclaré (par exemple, String[].class pour GenSet<String>). Notez que vous ne serez pas en mesure d'obtenir un tableau de primitives, depuis primitives ne peuvent pas être utilisés pour les variables de type.

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

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

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

En va de même pour chaque méthode en Class qui renvoie un Class objet.

Quant à Joachim Sauer commentaire de cette réponse (je n'ai pas assez de réputation pour commenter moi-même), l'exemple d'utilisation de la fonte d' T[] entraînera un avertissement, car 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));
}

46voto

irreputable Points 25577

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

36voto

Jason C Points 14927

Pour s'étendre à plusieurs dimensions, il suffit d'ajouter []'s et les paramètres de dimension à l' newInstance() (T est un paramètre de type, cls est Class<T>, d1 par 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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X