web-dev-qa-db-fra.com

Pourquoi ne puis-je pas créer de types de tableaux génériques en Java?

Quelle est la raison pour laquelle Java ne nous permet pas de faire

private T[] elements = new T[initialCapacity];

Je pouvais comprendre que .NET ne nous permettait pas de le faire, car dans .NET, vous avez des types de valeur qui au moment de l’exécution peuvent avoir des tailles différentes, mais en Java, tous les types de T seront des références d’objet, donc de même taille Corrige moi si je me trompe).

Quelle est la raison?

237
devoured elysium

C'est parce que les tableaux de Java (contrairement aux génériques) contiennent, au moment de l'exécution, des informations sur son type de composant. Vous devez donc connaître le type de composant lorsque vous créez le tableau. Étant donné que vous ne savez pas ce que T correspond à l'exécution, vous ne pouvez pas créer le tableau.

180
newacct

Citation:

Les tableaux de types génériques ne sont pas autorisés parce qu'ils ne sont pas sains. Le problème est dû à l'interaction de Tableaux Java, qui ne sont pas statiques sonores mais sont vérifiés dynamiquement, avec des génériques, qui sont statiques sain et non vérifié dynamiquement . Voici comment vous pouvez exploiter le échappatoire:

class Box<T> {
    final T x;
    Box(T x) {
        this.x = x;
    }
}

class Loophole {
    public static void main(String[] args) {
        Box<String>[] bsa = new Box<String>[3];
        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3); // error not caught by array store check
        String s = bsa[0].x; // BOOM!
    }
}

Nous avions proposé de résoudre ce problème problème d'utilisation des tableaux statiquement sûrs (aka Variance) bute qui a été rejeté pour Tiger.

- gafter

(Je crois que c'est Neal Gafter , mais je ne suis pas sûr)

Voir le contexte ici: http://forums.Sun.com/thread.jspa?threadID=457033&forumID=316

131
Bart Kiers

En omettant de fournir une solution décente, vous vous retrouvez avec quelque chose de pire à mon humble avis.

Le travail commun autour est comme suit.

T[] ts = new T[n];

est remplacé par (en supposant que T étend Object et non une autre classe)

T[] ts = (T[]) new Object[n];

Je préfère le premier exemple, mais des types plus académiques semblent préférer le second, ou préfèrent tout simplement ne rien en dire. 

La plupart des exemples de pourquoi vous ne pouvez pas simplement utiliser un objet [] s'appliquent également à List ou Collection (qui sont supportés), je les vois donc comme de très mauvais arguments.

Remarque: c'est l'une des raisons pour lesquelles la bibliothèque de collections elle-même ne compile pas sans avertissements. Si vous ne pouvez pas prendre en charge ce cas d'utilisation sans avertissements, quelque chose est fondamentalement cassé avec le modèle générique IMHO.

39
Peter Lawrey

Cela est impossible parce que Java implémente ses génériques uniquement au niveau du compilateur, et qu’un seul fichier de classe est généré pour chaque classe . Ceci est appelé Type Erasure .

Au moment de l'exécution, la classe compilée doit gérer toutes ses utilisations avec le même bytecode. Donc, new T[capacity] n'aurait absolument aucune idée du type à instancier.

27
Durandal

Les tableaux sont covariants

Les tableaux sont dits covariants, ce qui signifie fondamentalement que, compte tenu des règles de sous-typage de Java, un tableau de type T [] peut contenir des éléments de type T ou tout sous-type de T. Par exemple:

Number[] numbers = newNumber[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);

Mais non seulement cela, les règles de sous-typage de Java indiquent également qu'un tableau S [] est un sous-type du tableau T [] si S est un sous-type de T, par conséquent, quelque chose comme ceci est également valide:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

En raison des règles de sous-typage en Java, un tableau Integer [] est un sous-type d'un tableau Number [], car Integer est un sous-type de Number.

Mais cette règle de sous-typage peut conduire à une question intéressante: que se passerait-il si nous essayions de faire cela?

myNumber[0] = 3.14; //attempt of heap pollution

Cette dernière ligne compilerait parfaitement, mais si nous exécutons ce code, nous obtiendrions une ArrayStoreException car nous essayons de placer un double dans un tableau d’entiers. Le fait que nous accédions au tableau par le biais d'une référence à Number n'est pas pertinent ici. Ce qui compte, c'est que le tableau soit un tableau d'entiers.

Cela signifie que nous pouvons tromper le compilateur, mais nous ne pouvons pas tromper le système de types à l'exécution. Et c’est parce que les tableaux sont ce que nous appelons un type réifiable. Cela signifie qu’au moment de l’exécution, Java sait que ce tableau a été réellement instancié sous la forme d’un tableau d’entiers auquel il est simplement possible d’accéder via une référence de type Number [].

Donc, comme nous pouvons le constater, une chose est le type réel de l'objet, une autre chose est le type de la référence que nous utilisons pour y accéder, n'est-ce pas?

Le problème avec les génériques Java

Maintenant, le problème avec les types génériques en Java est que les informations de type pour les paramètres de type sont ignorées par le compilateur une fois la compilation du code terminée; par conséquent, cette information de type n'est pas disponible au moment de l'exécution. Ce processus s'appelle effacement de type. Il existe de bonnes raisons d’appliquer de tels génériques en Java, mais c’est une longue histoire qui concerne la compatibilité binaire avec du code préexistant.

Le point important ici est qu’étant donné qu’au moment de l’exécution, il n’existe aucune information de type, il n’existe aucun moyen de s’assurer que nous ne commettons pas de pollution en tas.

Voyons maintenant le code dangereux suivant:

List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution

Si le compilateur Java ne nous en empêche pas, le système de types au moment de l'exécution ne peut pas non plus nous arrêter, car il est impossible, au moment de l'exécution, de déterminer que cette liste était supposée être une liste d'entiers uniquement. L'exécution de Java nous laisserait mettre ce que nous voulons dans cette liste, alors qu'elle ne devrait contenir que des entiers, car lors de sa création, elle était déclarée sous forme de liste d'entiers. C’est la raison pour laquelle le compilateur rejette la ligne 4 car elle est dangereuse et, si elle est autorisée, pourrait casser les hypothèses du système de types.

En tant que tels, les concepteurs de Java ont veillé à ce que nous ne puissions pas tromper le compilateur. Si nous ne pouvons pas tromper le compilateur (comme nous pouvons le faire avec des tableaux), nous ne pouvons pas non plus tromper le système de types au moment de l'exécution.

En tant que tels, nous disons que les types génériques ne peuvent pas être revérifiés car, au moment de l'exécution, nous ne pouvons pas déterminer la véritable nature du type générique.

J'ai sauté certaines parties de cette réponse, vous pouvez lire l'article complet ici: https://dzone.com/articles/covariance-and-contravariance

22
Humoyun

La réponse a déjà été donnée, mais si vous avez déjà une instance de T, vous pouvez le faire:

T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);

