Hoe maak je een generieke array in Java?

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 . GenSetWeet expliciet welk type objecten het bevat (dwz zijn constructeur werd expliciet gebeld met een Class<E>argument en methoden zullen een uitzondering gooien wanneer ze argumenten zijn zijn niet van het type E. Zie Collections.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 ClassCastExceptions 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 Lists 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 GenSetals, kunt u aNaar een reeks van dat type, en u kunt een element van aop 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.classHandelt als een ClassObject 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).

Classzelf is generiek (gedeclareerd als Class<T>, waarbij Tstaat voor het type dat de ClassObject is vertegenwoordigend), wat betekent dat het type String.classis Class<String>.

Dus wanneer u de constructeur voor GenSet, passeert u een klasse letterlijk voor het eerste argument dat een reeks van de GenSetInstally-type vertegenwoordigt (bijv. String[].classvoor 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 castRETOURNEREN DE AANGEVOEGD ObjectARGUMENT GEWORDEN NAAR DE KLASSE WERKELD DOOR DE ClassOBJECTIE OP WAARDE heette. De statische methode oproepen newInstanceIN java.lang.reflect.ArrayRetourneert als een ObjectEen reeks van het type weergegeven door de ClassObject doorgegeven als het eerste argument en van de lengte gespecificeerd door de intGAAND ALS HET tweede argument. De methode oproepen getComponentTypeRetourneert een ClassObject dat het componenttype van de array vertegenwoordigt, vertegenwoordigd door de ClassObject waarop de methode is gebeld (bijv. String.classVoor String[].class, nullAls de Classobject geen array weergeven).

die laatste zin is niet helemaal nauwkeurig. Bellen String[].class.getComponentType()Retourneert een ClassObject dat de klasse Stringvertegenwoordigt, 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 Classdie een ClassObject 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()(Tis een typeparameter, clsis een Class<T>, d1tot en met d5zijn 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 Classdoorstaat), 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 SIZEeen constante is en Teen 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];
 }
}

Other episodes