Création d'un tableau générique en Java

1. Introduction

Nous souhaitons peut-être utiliser des tableaux dans le cadre de classes ou de fonctions prenant en charge les génériques. En raison de la façon dont Java gère les génériques, cela peut être difficile.

Dans ce didacticiel, nous allons comprendre les défis liés à l'utilisation de génériques avec des tableaux. Ensuite, nous allons créer un exemple de tableau générique.

Nous verrons également où l'API Java a résolu un problème similaire.

2. Considérations lors de l'utilisation de baies génériques

Une différence importante entre les tableaux et les génériques est la façon dont ils appliquent la vérification de type. Plus précisément, les baies stockent et vérifient les informations de type au moment de l'exécution. Les génériques, cependant, vérifient les erreurs de type au moment de la compilation et ne disposent pas d'informations de type au moment de l'exécution.

La syntaxe de Java suggère que nous pourrions être en mesure de créer un nouveau tableau générique:

T[] elements = new T[size];

Mais, si nous essayions cela, nous obtiendrions une erreur de compilation.

Pour comprendre pourquoi, considérons ce qui suit:

public  T[] getArray(int size) { T[] genericArray = new T[size]; // suppose this is allowed return genericArray; }

Lorsqu'un type T générique indépendant se résout en Object, notre méthode à l'exécution sera:

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

Ensuite, si nous appelons notre méthode et stockons le résultat dans un tableau String :

String[] myArray = getArray(5);

Le code se compilera correctement mais échouera au moment de l'exécution avec une ClassCastException . C'est parce que nous venons d'attribuer un Object [] à une référence String [] . Plus précisément, un cast implicite par le compilateur échouerait à convertir Object [] en notre type requis String [] .

Bien que nous ne puissions pas initialiser directement les tableaux génériques, il est toujours possible de réaliser l'opération équivalente si le type précis d'informations est fourni par le code appelant.

3. Création d'un tableau générique

Pour notre exemple, considérons une structure de données de pile bornée MyStack , où la capacité est fixée à une certaine taille. De plus, comme nous aimerions que la pile fonctionne avec n'importe quel type, un choix d'implémentation raisonnable serait un tableau générique.

Commençons par créer un champ pour stocker les éléments de notre pile, qui est un tableau générique de type E :

private E[] elements;

Deuxièmement, ajoutons un constructeur:

public MyStack(Class clazz, int capacity) { elements = (E[]) Array.newInstance(clazz, capacity); }

Remarquez comment nous utilisons java.lang.reflect.Array # newInstance pour initialiser notre tableau générique , qui nécessite deux paramètres. Le premier paramètre spécifie le type d'objet à l'intérieur du nouveau tableau. Le deuxième paramètre spécifie la quantité d'espace à créer pour le tableau. Comme le résultat de Array # newInstance est de type Object , nous devons le convertir en E [] pour créer notre tableau générique.

Nous devons également noter la convention de nommer un paramètre de type clazz plutôt que class, qui est un mot réservé en Java.

4. Considérant ArrayList

4.1. Utilisation de ArrayList à la place d'un tableau

Il est souvent plus facile d'utiliser une ArrayList générique à la place d'un tableau générique. Voyons comment nous pouvons changer MyStack pour utiliser une ArrayList .

Commençons par créer un champ pour stocker nos éléments:

private List elements;

Deuxièmement, dans notre constructeur de pile, nous pouvons initialiser ArrayList avec une capacité initiale:

elements = new ArrayList(capacity);

Cela rend notre classe plus simple, car nous n'avons pas besoin d'utiliser la réflexion. De plus, nous ne sommes pas obligés de passer un littéral de classe lors de la création de notre pile. Enfin, comme nous pouvons définir la capacité initiale d'un ArrayList , nous pouvons obtenir les mêmes avantages qu'un tableau.

Par conséquent, nous n'avons besoin de construire des tableaux de génériques que dans de rares situations ou lorsque nous interagissons avec une bibliothèque externe qui nécessite un tableau.

4.2. Implémentation ArrayList

Fait intéressant, ArrayList lui-même est implémenté à l'aide de tableaux génériques. Jetons un coup d'œil à l'intérieur de ArrayList pour voir comment.

Voyons d'abord le champ des éléments de la liste:

transient Object[] elementData;

Remarque ArrayList utilise Object comme type d'élément. Comme notre type générique n'est pas connu avant l'exécution, Object est utilisé comme la superclasse de tout type.

Il convient de noter que presque toutes les opérations dans ArrayList peuvent utiliser ce tableau générique car elles n'ont pas besoin de fournir un tableau fortement typé au monde extérieur, à l'exception d'une méthode - toArray !

5. Création d'un tableau à partir d'une collection

5.1. Exemple LinkedList

Examinons l'utilisation de tableaux génériques dans l'API Java Collections, où nous allons créer un nouveau tableau à partir d'une collection.

Tout d'abord, créons une nouvelle LinkedList avec un argument de type String et ajoutons-y des éléments:

List items = new LinkedList(); items.add("first item"); items.add("second item"); 

Deuxièmement, construisons un tableau des éléments que nous venons d'ajouter:

String[] itemsAsArray = items.toArray(new String[0]);

Pour construire notre tableau, le List . La méthode toArray nécessite un tableau d'entrée. Il utilise ce tableau uniquement pour obtenir les informations de type afin de créer un tableau de retour du bon type.

Dans notre exemple ci-dessus, nous avons utilisé new String [0] comme tableau d'entrée pour construire le tableau String résultant .

5.2. Implémentation LinkedList.toArray

Jetons un coup d'œil à LinkedList.toArray , pour voir comment il est implémenté dans le JDK Java.

Tout d'abord, regardons la signature de la méthode:

public  T[] toArray(T[] a)

Deuxièmement, voyons comment un nouveau tableau est créé lorsque cela est nécessaire:

a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);

Remarquez comment il utilise Array # newInstance pour créer un nouveau tableau, comme dans notre exemple de pile précédemment. Notez également comment le paramètre a est utilisé pour fournir un type à Array # newInstance. Enfin, le résultat de Array # newInstance est converti en T [] pour créer un tableau générique.

6. Conclusion

Dans cet article, nous avons d'abord examiné les différences entre les tableaux et les génériques, suivis d'un exemple de création d'un tableau générique. Ensuite, nous avons montré comment utiliser une ArrayList peut être plus facile que d'utiliser un tableau générique. Enfin, nous avons également examiné l'utilisation d'un tableau générique dans l'API Collections.

Comme toujours, l'exemple de code est disponible à l'adresse over sur GitHub.