J'espère pouvoir vous aider, Ferdi265

18
Ferdi265

La raison principale est due au fait que les tableaux en Java sont covariants.

Il y a un bon aperçu ici .

4
GaryF

J'aime la réponse donnée indirectementby Gafter . Cependant, je propose que ce soit faux. J'ai un peu changé le code de Gafter. Il compile et fonctionne pendant un moment puis il bombe où Gafter prédit qu'il serait

class Box<T> {

    final T x;

    Box(T x) {
        this.x = x;
    }
}

class Loophole {

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

    public static void main(String[] args) {

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3);
        System.out.println("error not caught by array store check");

        try {
            String s = bsa[0].x;
        } catch (ClassCastException cause) {
            System.out.println("BOOM!");
            cause.printStackTrace();
        }
    }
}

La sortie est

I created an array of generics.
error not caught by array store check
BOOM!
Java.lang.ClassCastException: Java.lang.Integer cannot be cast to Java.lang.String
    at Loophole.main(Box.Java:26)

Il me semble donc que vous pouvez créer des types de tableaux génériques en Java. Ai-je mal compris la question?

4
emory

De tutoriel Oracle :

Vous ne pouvez pas créer de tableaux de types paramétrés. Par exemple, le code suivant ne compile pas:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

Le code suivant illustre ce qui se passe lorsque différents types sont insérés dans un tableau:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

Si vous essayez la même chose avec une liste générique, il y aurait un problème:

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

Si les tableaux de listes paramétrées étaient autorisés, le code précédent ne générerait pas l'exception ArrayStoreException souhaitée.

Pour moi, cela semble très faible. Je pense que quiconque ayant une compréhension suffisante des génériques serait parfaitement d'accord, et même s'attend à ce que l'exception ArrayStoredException ne soit pas levée dans ce cas.

2
Stick Hero

Dans mon cas, je voulais simplement un tableau de piles, quelque chose comme ceci:

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

Comme cela n’était pas possible, j’ai utilisé la solution suivante:

  1. Création d'une classe de wrapper non générique autour de Stack (par exemple MyStack)
  2. MyStack [] stacks = new MyStack [2] a parfaitement fonctionné

Moche, mais Java est heureux.

Remarque: comme mentionné par BrainSlugs83 dans le commentaire de la question, il est tout à fait possible d’avoir des tableaux de génériques dans .NET.

2

Je sais que je suis un peu en retard pour la fête ici, mais je me suis dit que je pourrais peut-être aider les futurs googleurs, car aucune de ces réponses n'a résolu mon problème. La réponse de Ferdi265 a cependant énormément aidé.

