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.
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 argumentClass<E>
, et les méthodes vont jeter une exception quand ils sont passés des arguments qui ne sont pas de typeE
. VoirCollections.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é.
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.
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));
}
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);
}
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.
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.
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 .
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.
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:
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.
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?
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.
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.
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.
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.
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]
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];
où SIZE
est une constante et T
est un identificateur de type
passant une liste de valeurs...
public <T> T[] array(T... values) {
return values;
}
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.
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.).
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.
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.
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>();
}
}
}
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]
}
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];
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.
vous pouvez utiliser un plâtre:
public class GenSet<Item> {
private Item[] a;
public GenSet(int s) {
a = (Item[]) new Object[s];
}
}
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++;
}
private E a[];
private int size;
public GenSet(int elem)
{
size = elem;
a = (E[]) new E[size];
}
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];
}
}