En raison de l'implémentation de génériques Java, vous ne pouvez pas avoir de code comme celui-ci:
public class GenSet<E> {
private E a[];
public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
}
}
Comment puis-je implémenter cela tout en maintenant la sécurité des types?
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.
java
arrays
generics
reflection
instantiation
tatsuhirosatou
la source
la source
Réponses:
Je dois vous poser une question en retour: est-ce que vous
GenSet
êtes "coché" ou "non coché"? Qu'est-ce que ça veut dire?Vérifié : frappe forte .
GenSet
sait explicitement quel type d'objets il contient (c'est-à-dire que son constructeur a été explicitement appelé avec unClass<E>
argument, et les méthodes lèveront une exception quand on leur passera des arguments qui ne sont pas de typeE
. VoirCollections.checkedCollection
.-> dans ce cas, vous devez écrire:
Décoché : frappe faible . Aucune vérification de type n'est effectuée sur aucun des objets passés en argument.
-> dans ce cas, vous devez écrire
Notez que le type de composant du tableau doit être l' effacement du paramètre type:
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 elles ont été créées au moment de l'exécution, et ne peuvent donc pas fournir de type- sécurité à moins qu'un mécanisme explicite (vérification de type) ne soit implémenté.
la source
Object[] EMPTY_ELEMENTDATA = {}
pour le stockage. Puis-je utiliser ce mécanisme pour redimensionner sans connaître le type à l'aide de génériques?public void <T> T[] newArray(Class<T> type, int length) { ... }
Tu peux le faire:
C'est l'un des moyens suggérés pour implémenter une collection générique dans Java efficace; Point 26 . Aucune erreur de type, pas besoin de transtyper le tableau à plusieurs reprises. Cependant, cela déclenche un avertissement car il est potentiellement dangereux et doit être utilisé avec prudence. Comme détaillé dans les commentaires, cela se
Object[]
fait maintenant passer pour notreE[]
type et peut provoquer des erreurs inattendues ouClassCastException
s s'il est utilisé de manière non sécurisée.En règle générale, ce comportement est sûr tant que le tableau de cast est utilisé en interne (par exemple pour sauvegarder une structure de données), et non retourné ou exposé au code client. Si vous devez renvoyer un tableau d'un type générique vers un autre code, la
Array
classe de réflexion que vous mentionnez est la bonne solution.Il convient de mentionner que dans la mesure du possible, vous aurez beaucoup plus de plaisir à travailler avec des
List
s plutôt qu'avec des tableaux si vous utilisez des génériques. Certes, parfois, vous n'avez pas le choix, mais l'utilisation du cadre de collections est beaucoup plus robuste.la source
String[] s=b;
dans latest()
méthode ci-dessus . C'est parce que le tableau de E n'est pas vraiment, c'est Object []. Cela importe si vous voulez, par exemple unList<String>[]
- vous ne pouvez pas utiliser unObject[]
pour cela, vous devez en avoir unList[]
spécifiquement. C'est pourquoi vous devez utiliser la création de tableau de classe <?> Reflétée.public E[] toArray() { return (E[])internalArray.clone(); }
quandinternalArray
est tapé commeE[]
, et est donc en fait unObject[]
. Cela échoue au moment de l'exécution avec une exception de conversion de type car unObject[]
ne peut pas être affecté à un tableau de n'importe quel typeE
.E[] b = (E[])new Object[1];
vous pouvez clairement voir que la seule référence au tableau créé estb
et que le type deb
estE[]
. Par conséquent, il n'y a aucun risque que vous accédiez accidentellement au même tableau via une variable différente d'un type différent. Si au lieu de cela, vous l'avez fait,Object[] a = new Object[1]; E[]b = (E[])a;
vous devrez être paranoïaque quant à la façon dont vous l'utiliseza
.Voici comment utiliser les génériques pour obtenir un tableau du type précis que vous recherchez tout en préservant la sécurité du type (contrairement aux autres réponses, qui vous redonneront un
Object
tableau ou entraîneront des avertissements au moment de la compilation):Cela se compile sans avertissements, et comme vous pouvez le voir
main
, pour n'importe quel type dont vous déclarez une instanceGenSet
as, vous pouvez l'affectera
à un tableau de ce type, et vous pouvez affecter un élément dea
à une variable de ce type, ce qui signifie que le tableau et les valeurs du tableau sont du type correct.Il fonctionne en utilisant des littéraux de classe comme jetons de type runtime, comme indiqué dans les didacticiels Java . Les littéraux de classe sont traités par le compilateur comme des instances de
java.lang.Class
. Pour en utiliser un, suivez simplement le nom d'une classe avec.class
. Agit doncString.class
comme unClass
objet représentant la classeString
. Cela fonctionne également pour les interfaces, les énumérations, les tableaux de n'importe quelle dimension (par exempleString[].class
), les primitives (par exempleint.class
) et le mot clévoid
(par exemplevoid.class
).Class
lui-même est générique (déclaré commeClass<T>
, oùT
représente le type que l'Class
objet représente), ce qui signifie que le type deString.class
estClass<String>
.Ainsi, chaque fois que vous appelez le constructeur pour
GenSet
, vous passez un littéral de classe pour le premier argument représentant un tableau duGenSet
type déclaré de l' instance (par exempleString[].class
pourGenSet<String>
). Notez que vous ne pourrez pas obtenir un tableau de primitives, car les primitives ne peuvent pas être utilisées pour les variables de type.A l'intérieur du constructeur, l'appel de la méthode
cast
renvoie l'Object
argument passé cast à la classe représentée par l'Class
objet sur lequel la méthode a été appelée. L'appel de la méthode statiquenewInstance
dansjava.lang.reflect.Array
renvoie comme unObject
tableau du type représenté par l'Class
objet passé comme premier argument et de la longueur spécifiée parint
passé comme deuxième argument. Appel de la méthodegetComponentType
renvoie unClass
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
pourString[].class
,null
si l'Class
objet ne représente pas un tableau).Cette dernière phrase n'est pas tout à fait exacte. L'appel
String[].class.getComponentType()
renvoie unClass
objet représentant la classeString
, mais son type ne l'estClass<?>
pasClass<String>
, c'est pourquoi vous ne pouvez pas faire quelque chose comme ce qui suit.Il en va de même pour chaque méthode
Class
qui renvoie unClass
objet.Concernant 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 cast pour
T[]
entraînera un avertissement car le compilateur ne peut pas garantir la sécurité du type dans ce cas.Modifier concernant les commentaires d'Ingo:
la source
Ceci est la seule réponse qui est de type sûr
la source
Arrays#copyOf()
est indépendant de la longueur du tableau fourni comme premier argument. C'est intelligent, bien qu'il paie le coût des appels versMath#min()
etSystem#arrayCopy()
, qui ne sont strictement nécessaires pour faire ce travail. docs.oracle.com/javase/7/docs/api/java/util/…E
s'agit d'une variable de type. Le varargs crée un tableau d'effacement deE
quandE
est une variable de type, ce qui la rend peu différente de(E[])new Object[n]
. Veuillez consulter http://ideone.com/T8xF91 . Ce n'est en aucun cas plus sûr que n'importe quelle autre réponse.new E[]
. Le problème que vous avez montré dans votre exemple est un problème d'effacement général, qui n'est pas propre à cette question et à cette réponse.Pour étendre à plus de dimensions, ajoutez simplement
[]
les paramètres s et dimension ànewInstance()
(T
est un paramètre de type,cls
est unClass<T>
, àd1
travers desd5
entiers):Voir
Array.newInstance()
pour plus de détails.la source
En Java 8, nous pouvons faire une sorte de création de tableau générique en utilisant une référence lambda ou une méthode. Ceci est similaire à l'approche réflexive (qui passe a
Class
), mais ici nous n'utilisons pas la réflexion.Par exemple, il est utilisé par
<A> A[] Stream.toArray(IntFunction<A[]>)
.Cela pourrait également être fait avant Java 8 à l'aide de classes anonymes, mais c'est plus lourd.
la source
ArraySupplier
celle-ci, vous pouvez déclarer le constructeur asGenSet(Supplier<E[]> supplier) { ...
et l'appeler avec la même ligne que vous.IntFunction<E[]>
, mais oui c'est vrai.Ceci est couvert dans le chapitre 5 (Generics) de Effective Java, 2e édition , point 25 ... Préférez les listes de tableaux
Votre code fonctionnera, bien qu'il génère un avertissement non contrôlé (que vous pouvez supprimer avec l'annotation suivante:
Cependant, il serait probablement préférable d'utiliser une liste au lieu d'un tableau.
Il y a une discussion intéressante sur ce bug / fonctionnalité sur le site du projet OpenJDK .
la source
Vous n'avez pas besoin de passer l'argument Class au constructeur. Essaye ça.
et
résultat:
la source
Les génériques Java fonctionnent en vérifiant les types au moment de la compilation et en insérant les transtypages appropriés, mais en 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 les génériques (ce qui était une décision de conception délibérée) mais ce qui signifie que vous ne pouvez pas normalement savoir quel est le type au moment de l'exécution.
Le
Stack(Class<T> clazz,int capacity)
constructeur public vous oblige à passer un objet Class au moment de l'exécution, ce qui signifie que les informations de classe sont disponibles au moment de l'exécution pour coder qui en a besoin. Et leClass<T>
formulaire signifie que le compilateur vérifiera que l'objet Class que vous passez est précisément l'objet Class 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 tableau du type approprié dans votre constructeur, ce qui signifie que le type des objets que vous stockez dans votre collection verra leurs types vérifiés au moment où ils sont ajoutés à la collection.
la source
Salut bien que le fil soit mort, je voudrais attirer votre attention sur ceci:
Les génériques sont utilisés pour la vérification de type pendant la compilation:
Ne vous inquiétez pas des avertissements de transtypage lorsque vous écrivez une classe générique. Inquiétez-vous lorsque vous l'utilisez.
la source
Et cette solution?
Cela fonctionne et semble trop simple pour être vrai. Y a-t-il un inconvénient?
la source
T[]
, vous ne pouvez pas créer par programmation unT[] elems
pour passer dans la fonction. Et si vous le pouviez, vous n'auriez pas besoin de la fonction.Regardez aussi ce code:
Il convertit une liste de tout type d'objet en un tableau du même type.
la source
List
contient plus d'un type d'objet, par exemple,toArray(Arrays.asList("abc", new Object()))
lanceraArrayStoreException
.for
boucle et d'autres, j'ai utiliséArrays.fill(res, obj);
car je voulais la même valeur pour chaque index.J'ai trouvé un moyen rapide et facile qui fonctionne pour moi. Notez que je l'ai utilisé uniquement sur Java JDK 8. Je ne sais pas si cela fonctionnera 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.
Maintenant, en général, nous pouvons créer le tableau comme suit:
Pour plus de flexibilité avec vos tableaux, vous pouvez utiliser une liste chaînée par exemple. ArrayList et d'autres méthodes trouvées dans la classe Java.util.ArrayList.
la source
L'exemple utilise la réflexion Java pour créer un tableau. Cette opération n'est généralement pas recommandée, car elle n'est pas sécurisée. Au lieu de cela, ce que vous devez faire est simplement d'utiliser une liste interne et d'éviter du tout le tableau.
la source
Passer une liste de valeurs ...
la source
J'ai fait cet extrait de code pour instancier de manière réfléchie une classe qui est passée pour un simple utilitaire de test automatisé.
Notez ce segment:
pour le tableau initiant où Array.newInstance (classe de tableau, taille du tableau) . La classe peut être à la fois primitive (int.class) et objet (Integer.class).
BeanUtils fait partie du printemps.
la source
En fait, un moyen plus simple de le faire consiste à créer un tableau d'objets et à le caster selon le type souhaité, comme dans l'exemple suivant:
où
SIZE
est une constante etT
est un identificateur de typela source
Le casting forcé suggéré par d'autres personnes n'a pas fonctionné pour moi, jetant une exception au casting illégal.
Cependant, cette distribution implicite a bien fonctionné:
où Item est une classe que j'ai définie contenant le membre:
De cette façon, vous obtenez un tableau de type K (si l'élément n'a que la valeur) ou tout type générique que vous souhaitez définir dans la classe Item.
la source
Personne d'autre n'a répondu à la question de savoir ce qui se passe dans l'exemple que vous avez publié.
Comme d'autres l'ont dit, les génériques sont "effacés" lors de la compilation. Ainsi, lors de l'exécution, une instance d'un générique ne sait pas quel est son type de composant. La raison en est historique, Sun voulait ajouter des génériques sans casser l'interface existante (à la fois source et binaire).
Les tableaux, en revanche , connaissent leur type de composant lors de l'exécution.
Cet exemple contourne le problème en faisant passer le code qui appelle le constructeur (qui connaît le type) un paramètre indiquant à la classe le type requis.
Donc, l'application construirait la classe avec quelque chose comme
et le constructeur sait maintenant (au moment de l'exécution) quel est le type de composant et peut utiliser ces informations pour construire le tableau via l'API de réflexion.
Enfin, 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 le savons).Ce style est un peu moche mais il peut parfois être la moins mauvaise solution pour créer des types génériques qui ont besoin de connaître leur type de composant au moment de l'exécution pour une raison quelconque (création de tableaux ou création d'instances de leur type de composant, etc.).
la source
J'ai trouvé une sorte de solution à ce problème.
La ligne ci-dessous renvoie une erreur de création de tableau générique
Cependant, si j'encapsule
List<Person>
dans une classe distincte, cela fonctionne.Vous pouvez exposer des personnes de la classe PersonList via un getter. La ligne ci-dessous vous donnera un tableau, qui a un
List<Person>
dans chaque élément. En d'autres termes, tableau deList<Person>
.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'à présent, aucun problème.
la source
Vous pouvez créer un tableau d'objets et le convertir en E partout. Ouais, ce n'est pas une façon très propre de le faire mais cela devrait au moins fonctionner.
la source
essaye ça.
la source
Element
classe?Une solution de contournement simple, quoique compliquée, consisterait à imbriquer une deuxième classe «titulaire» à l'intérieur de votre classe principale et à l'utiliser pour stocker vos données.
la source
new Holder<Thing>[10]
est une création de tableau générique.Peut-être sans rapport avec cette question, mais pendant que j'obtenais l'
generic array creation
erreur " " pour l'utilisationJ'ai découvert les œuvres suivantes (et travaillé pour moi) avec
@SuppressWarnings({"unchecked"})
:la source
Je me demande si ce code créerait un tableau générique efficace?
Edit: Peut-être qu'une autre façon de créer un tel tableau, si la taille dont vous aviez besoin était connue et petite, serait de simplement introduire le nombre requis de "null" dans la commande zeroArray?
Bien que ce ne soit évidemment pas aussi polyvalent que l'utilisation du code createArray.
la source
T
quandT
est une variable de type, c'est-à-direzeroArray
renvoie unObject[]
. Voir http://ideone.com/T8xF91 .Vous pouvez utiliser un casting:
la source
a
à l'extérieur de la classe!En fait, j'ai trouvé une solution assez unique pour contourner l'incapacité à lancer un tableau générique. Ce que vous devez faire est de créer une classe qui accepte la variable générique T comme ceci:
puis dans votre classe de tableau, commencez comme ceci:
commencer un
new Generic Invoker[]
entraînera un problème avec non coché mais il ne devrait pas y avoir de problème.Pour obtenir du tableau, vous devez appeler le tableau [i] .variable comme ceci:
Le reste, tel que le redimensionnement du tableau peut être fait avec Arrays.copyOf () comme ceci:
Et la fonction d'ajout peut être ajoutée comme suit:
la source
T
, et non un tableau d'un type paramétré.Selon vnportnoy, la syntaxe
crée un tableau de références nulles, à remplir comme
qui est de type sûr.
la source
la source
La création de tableaux génériques n'est pas autorisée en Java, mais vous pouvez le faire comme
la source