La relation entre les tableaux et les génériques est encore un peu compliquée. La création directe de tableaux génériques n'est pas autorisée en Java. Cet article analyse les raisons et résume quelques façons de créer des tableaux génériques. A une très bonne valeur de référence. Jetons-y un coup d'œil avec l'éditeur ci-dessous
Introduction
L'article précédent a présenté l'utilisation de base des génériques et le problème de l'effacement de type, jetons maintenant un coup d'œil La relation entre les génériques et les tableaux. Par rapport à la classe conteneur de Java bibliothèque de classes , les tableaux sont spéciaux, se reflétant principalement sous trois aspects :
Une fois le tableau créé, la taille est fixe, mais l'efficacité supérieure
Le tableau peut suivre le type spécifique des éléments stockés à l'intérieur, et le type d'élément inséré sera vérifié au moment de la compilation
Les tableaux peuvent contenir des types primitifs (int, float, etc.), mais avec l'autoboxing, les classes conteneurs semblent pouvoir contenir des types primitifs
Alors, que se passe-t-il lorsqu'un tableau rencontre un générique ? Puis-je créer un tableau générique ? C’est le contenu principal de cet article.
Deux autres articles dans cette série :
Résumé des génériques Java (1) : utilisation de base et effacement de type
Résumé des génériques Java (3) : Utilisation de Wildcard
Tableau générique
Comment pour créer un tableau générique
S'il existe une classe comme suit :
class Generic<T> { }
Si vous souhaitez créer un tableau générique, cela devrait ressembler à ceci : Generic<Integer> ga = new Generic<Integer>[]
Cependant Le code signalera une erreur, ce qui signifie qu'un tableau générique ne pourra pas être créé directement.
Et si vous souhaitez utiliser un tableau générique ? Une solution est d'utiliser ArrayList
, comme dans l'exemple suivant :
public class ListOfGenerics<T> { private List<T> array = new ArrayList<T>(); public void add(T item) { array.add(item); } public T get(int index) { return array.get(index); } }
Comment créer un véritable tableau générique ? On ne peut pas créer directement, mais on peut définir des références de tableaux génériques. Par exemple :
public class ArrayOfGenericReference { static Generic<Integer>[] gia; }
gia
est une référence à un tableau générique, et ce code peut être compilé. Cependant, nous ne pouvons pas créer un tableau de ce type exact, c'est-à-dire que nous ne pouvons pas utiliser new Generic<Integer>[]
Voir l'exemple suivant pour plus de détails :
public class ArrayOfGeneric { static final int SIZE = 100; static Generic<Integer>[] gia; @SuppressWarnings("unchecked") public static void main(String[] args) { // Compiles; produces ClassCastException: //! gia = (Generic<Integer>[])new Object[SIZE]; // Runtime type is the raw (erased) type: gia = (Generic<Integer>[])new Generic[SIZE]; System.out.println(gia.getClass().getSimpleName()); gia[0] = new Generic<Integer>(); //! gia[1] = new Object(); // Compile-time error // Discovers type mismatch at compile time: //! gia[2] = new Generic<Double>(); Generic<Integer> g = gia[0]; } } /*输出: Generic[] *///:~
Les tableaux peuvent suivre le type réel d'éléments. déterminé lorsque le tableau est créé. La ligne de code commentée par ci-dessus : gia = (Generic<Integer>[])new Object[SIZE]
, le tableau est un tableau Objet lors de sa création, et une erreur sera signalée en cas de transformation. La seule façon de réussir à créer un tableau générique est de créer un tableau dont le type est effacé, puis de le convertir, comme dans le code : gia = (Generic<Integer>[])new Generic[SIZE]
, le nom de sortie de l'objet Class de gia est Generic[].
Ma compréhension personnelle est la suivante : en raison de l'effacement du type, Generic<Integer> est équivalent au type initial Generic, donc la transformation dans gia = (Generic<Integer>[])new Generic[SIZE]
est en fait transformée en Generic[], ce qui semble ne pas avoir de transformation. , mais avec la vérification des paramètres et la transformation automatique du compilateur, l'insertion de new Object()
et new Generic<Double>()
dans le tableau signalera une erreur, et le retrait de gia[0] vers Generic<Integer>
ne nous oblige pas à effectuer une transformation manuelle.
Utiliser le tableau T[]
Dans l'exemple ci-dessus, le type de l'élément est une classe générique. Regardons un exemple où le type de l'élément lui-même est un paramètre générique :
public class GenericArray<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz) { array = (T[])new Object[sz]; // 创建泛型数组 } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Method that exposes the underlying representation: public T[] rep() { return array; } //返回数组 会报错 public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); // This causes a ClassCastException: //! Integer[] ia = gai.rep(); // This is OK: Object[] oa = gai.rep(); } }
Dans le code ci-dessus, la création d'un tableau générique consiste à créer un tableau Object puis à le convertir en T[ ]. Mais le type réel du tableau est toujours Object[]. Lors de l'appel de la méthode rep(), une ClassCastException est signalée car Object[] ne peut pas être converti en Integer[].
Ensuite, le code qui crée un tableau générique array = (T[])new Object[sz]
Pourquoi ne signale-t-il pas d'erreur ? Ma compréhension est similaire à ce qui a été introduit auparavant. En raison de l'effacement du type, cela équivaut à une transformation en Object[]
. Il semble qu'il ne soit pas transformé, mais il a une vérification de paramètres supplémentaire et une transformation automatique par le. compilateur. Et si vous changez le paramètre générique en <T extends Integer>
, alors parce que le type est effacé jusqu'à la première limite, array = (T[])new Object[sz]
équivaut à une transformation en Integer[]
, ce qui devrait entraîner une erreur. Voici le code de l'expérience :
public class GenericArray<T extends Integer> { private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz) { array = (T[])new Object[sz]; // 创建泛型数组 } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Method that exposes the underlying representation: public T[] rep() { return array; } //返回数组 会报错 public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); // This causes a ClassCastException: //! Integer[] ia = gai.rep(); // This is OK: Object[] oa = gai.rep(); } }
Par rapport à la version originale, le code ci-dessus n'a modifié que la première ligne, en changeant <T>
en <T extends Integer>
Ensuite, il n'est pas nécessaire d'appeler rep( ), dans Une erreur sera signalée lors de la création d'un tableau générique. Voici le résultat en cours :
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; at GenericArray.<init>(GenericArray.java:15)
Utiliser le tableau Object[]
由于擦除,运行期的数组类型只能是 Object[],如果我们立即把它转型为 T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 Object[] 数组,在取出元素的时候再转型。看下面的例子:
public class GenericArray2<T> { private Object[] array; public GenericArray2(int sz) { array = new Object[sz]; } public void put(int index, T item) { array[index] = item; } @SuppressWarnings("unchecked") public T get(int index) { return (T)array[index]; } @SuppressWarnings("unchecked") public T[] rep() { return (T[])array; // Warning: unchecked cast } public static void main(String[] args) { GenericArray2<Integer> gai = new GenericArray2<Integer>(10); for(int i = 0; i < 10; i ++) gai.put(i, i); for(int i = 0; i < 10; i ++) System.out.print(gai.get(i) + " "); System.out.println(); try { Integer[] ia = gai.rep(); } catch(Exception e) { System.out.println(e); } } } /* Output: (Sample) 0 1 2 3 4 5 6 7 8 9 java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; *///:~
现在内部数组的呈现不是 T[] 而是 Object[],当 get() 被调用的时候数组的元素被转型为 T,这正是元素的实际类型。不过调用 rep() 还是会报错, 因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。
使用类型标识
其实使用 Class 对象作为类型标识是更好的设计:
public class GenericArrayWithTypeToken<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArrayWithTypeToken(Class<T> type, int sz) { array = (T[])Array.newInstance(type, sz); } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Expose the underlying representation: public T[] rep() { return array; } public static void main(String[] args) { GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>( Integer.class, 10); // This now works: Integer[] ia = gai.rep(); } }
在构造器中传入了 Class<T>
对象,通过 Array.newInstance(type, sz)
创建一个数组,这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 Object,而是 T。这个方法返回 Object 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 rep() 方法,因为数组的实际类型与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。
总结
数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!