J'essaie de créer ma propre liste liée, le code suivant a donc fonctionné pour moi:

package myList;
import Java.lang.reflect.Array;

public class MyList<TYPE>  {

    private Node<TYPE> header = null;

    public void clear() {   header = null;  }

    public void add(TYPE t) {   header = new Node<TYPE>(t,header);    }

    public TYPE get(int position) {  return getNode(position).getObject();  }

    @SuppressWarnings("unchecked")
    public TYPE[] toArray() {       
        TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());        
        for(int i=0 ; i<size() ; i++)   result[i] = get(i); 
        return result;
    }


    public int size(){
         int i = 0;   
         Node<TYPE> current = header;
         while(current != null) {   
           current = current.getNext();
           i++;
        }
        return i;
    }  

Dans la méthode toArray () se trouve le moyen de créer un tableau d'un type générique pour moi:

TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());    
1
Derek Ziemba

Si nous ne pouvons pas instancier des tableaux génériques, pourquoi le langage contient-il des types de tableaux génériques? Quel est l'intérêt d'avoir un type sans objets?

La seule raison pour laquelle je peux penser, est varargs - foo(T...). Sinon, ils pourraient avoir complètement nettoyé les types de tableaux génériques. (Eh bien, ils n'avaient pas vraiment besoin d'utiliser array pour varargs, car ils n'existaient pas avant 1.5 . C'est probablement une autre erreur.)

Donc, c'est un mensonge, vous pouvez instancier des tableaux génériques, à travers varargs!

Bien sûr, les problèmes avec les tableaux génériques sont toujours réels, par exemple.

static <T> T[] foo(T... args){
    return args;
}
static <T> T[] foo2(T a1, T a2){
    return foo(a1, a2);
}

public static void main(String[] args){
    String[] x2 = foo2("a", "b"); // heap pollution!
}

Nous pouvons utiliser cet exemple pour démontrer réellement le danger de generic array.

D'autre part, nous utilisons des varargs génériques depuis une décennie et le ciel ne s'annonce pas encore. Nous pouvons donc affirmer que les problèmes sont exagérés; ce n'est pas grave. Si la création explicite de tableaux génériques est autorisée, nous aurons des bugs ici et là; mais nous sommes habitués aux problèmes d’effacement et nous pouvons vivre avec.

Et nous pouvons nous référer à foo2 pour réfuter l’affirmation selon laquelle les spécifications nous préservent des problèmes qu’ils prétendent nous garder. Si Sun avait plus de temps et de ressources pour 1,5 , je pense qu’ils auraient pu parvenir à une résolution plus satisfaisante.

0
ZhongYu

Comme d’autres déjà mentionné, vous pouvez bien sûr créer via quelques astuces .

Mais ce n'est pas recommandé.

Parce que le type effacement et plus important encore le covariancedans un tableau qui autorise simplement un tableau de sous-type peut être affecté à un tableau de super-type, ce qui vous oblige à utiliser un transtypage de type explicite lorsque vous essayez de récupérer la valeur, ce qui provoque l'exécution ClassCastExceptionest l’un des principaux objectifs que les génériques tentent d’éliminer: Contrôles de type plus stricts au moment de la compilation .

Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

Un exemple plus direct peut être trouvé dans Effective Java: Item 25 .


covariance: un tableau de type S [] est un sous-type de T [] si S est un sous-type de T

0
Hearen

Il doit sûrement y avoir un bon moyen de contourner le problème (peut-être en utilisant la réflexion), car il me semble que c’est exactement ce que fait ArrayList.toArray(T[] a). Je cite:

public <T> T[] toArray(T[] a)

Renvoie un tableau contenant tous les éléments de cette liste dans le bon ordre. le type d'exécution du tableau retourné est celui du tableau spécifié. Si la liste rentre dans le tableau spécifié, elle y est retournée. Sinon, un nouveau tableau est alloué avec le type d'exécution du tableau spécifié et la taille de cette liste.

Vous pouvez donc utiliser cette fonction, c’est-à-dire créer un ArrayList des objets de votre choix dans le tableau, puis utiliser toArray(T[] a) pour créer le tableau actuel. Ce ne serait pas rapide, mais vous n'avez pas mentionné vos exigences.

Alors, est-ce que quelqu'un sait comment toArray(T[] a) est implémenté?

0
Adam

C’est parce que des génériques ont été ajoutés à Java après leur fabrication. C’est donc un peu maladroit, car les concepteurs originaux de Java pensaient que lors de la création d’un tableau, le type serait spécifié lors de sa création. Donc, cela ne fonctionne pas avec les génériques, donc vous devez faire E [] array = (E []) nouvel objet [15]; Ceci compile mais donne un avertissement. 

0
Alvin

Essaye ça:

List<?>[] arrayOfLists = new List<?>[4];
0
Jorge Washington