Vanwege de implementatie van Java Generics, kunt u geen code hebben zoals deze:
public class GenSet<E> {
private E a[];
public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
}
}
Hoe kan ik dit implementeren tijdens het handhaven van het type veiligheid?
Ik zag een oplossing op de Java-forums die zo gaat:
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;
}
Maar ik begrijp echt niet wat er aan de hand is.
1, Autoriteit 100%
Ik moet een vraag stellen in ruil: Is uw GenSet
“gecontroleerd” of “ongecontroleerd”?
Wat betekent dat?
-
gecontroleerd : sterk typen .
GenSet
Weet expliciet welk type objecten het bevat (dwz zijn constructeur werd expliciet gebeld met eenClass<E>
argument en methoden zullen een uitzondering gooien wanneer ze argumenten zijn zijn niet van het typeE
. ZieCollections.checkedCollection
.– & GT; In dat geval moet u schrijven:
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]; } }
-
Niet aangevinkt: slecht typen. Er wordt geen typecontrole uitgevoerd op een van de objecten die als argument zijn doorgegeven.
-> in dat geval moet je schrijven
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; } }
Merk op dat het componenttype van de array moet zijn wissenvan de typeparameter:
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]; } ... }
Dit alles is het gevolg van een bekende en opzettelijke zwakte van generieke geneesmiddelen in Java: het werd geïmplementeerd met behulp van wissen, dus “generieke” klassen weten niet met welk type argument ze zijn gemaakt tijdens runtime, en kunnen daarom niet typeveiligheid bieden, tenzij een expliciet mechanisme (typecontrole) is geïmplementeerd.
Antwoord 2, autoriteit 33%
U kunt dit doen:
E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];
Dit is een van de voorgestelde manieren om een generieke verzameling te implementeren in Effectieve Java; Punt 26. Geen typefouten, het is niet nodig om de array herhaaldelijk te casten. Echterdit geeft een waarschuwing omdat het potentieel gevaarlijk is en voorzichtig moet worden gebruikt. Zoals beschreven in de opmerkingen, doet dit Object[]
zich nu voor als ons E[]
-type en kan het onverwachte fouten of ClassCastException
s veroorzaken als onveilig gebruikt.
Als vuistregel is dit gedrag veilig zolang de cast-array intern wordt gebruikt (bijvoorbeeld om een gegevensstructuur te ondersteunen) en niet wordt geretourneerd of blootgesteld aan clientcode. Als je een array van een generiek type moet terugsturen naar andere code, dan is de reflectie Array
-klasse die je noemt de juiste manier om te gaan.
Het is het vermelden waard dat u, waar mogelijk, veel plezier zult beleven aan het werken met List
s in plaats van arrays als u generieke geneesmiddelen gebruikt. Natuurlijk heb je soms geen keus, maar het gebruik van het collectieframework is veel robuuster.
Antwoord 3, autoriteit 9%
Hier leest u hoe u generieke geneesmiddelen kunt gebruiken om een array te krijgen van precies het type waarnaar u op zoek bent, terwijl de typeveiligheid behouden blijft (in tegenstelling tot de andere antwoorden, die u ofwel een Object
-array teruggeven of resulteren in waarschuwingen tijdens het compileren):
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];
}
}
Dat compileert zonder waarschuwingen, en zoals je kunt zien in main
, voor welk type je een exemplaar van GenSet
als, kunt u a
Naar een reeks van dat type, en u kunt een element van a
op een variabele van dat type, wat betekent dat de array en de waarden in de array van het juiste type zijn.
Het werkt met behulp van de klasse-literaten als runtime-type tokens, zoals besproken in de Java-tutorials . Klasse-literaten worden door de compiler behandeld als instanties van java.lang.Class
. Om er een te gebruiken, volgt u gewoon de naam van een klasse met .class
. Dus, String.class
Handelt als een Class
Object vertegenwoordigen de klasse String
. Dit werkt ook voor interfaces, ENUMS, elke dimensionale arrays (bijv. String[].class
), primitieven (bijv. int.class
) en het trefwoord void
(dwz void.class
).
Class
zelf is generiek (gedeclareerd als Class<T>
, waarbij T
staat voor het type dat de Class
Object is vertegenwoordigend), wat betekent dat het type String.class
is Class<String>
.
Dus wanneer u de constructeur voor GenSet
, passeert u een klasse letterlijk voor het eerste argument dat een reeks van de GenSet
Instally-type vertegenwoordigt (bijv. String[].class
voor GenSet<String>
). Merk op dat u geen reeks primitieven kunt krijgen, aangezien Primitives niet voor typevariabelen kunnen worden gebruikt.
In de constructor, belt u de methode cast
RETOURNEREN DE AANGEVOEGD Object
ARGUMENT GEWORDEN NAAR DE KLASSE WERKELD DOOR DE Class
OBJECTIE OP WAARDE heette. De statische methode oproepen newInstance
IN java.lang.reflect.Array
Retourneert als een Object
Een reeks van het type weergegeven door de Class
Object doorgegeven als het eerste argument en van de lengte gespecificeerd door de int
GAAND ALS HET tweede argument. De methode oproepen getComponentType
Retourneert een Class
Object dat het componenttype van de array vertegenwoordigt, vertegenwoordigd door de Class
Object waarop de methode is gebeld (bijv. String.class
Voor String[].class
, null
Als de Class
object geen array weergeven).
die laatste zin is niet helemaal nauwkeurig. Bellen String[].class.getComponentType()
Retourneert een Class
Object dat de klasse String
vertegenwoordigt, maar het type is Class<?>
, NIET Class<String>
, daarom kan u niet iets als het volgende doen.
String foo = String[].class.getComponentType().cast("bar"); // won't compile
Hetzelfde geldt voor elke methode in Class
die een Class
Object retourneert.
over de opmerking van Joachim Sauer op dit antwoord (I Heb niet genoeg reputatie om er zelf op te reageren), het voorbeeld met behulp van de cast naar T[]
zal resulteren in een waarschuwing omdat de compiler in dat geval geen type veiligheid kan garanderen.
Bewerken met betrekking tot Ingo’s opmerkingen:
public static <T> T[] newArray(Class<T[]> type, int size) {
return type.cast(Array.newInstance(type.getComponentType(), size));
}
Antwoord 4, autoriteit 6%
Dit is het enige antwoord dat veilig is
E[] a;
a = newArray(size);
@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
return Arrays.copyOf(array, length);
}
Antwoord 5, autoriteit 5%
Om uit te breiden naar meer dimensies, voegt u gewoon []
‘s en dimensieparameters toe aan newInstance()
(T
is een typeparameter, cls
is een Class<T>
, d1
tot en met d5
zijn gehele getallen):
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);
Zie Array.newInstance()
voor details.
Antwoord 6, autoriteit 2%
In Java 8 kunnen we een soort generieke array-creatie doen met behulp van een lambda- of methodereferentie. Dit is vergelijkbaar met de reflectieve benadering (die een Class
doorstaat), maar hier gebruiken we geen reflectie.
@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);
}
}
Bijvoorbeeld, deze wordt gebruikt door <A> A[] Stream.toArray(IntFunction<A[]>)
kan ook gedaan pre-Java 8 met behulp van anonieme klassen zijn, maar het is meer omslachtig.
7, Autoriteit 2%
Dit wordt besproken in hoofdstuk 5 (Generics) van Effective Java, 2nd Edition , punt 25 … liever lijsten arrays
Uw code zal werken, hoewel het een niet-gecontroleerde waarschuwing (die je zou kunnen onderdrukken met de volgende annotatie zal genereren:
@SuppressWarnings({"unchecked"})
Maar het zou waarschijnlijk beter zijn om een lijst te gebruiken in plaats van een array.
Er is een interessante discussie over deze bug / functie op de OpenJDK-project site .
8
U hoeft niet aan de Klasse argument aan de constructor passeren.
Probeer deze.
public class GenSet<T> {
private final T[] array;
@SafeVarargs
public GenSet(int capacity, T... dummy) {
if (dummy.length > 0)
throw new IllegalArgumentException(
"Do not provide values for dummy argument.");
this.array = Arrays.copyOf(dummy, capacity);
}
@Override
public String toString() {
return "GenSet of " + array.getClass().getComponentType().getName()
+ "[" + array.length + "]";
}
}
en
GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));
resultaat:
GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]
Antwoord 9
Java-generieken werken door typen te controleren tijdens het compileren en de juiste casts in te voegen, maar de typen in de gecompileerde bestanden wissen. Dit maakt generieke bibliotheken bruikbaar door code die generieke bibliotheken niet begrijpt (wat een bewuste ontwerpbeslissing was), maar wat betekent dat je normaal gesproken niet kunt achterhalen wat het type is tijdens runtime.
De openbare Stack(Class<T> clazz,int capacity)
-constructor vereist dat u tijdens runtime een Class-object doorgeeft, wat betekent dat klasse-informatie isbeschikbaar tijdens runtime om te coderen die het nodig heeft. En het Class<T>
-formulier betekent dat de compiler zal controleren of het Class-object dat u doorgeeft precies het Class-object voor type T is. Geen subklasse van T, geen superklasse van T, maar juist T .
Dit betekent dan dat u een array-object van het juiste type in uw constructor kunt maken, wat betekent dat het type van de objecten die u in uw verzameling opslaat, wordt gecontroleerd op het moment dat ze aan de verzameling worden toegevoegd.
Antwoord 10
Hoewel de thread dood is, wil ik je hier graag op attenderen.
Generics worden gebruikt voor typecontrole tijdens compileertijd. Daarom is het doel om
te controleren
- Wat komt erin is wat je nodig hebt.
- Wat u terugkeert, is wat de consumentenbehoeften nodig heeft.
Controleer dit:
Maak je geen zorgen over het typen van waarschuwingen als je een generieke klasse schrijft; zorgen wanneer u het gebruikt.
11
Hoe zit het met deze oplossing?
@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
return elems;
}
Het werkt en ziet er te eenvoudig uit om waar te zijn. Is er een nadeel?
12
kijk ook naar deze 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;
}
Het converteert een lijst met een of andere vorm van een object naar een reeks van hetzelfde type.
13
Ik heb een snelle en gemakkelijke manier gevonden die voor mij werkt. Merk op dat ik dit alleen heb gebruikt op Java JDK 8. Ik weet niet of het met eerdere versies zal werken.
Hoewel we geen generieke reeks van een specifieke typeparameter kunnen oplossen, kunnen we een reeds gemaakte array doorgeven aan een generieke klassenconstructeur.
class GenArray <T> {
private T theArray[]; // reference array
// ...
GenArray(T[] arr) {
theArray = arr;
}
// Do whatever with the array...
}
Nu kunnen we in main de array als volgt maken:
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
// ...
}
}
Voor meer flexibiliteit met uw arrays kunt u een gekoppelde lijst gebruiken, bijv. de ArrayList en andere methoden in de klasse Java.util.ArrayList.
Antwoord 14
Een lijst met waarden doorgeven…
public <T> T[] array(T... values) {
return values;
}
Antwoord 15
Het voorbeeld gebruikt Java-reflectie om een array te maken. Dit wordt over het algemeen niet aanbevolen, omdat het niet typeveilig is. In plaats daarvan moet je gewoon een interne lijst gebruiken en de array helemaal vermijden.
Antwoord 16
Ik heb dit codefragment gemaakt om reflectief een klasse te instantiëren die is geslaagd voor een eenvoudig geautomatiseerd testhulpprogramma.
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});
}
Let op dit segment:
if(clazz.isArray()){
Class<?> arrayType = clazz.getComponentType();
attributeValue = Array.newInstance(arrayType, 0);
}
voor het starten van een array waar Array.newInstance(klasse van array, grootte van array). Klasse kan zowel primitief (int.class) als object (Integer.class) zijn.
BeanUtils is onderdeel van Spring.
Antwoord 17
Een eenvoudigere manier om dit te doen, is door een array van objecten te maken en deze naar het gewenste type te casten, zoals in het volgende voorbeeld:
T[] array = (T[])new Object[SIZE];
waar SIZE
een constante is en T
een type-ID is
Antwoord 18
De gedwongen cast die door andere mensen werd voorgesteld, werkte niet voor mij, met uitzondering van illegale casting.
Deze impliciete cast werkte echter prima:
Item<K>[] array = new Item[SIZE];
waar Item een klasse is die ik heb gedefinieerd die het lid bevat:
private K value;
Op deze manier krijg je een array van het type K (als het item alleen de waarde heeft) of een willekeurig generiek type dat je wilt definiëren in de klasse Item.
Antwoord 19
Niemand anders heeft de vraag beantwoord wat er aan de hand is in het voorbeeld dat je hebt gepost.
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;
}
Zoals anderen al hebben gezegd, worden generieke geneesmiddelen “gewist” tijdens het compileren. Dus tijdens runtime weet een instantie van een generiek niet wat het componenttype is. De reden hiervoor is historisch, Sun wilde generieke geneesmiddelen toevoegen zonder de bestaande interface (zowel bron als binair) te verbreken.
Arrays daarentegen wetenhun componenttype tijdens runtime.
Dit voorbeeld lost het probleem op door de code die de constructor aanroept (die het type wel kent) een parameter te laten doorgeven die de klasse het vereiste type vertelt.
Dus de toepassing zou de klasse construeren met iets als
Stack<foo> = new Stack<foo>(foo.class,50)
en de constructor weet nu (tijdens runtime) wat het componenttype is en kan die informatie gebruiken om de array te construeren via de reflectie-API.
Array.newInstance(clazz, capacity);
Eindelijk hebben we een typecast omdat de compiler niet kan weten of de array die wordt geretourneerd door Array#newInstance()
het juiste type is (ook al weten we dat).
Deze stijl is een beetje lelijk, maar het kan soms de minst slechte oplossing zijn voor het maken van generieke typen die om welke reden dan ook hun componenttype tijdens runtime moeten kennen (arrays maken of instanties van hun componenttype maken, enz.) .
20
U kunt een voorwerpsarray maken en overal naar E casten. Ja, het is niet erg schoon om het te doen, maar het moet op zijn minst werken.
21
Probeer dit.
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>();
}
}
}
22
Een eenvoudige, zij het rommelige oplossing hierop zou zijn om een tweede “houder” klasse in uw hoofdklasse te nestelen en deze te gebruiken om uw gegevens vast te houden.
public class Whatever<Thing>{
private class Holder<OtherThing>{
OtherThing thing;
}
public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
23
Misschien niet gerelateerd aan deze vraag, maar terwijl ik de foutmelding “generic array creation
” kreeg voor het gebruik van
Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];
Ik ontdek de volgende werken (en werkte voor mij) met @SuppressWarnings({"unchecked"})
:
Tuple<Long, String>[] tupleArray = new Tuple[10];
Antwoord 24
Ik vraag me af of deze code een effectieve generieke array zou creëren?
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;
}
Bewerken: misschien is een alternatieve manier om zo’n array te maken, als de gewenste grootte bekend en klein was, gewoon het vereiste aantal “null”-en in het zeroArray-commando in te voeren?
Hoewel dit natuurlijk niet zo veelzijdig is als het gebruik van de createArray-code.
Antwoord 25
Je zou een cast kunnen gebruiken:
public class GenSet<Item> {
private Item[] a;
public GenSet(int s) {
a = (Item[]) new Object[s];
}
}
Antwoord 26
Ik heb eigenlijk een vrij unieke oplossing gevonden om het onvermogen om een generieke array te starten te omzeilen. Wat je moet doen is een klasse maken die de generieke variabele T als volgt inneemt:
class GenericInvoker <T> {
T variable;
public GenericInvoker(T variable){
this.variable = variable;
}
}
en laat het dan in je array-klasse zo beginnen:
GenericInvoker<T>[] array;
public MyArray(){
array = new GenericInvoker[];
}
het starten van een new Generic Invoker[]
zal een probleem veroorzaken met niet aangevinkt, maar er zouden eigenlijk geen problemen moeten zijn.
Om uit de array te komen, moet je de array[i].variabele als volgt aanroepen:
public T get(int index){
return array[index].variable;
}
De rest, zoals het vergroten of verkleinen van de array, kan als volgt met Arrays.copyOf() worden gedaan:
public void resize(int newSize){
array = Arrays.copyOf(array, newSize);
}
En de toevoegfunctie kan als volgt worden toegevoegd:
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++;
}
Antwoord 27
Volgens vnportnoy de syntaxis
GenSet<Integer> intSet[] = new GenSet[3];
creëert een array van null-referenties, in te vullen als
for (int i = 0; i < 3; i++)
{
intSet[i] = new GenSet<Integer>();
}
die type veilig is.
28
private E a[];
private int size;
public GenSet(int elem)
{
size = elem;
a = (E[]) new E[size];
}
29
Generieke array-creatie is niet toegestaan in Java, maar u kunt het doen als
class Stack<T> {
private final T[] array;
public Stack(int capacity) {
array = (T[]) new Object[capacity];
}
}