Jak utworzyć ogólną tablicę w Javie?


1090

Ze względu na implementację generycznych Java, nie możesz mieć takiego kodu:

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

Jak mogę to zaimplementować, zachowując bezpieczeństwo typu?

Na forach Java zobaczyłem takie rozwiązanie:

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;
}

Ale tak naprawdę nie rozumiem, co się dzieje.


14
Czy naprawdę potrzebujesz tutaj użyć tablicy? Co z użyciem kolekcji?
matt b

12
Tak, myślę też, że kolekcje są bardziej eleganckie na ten problem. Ale to jest zadanie klasowe i są one wymagane :(
tatsuhirosatou

3
Nie rozumiem, dlaczego potrzebuję tutaj refleksji. Gramatyka Javy jest dziwna: jak nowa java.util.HashMap <Łańcuch, Łańcuch> [10] jest niepoprawny. new java.util.HashMap <długi, długi> (10) jest niepoprawny. nowa długa [] [10] jest nieprawidłowa, nowa długa [10] [] jest ważna. Te rzeczy sprawiają, że napisanie programu, który potrafi napisać program Java, jest trudniejsze niż się wydaje.
spiżowy mężczyzna

Odpowiedzi:


703

W zamian muszę zadać pytanie: czy Twoje GenSet„zaznaczone” czy „niezaznaczone”? Co to znaczy?

  • Sprawdzone : mocne pisanie . GenSetwie wyraźnie, jaki typ obiektów zawiera (tzn. jego konstruktor został jawnie wywołany z Class<E>argumentem, a metody zgłoszą wyjątek, gdy zostaną przekazane argumenty, które nie są typu E. Zobacz Collections.checkedCollection.

    -> w takim przypadku powinieneś napisać:

    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];
        }
    }
  • Niezaznaczone : słabe pisanie . W rzeczywistości żaden sprawdzanie typu nie jest wykonywane na żadnym obiekcie przekazywanym jako argument.

    -> w takim przypadku powinieneś napisać

    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;
        }
    }

    Zauważ, że typem komponentu tablicy powinno być usunięcie parametru 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];
        }
    
        ...
    }

Wszystko to wynika ze znanej i celowej słabości generyków w Javie: zostało zaimplementowane przy użyciu skasowania, więc klasy „ogólne” nie wiedzą, z jakim argumentem typu zostały utworzone w czasie wykonywania, a zatem nie mogą dostarczyć typu bezpieczeństwo, chyba że wdrożony zostanie jakiś jawny mechanizm (kontrola typu).


7
Jaka byłaby najlepsza opcja pod względem wydajności? Muszę dość często pobierać elementy z tej tablicy (w pętli). Kolekcja jest więc prawdopodobnie wolniejsza, ale który z nich jest najszybszy?
user1111929

3
A jeśli typ ogólny jest ograniczony, tablica podkładowa powinna być typu granicznego.
Mordechai,

5
@AaronDigulla Aby wyjaśnić, że to nie jest przypisanie, ale inicjalizacja zmiennej lokalnej. Nie można dodawać adnotacji do wyrażenia / instrukcji.
kennytm

1
@ Varkhan Czy istnieje sposób na zmianę rozmiaru tych tablic z poziomu implementacji klasy. Na przykład, jeśli chcę zmienić rozmiar po przepełnieniu, jak ArrayList. Sprawdziłem implementację ArrayList, którą mają Object[] EMPTY_ELEMENTDATA = {}do przechowywania. Czy mogę użyć tego mechanizmu do zmiany rozmiaru bez znajomości typu za pomocą generycznych?
JourneyMan

2
Dla tych, którzy chcą stworzyć metodę z typem ogólnym (tego właśnie szukałem), użyj tego:public void <T> T[] newArray(Class<T> type, int length) { ... }
Daniel Kvist

225

Możesz to zrobić:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

Jest to jeden z sugerowanych sposobów implementacji ogólnej kolekcji w Effective Java; Pozycja 26 . Bez błędów typu, nie trzeba wielokrotnie rzutować tablicy. Jednak to wyzwala ostrzeżenie, ponieważ jest potencjalnie niebezpieczny i powinien być stosowany z ostrożnością. Jak wyszczególniono w komentarzach, Object[]teraz jest to maskarada naszego E[]typu i może powodować nieoczekiwane błędy, ClassCastExceptionjeśli zostanie użyta niezgodnie z przeznaczeniem.

Zasadniczo takie zachowanie jest bezpieczne, o ile tablica rzutowania jest używana wewnętrznie (np. Do tworzenia kopii zapasowej struktury danych) i nie jest zwracana ani narażana na kod klienta. Jeśli musisz zwrócić tablicę typu ogólnego do innego kodu, wspomniana Arrayklasa odbicia jest właściwą drogą.


Warto wspomnieć, że tam, gdzie to możliwe, będziesz mieć dużo więcej czasu na pracę z Lists niż z tablicami, jeśli używasz ogólnych. Z pewnością czasami nie masz wyboru, ale korzystanie ze struktury kolekcji jest znacznie bardziej niezawodne.


47
To nie zadziała, jeśli tablica jest traktowana jako tablica dowolnego typu, na przykład String[] s=b;w powyższej test()metodzie. To dlatego, że tablica E nie jest tak naprawdę, to Object []. Ma to znaczenie, jeśli chcesz, np. A List<String>[]- nie możesz użyć Object[]do tego, musisz mieć List[]konkretny. Dlatego musisz użyć odzwierciedlonego tworzenia tablicy klasy <?>.
Lawrence Dol

8
Narożnik / problem występuje, jeśli chcesz to zrobić, na przykład, public E[] toArray() { return (E[])internalArray.clone(); }gdy internalArrayjest wpisany jako E[], a zatem w rzeczywistości jest Object[]. Nie udaje się to w czasie wykonywania z wyjątkiem rzutowania typu, ponieważ Object[]nie można przypisać tablicy do tablicy dowolnego typu E.
Lawrence Dol,

17
Zasadniczo takie podejście będzie działać, o ile nie zwrócisz tablicy, nie przekażesz jej ani nie przechowujesz w miejscu poza klasą, które wymaga tablicy określonego typu. Tak długo, jak jesteś w klasie, wszystko jest w porządku, ponieważ E jest usuwane. Jest to „niebezpieczne”, ponieważ jeśli spróbujesz go zwrócić lub coś takiego, nie otrzymasz ostrzeżenia, że ​​jest niebezpieczne. Ale jeśli jesteś ostrożny, to działa.
newacct

3
To jest całkiem bezpieczne. W E[] b = (E[])new Object[1];wyraźnie widać, że jedynym odwołaniem do utworzonej tablicy jest bi że typem bjest E[]. Dlatego nie ma niebezpieczeństwa przypadkowego dostępu do tej samej tablicy przez inną zmienną innego typu. Jeśli tak, Object[] a = new Object[1]; E[]b = (E[])a; to musiałbyś być paranoikiem na temat tego, jak używasz a.
Aaron McDaid

5
Przynajmniej w Javie 1.6 generuje to ostrzeżenie: „Niezaznaczone rzutowanie z Object [] na T []”
Quantum7

61

Oto jak używać ogólnych, aby uzyskać tablicę dokładnie tego, czego szukasz, przy jednoczesnym zachowaniu bezpieczeństwa typu (w przeciwieństwie do innych odpowiedzi, które albo zwrócą Objecttablicę, albo spowodują pojawienie się ostrzeżeń w czasie kompilacji):

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];  
    }  
}

To się kompiluje bez ostrzeżeń i jak widać w main, dla dowolnego typu deklarowanego jako instancję GenSet, można przypisać ado tablicy tego typu i można przypisać element ado zmiennej tego typu, co oznacza, że ​​tablica a wartości w tablicy są poprawnego typu.

Działa przy użyciu literałów klasowych jako tokenów typu wykonawczego, jak omówiono w samouczkach Java . Literały klas są traktowane przez kompilator jako instancje java.lang.Class. Aby użyć jednego, po prostu podążaj za nazwą klasy za pomocą .class. String.classDziała więc jako Classobiekt reprezentujący klasę String. Działa to również w przypadku interfejsów, wyliczeń, tablic dowolnego wymiaru (np. String[].class), Prymitywów (np. int.class) I słowa kluczowego void(tj void.class.).

Classsam jest ogólny (zadeklarowany jako Class<T>, gdzie Toznacza typ reprezentowany przez Classobiekt), co oznacza, że ​​typem String.classjest Class<String>.

Tak więc, za każdym razem, gdy wywołujesz konstruktor GenSet, przekazujesz literał klasy dla pierwszego argumentu reprezentującego tablicę GenSetzadeklarowanego typu instancji (np. String[].classDla GenSet<String>). Zauważ, że nie będziesz w stanie uzyskać tablicy prymitywów, ponieważ prymitywów nie można używać do zmiennych typu.

Wywołanie metody wewnątrz konstruktora castzwraca przekazany Objectargument rzutowany do klasy reprezentowanej przez Classobiekt, na którym wywołano metodę. Wywołanie metody statycznej newInstancew java.lang.reflect.Arrayzwraca jako Objecttablicę typu reprezentowanego przez Classobiekt przekazany jako pierwszy argument i o długości określonej przez intprzekazany jako drugi argument. Wywołanie metody getComponentTypezwracającej ClassPrzedmiotem reprezentującym typ komponentu tablicy reprezentowane przez Classobiekt na którym sposób został nazwany (na przykład String.classprzez String[].class, nulljeśli Classobiekt nie stanowią macierz).

To ostatnie zdanie nie jest do końca dokładne. Wywołanie String[].class.getComponentType()zwraca Classobiekt reprezentujący klasę String, ale jego typ Class<?>nie Class<String>jest, dlatego nie można zrobić czegoś takiego jak poniżej.

String foo = String[].class.getComponentType().cast("bar"); // won't compile

To samo dotyczy każdej metody, Classktóra zwraca Classobiekt.

W odniesieniu do komentarza Joachima Sauera do tej odpowiedzi (nie mam wystarczającej reputacji, aby skomentować ją osobiście), przykład użycia cast do T[]spowoduje ostrzeżenie, ponieważ kompilator nie może zagwarantować bezpieczeństwa typu w tym przypadku.


Edytuj w odniesieniu do komentarzy Ingo:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}

5
Jest to bezużyteczne, jest to tylko skomplikowany sposób na napisanie nowego ciągu [...]. Ale tak naprawdę potrzebna jest coś takiego jak publiczna statyczna <T> T [] newArray (int size) {...}, a to po prostu nie istnieje w java noir, czy można to symulować za pomocą refleksji - powodem jest ta informacja o tym, jak instancja typu ogólnego jest niedostępna w czasie wykonywania.
Ingo

4
@Ingo O czym ty mówisz? Mój kod może zostać użyty do utworzenia tablicy dowolnego typu.
gdejohn

3
@Charlatan: Jasne, ale może też nowe []. Pytanie brzmi: kto zna typ i kiedy. Dlatego jeśli wszystko, co masz, to rodzaj ogólny, nie możesz.
Ingo

2
Nie wątpię w to. Chodzi o to, że nie dostajesz obiektu klasy w środowisku wykonawczym dla ogólnego typu X.
Ingo

2
Prawie. Przyznaję, że to więcej niż można osiągnąć dzięki nowemu []. W praktyce prawie zawsze spełni to zadanie. Jednak nadal nie jest możliwe, na przykład, napisanie klasy kontenera sparametryzowanej za pomocą E, która ma metodę E [] na Array () i która rzeczywiście zwraca prawdziwą tablicę E []. Kod można zastosować tylko wtedy, gdy w kolekcji znajduje się co najmniej jeden obiekt E. Zatem ogólne rozwiązanie jest niemożliwe.
Ingo

42

To jedyna odpowiedź bezpieczna dla typu

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}

Musiałem to sprawdzić, ale tak, drugi argument „length” Arrays#copyOf()jest niezależny od długości tablicy podanej jako pierwszy argument. To sprytne, choć niesie za sobą koszty połączeń do Math#min()i System#arrayCopy()żadne z nich nie jest absolutnie konieczne do wykonania tej pracy. docs.oracle.com/javase/7/docs/api/java/util/…
seh

8
To nie działa, jeśli Ejest zmienną typu. Varargs tworzy tablicę wymazywania Ekiedy Ejest zmienną typu, dzięki czemu niewiele się różni (E[])new Object[n]. Zobacz http://ideone.com/T8xF91 . W żadnym wypadku nie jest bardziej bezpieczny niż jakakolwiek inna odpowiedź.
Radiodef,

1
@Radiodef - rozwiązanie jest bezpieczne dla typu w czasie kompilacji. zauważ, że usuwanie nie jest dokładnie częścią specyfikacji języka; specyfikacja jest napisana ostrożnie, abyśmy mogli w przyszłości w pełni zweryfikować - a wtedy to rozwiązanie działałoby idealnie również w czasie wykonywania, w przeciwieństwie do innych rozwiązań.
ZhongYu,

@Radiodef - Można dyskutować, czy zakaz tworzenia ogólnych tablic jest dobrym pomysłem. niezależnie od tego, że język pozostawia backdoora - vararg wymaga ogólnego tworzenia tablic. Jest tak dobry, jakby język na to pozwolił new E[]. Problem, który pokazałeś w swoim przykładzie, to ogólny problem usuwania, nie unikalny dla tego pytania i odpowiedzi.
ZhongYu,

2
@Radiodef - Istnieją pewne różnice. Kompilator sprawdza poprawność tego rozwiązania; nie opiera się na ludzkim rozumowaniu przymusowej obsady. Różnica nie jest znacząca dla tego konkretnego problemu. Niektórzy ludzie lubią być trochę fantazyjni, to wszystko. Jeśli ktoś zostanie wprowadzony w błąd przez sformułowanie OP, zostanie to wyjaśnione przez twoje komentarze i moje.
ZhongYu

33

Aby rozszerzyć na więcej wymiarów, po prostu dodaj []parametry i parametry wymiaru do newInstance()( Tjest parametrem typu, clsa Class<T>, d1przez, d5są liczbami całkowitymi):

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);

Zobacz Array.newInstance()szczegóły.


4
+1 Pojawiły się pytania dotyczące tworzenia macierzy wielowymiarowych, które zostały zamknięte jako duplikaty tego postu - ale żadna odpowiedź nie rozwiązała tego szczegółowo.
Paul Bellora

1
@JordanC Może; chociaż w duchu jest taki sam jak stackoverflow.com/a/5671304/616460 ; Pomyślę o najlepszym sposobie radzenia sobie jutro. Jestem śpiący.
Jason C

14

W Javie 8 możemy wykonać rodzaj ogólnego tworzenia tablic za pomocą lambda lub odwołania do metody. Jest to podobne do podejścia refleksyjnego (które przechodzi a Class), ale tutaj nie używamy refleksji.

@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);
    }
}

Na przykład jest to używane przez <A> A[] Stream.toArray(IntFunction<A[]>).

Można to również zrobić przed wersją Java 8 przy użyciu anonimowych klas, ale jest to bardziej kłopotliwe.


Tak naprawdę nie potrzebujesz specjalnego interfejsu ArraySupplier, możesz zadeklarować konstruktor jako GenSet(Supplier<E[]> supplier) { ...i wywołać go tym samym wierszem, co masz.
Lii,

4
@Lii Być taki sam jak mój przykład, byłoby IntFunction<E[]>, ale tak, to prawda.
Radiodef

11

Jest to omówione w rozdziale 5 (Generics) Effective Java, 2nd Edition , pozycja 25 ... Preferuj listy od tablic

Twój kod będzie działał, chociaż będzie generował niesprawdzone ostrzeżenie (które możesz pominąć za pomocą następującej adnotacji:

@SuppressWarnings({"unchecked"})

Jednak prawdopodobnie lepiej byłoby użyć listy zamiast tablicy.

Ciekawa dyskusja na temat tego błędu / funkcji na stronie projektu OpenJDK .


8

Nie trzeba przekazywać argumentu Konstruktor do klasy. Spróbuj tego.

public 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 + "]";
    }
}

i

GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

wynik:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]

7

Generyczne programy Java działają poprzez sprawdzanie typów w czasie kompilacji i wstawianie odpowiednich rzutowań, ale kasowanie typów w skompilowanych plikach. Dzięki temu biblioteki ogólne mogą być używane przez kod, który nie rozumie generycznych (co było świadomą decyzją projektową), ale co oznacza, że ​​normalnie nie można dowiedzieć się, jaki typ jest w czasie wykonywania.

Konstruktor publiczny Stack(Class<T> clazz,int capacity)wymaga przekazania obiektu klasy w czasie wykonywania, co oznacza, że ​​informacje o klasie dostępne w czasie wykonywania do potrzebującego kodu. IClass<T> środki tworzą, że kompilator będzie sprawdzić, czy obiekt klasy przechodzą właśnie obiekt Class dla typu T. nie podklasą T, nie nadklasą T, ale właśnie T.

Oznacza to, że możesz utworzyć w swoim konstruktorze obiekt tablicowy odpowiedniego typu, co oznacza, że ​​typ obiektów przechowywanych w Twojej kolekcji będzie sprawdzany pod kątem typów w momencie dodawania do kolekcji.


6

Cześć, chociaż wątek jest martwy, chciałbym zwrócić uwagę na to:

Generics służy do sprawdzania typu w czasie kompilacji:

  • Dlatego celem jest sprawdzenie, czy to, czego potrzebujesz, jest tym, czego potrzebujesz.
  • Zwrócisz to, czego potrzebuje konsument.
  • Sprawdź to:

wprowadź opis zdjęcia tutaj

Nie martw się ostrzeżeniami rzutowania podczas pisania klasy ogólnej. Martw się, gdy go używasz.


6

Co z tym rozwiązaniem?

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

Działa i wygląda na zbyt prosty, aby był prawdziwy. Czy jest jakaś wada?


3
Zgrabne, ale działa tylko wtedy, gdy wywołasz je „ręcznie”, tzn. Przekaż elementy indywidualnie. Jeśli nie możesz utworzyć nowej instancji T[], to nie możesz programowo zbudować T[] elemspolecenia przejścia do funkcji. A gdybyś mógł, nie potrzebujesz tej funkcji.
orlade

5

Zobacz także ten kod:

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;
}

Konwertuje listę dowolnego rodzaju obiektu na tablicę tego samego typu.


Tak, zwracasz null, co nie jest oczekiwaną pustą tablicą. To najlepsze, co możesz zrobić, ale nie idealne.
Kevin Cox,

Może się to również nie powieść, jeśli obiekt Listzawiera więcej niż jeden typ obiektu, np . toArray(Arrays.asList("abc", new Object()))Wyrzuci ArrayStoreException.
Radiodef,

Użyłem uproszczonej wersji tego; pierwsza rzecz, której mogłem użyć, działała, chociaż wprawdzie nie wypróbowałem niektórych bardziej zaangażowanych rozwiązań. Aby uniknąć forpętli i innych, których użyłem, Arrays.fill(res, obj);ponieważ chciałem mieć taką samą wartość dla każdego indeksu.
bbarker,

5

Znalazłem szybki i łatwy sposób, który mi odpowiada. Zauważ, że używałem tego tylko w Javie JDK 8. Nie wiem, czy będzie działać z poprzednimi wersjami.

Chociaż nie możemy utworzyć instancji tablicy ogólnej parametru określonego typu, możemy przekazać już utworzoną tablicę do konstruktora klasy ogólnej.

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

Teraz w zasadzie możemy utworzyć tablicę w następujący sposób:

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

        // ...

    }
}

Dla większej elastyczności macierzy możesz użyć połączonej listy, np. ArrayList i inne metody znalezione w klasie Java.util.ArrayList.


4

W przykładzie wykorzystano odbicie Java do utworzenia tablicy. Nie jest to zalecane, ponieważ nie jest to bezpieczne. Zamiast tego powinieneś po prostu użyć wewnętrznej listy i całkowicie unikać tablicy.


13
Drugi przykład (użycie Array.newInstance ()) jest w rzeczywistości bezpieczny. Jest to możliwe, ponieważ typ T obiektu klasy musi pasować do T tablicy. Zasadniczo zmusza Cię do podania informacji, które środowisko wykonawcze Java odrzuca dla generycznych.
Joachim Sauer


3

Zrobiłem ten fragment kodu, aby refleksyjnie utworzyć instancję klasy, która jest przekazywana do prostego narzędzia do automatycznego testowania.

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});
}

Uwaga na ten segment:

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

do inicjowania tablicy gdzie Array.newInstance (klasa tablicy, rozmiar tablicy) . Klasa może być zarówno pierwotna (int. Klasa), jak i obiektowa (Integer.class).

BeanUtils jest częścią wiosny.


3

W rzeczywistości łatwiejszym sposobem jest utworzenie tablicy obiektów i rzutowanie jej na pożądany typ, jak w poniższym przykładzie:

T[] array = (T[])new Object[SIZE];

gdzie SIZEjest stałą i Tjest identyfikatorem typu


1

Przymusowa obsada sugerowana przez innych ludzi nie działała dla mnie, rzucając wyjątek nielegalnego castingu.

Jednak ta domyślna obsada działała dobrze:

Item<K>[] array = new Item[SIZE];

gdzie pozycja jest zdefiniowaną przeze mnie klasą zawierającą element członkowski:

private K value;

W ten sposób otrzymujesz tablicę typu K (jeśli element ma tylko wartość) lub dowolny typ ogólny, który chcesz zdefiniować w klasie Element.


1

Nikt inny nie odpowiedział na pytanie, co się dzieje w opublikowanym przykładzie.

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;
}

Jak powiedzieli inni, generycy są „wymazywani” podczas kompilacji. Tak więc w czasie wykonywania instancja rodzaju ogólnego nie wie, jaki jest jego typ komponentu. Przyczyna tego jest historyczna, Sun chciał dodać ogólne, nie psując istniejącego interfejsu (zarówno źródłowego, jak i binarnego).

Tablice z drugiej strony zrób poznać ich typ komponentu przy starcie.

Ten przykład rozwiązuje ten problem, ponieważ kod wywołujący konstruktor (który zna typ) przekazuje parametr informujący klasę o wymaganym typie.

Więc aplikacja zbuduje klasę z czymś podobnym

Stack<foo> = new Stack<foo>(foo.class,50)

a konstruktor wie teraz (w czasie wykonywania), jaki jest typ komponentu i może wykorzystać te informacje do skonstruowania tablicy za pomocą interfejsu API odbicia.

Array.newInstance(clazz, capacity);

Wreszcie mamy rzutowany typ, ponieważ kompilator nie ma możliwości dowiedzenia się, że zwrócona tablica Array#newInstance()jest poprawnym typem (chociaż wiemy).

Ten styl jest nieco brzydki, ale czasem może być najgorszym rozwiązaniem do tworzenia typów ogólnych, które z jakiegokolwiek powodu muszą znać swój typ komponentu w czasie wykonywania (tworzenie tablic lub tworzenie instancji typu komponentu itp.).


1

Znalazłem sposób obejścia tego problemu.

Wiersz poniżej generuje ogólny błąd tworzenia tablicy

List<Person>[] personLists=new ArrayList<Person>()[10];

Jeśli jednak opakowuję List<Person>w osobną klasę, to działa.

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

Możesz wystawiać ludzi z klasy PersonList przez getter. W poniższej linii otrzymasz tablicę, która ma List<Person>w każdym elemencie. Innymi słowy tablica List<Person>.

PersonList[] personLists=new PersonList[10];

Potrzebowałem czegoś takiego w jakimś kodzie, nad którym pracowałem i właśnie to zrobiłem, aby to zadziałało. Jak dotąd żadnych problemów.


0

Możesz stworzyć tablicę Object i rzucić ją na E wszędzie. Tak, nie jest to bardzo czysty sposób, ale powinien przynajmniej działać.


„Szukamy długich odpowiedzi, które zawierają wyjaśnienia i kontekst. Nie udzielaj odpowiedzi tylko w jednym wierszu; wyjaśnij, dlaczego Twoja odpowiedź jest poprawna, najlepiej z cytatami. Odpowiedzi bez wyjaśnień mogą zostać usunięte”.
gparyani

BUt, który nie działa w niektórych przypadkach, np. Jeśli twoja klasa ogólna chce zaimplementować interfejs porównywalny.
RamPrasadBismil 21.04.16

Przypuszczam, że witam siedem lat temu.
Esko

1
To nie zadziała, jeśli spróbujesz zwrócić tablicę z kodu ogólnego do wywołującego nieogólnego. Pojawi się wyjątkowy wyjątek dla klasy.
płyn do płukania

0

Spróbuj tego.

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>();
        }
    }
}

Nie mogę uruchomić twojego kodu, skąd Elementpochodzi twoja klasa?

0

Łatwym, aczkolwiek niechlujnym obejściem tego problemu byłoby zagnieżdżenie drugiej klasy „holder” wewnątrz głównej klasy i użycie jej do przechowywania danych.

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

3
To nie działa. new Holder<Thing>[10]to ogólne tworzenie tablic.
Radiodef,

0

Może nie ma to związku z tym pytaniem, ale podczas otrzymywania generic array creationbłędu „ ” występowałem

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

Odnajduję następujące prace (i pracowałem dla mnie) z @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];

Tak, nie jest to do końca powiązane, ale zakorzenione w tych samych problemach (kasowanie, kowariancja macierzy). Oto przykład postu na temat tworzenia tablic typów sparametryzowanych: stackoverflow.com/questions/9542076/…
Paul Bellora

0

Zastanawiam się, czy ten kod stworzyłby skuteczną ogólną tablicę?

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;
}

Edycja: Być może alternatywnym sposobem utworzenia takiej tablicy, jeśli wymagany rozmiar byłby znany i mały, byłoby po prostu wprowadzenie wymaganej liczby „zerowych” do polecenia zeroArray?

Chociaż oczywiście nie jest to tak wszechstronne, jak użycie kodu createArray.


Nie, to nie działa. Zmienna varargs powoduje skasowanie Tkiedy Tjest zmienną typu, tzn. zeroArrayZwraca an Object[]. Zobacz http://ideone.com/T8xF91 .
Radiodef,

0

Możesz użyć obsady:

public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}

Jeśli zamierzasz to zasugerować, naprawdę musisz wyjaśnić jego ograniczenia. Nigdy nie wystawiać ana zewnątrz klasy!
Radiodef,

0

Znalazłem dość unikalne rozwiązanie, które pomija niemożność zainicjowania ogólnej tablicy. Musisz stworzyć klasę, która przyjmuje zmienną ogólną T w taki sposób:

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

a następnie w klasie macierzy po prostu zacznij tak:

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

uruchomienie a new Generic Invoker[]spowoduje problem z niezaznaczonym, ale tak naprawdę nie powinno być żadnych problemów.

Aby uzyskać z tablicy, należy wywołać tablicę [i] .variable w następujący sposób:

public T get(int index){
    return array[index].variable;
}

Resztę, na przykład zmianę rozmiaru tablicy, można wykonać za pomocą Arrays.copyOf () w następujący sposób:

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

A funkcję dodawania można dodać w następujący sposób:

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++;
}

1
Pytanie dotyczyło utworzenia tablicy typu parametru typu ogólnego T, a nie tablicy jakiegoś sparametryzowanego typu.
Sotirios Delimanolis

Wykonuje to samo zadanie i nie wymaga angażowania się w zajęcia, dzięki czemu korzystanie z niestandardowej kolekcji jest łatwiejsze.
Mgławica Kraba

Jakie zadanie ? To dosłownie inne zadanie: tablica typu sparametryzowanego w porównaniu do tablicy parametru typu ogólnego.
Sotirios Delimanolis

Pozwala ci stworzyć tablicę z ogólnego typu? Pierwotny problem polegał na zainicjowaniu tablicy za pomocą ogólnego typu, który przy użyciu mojej metody pozwala ci to zrobić bez konieczności popychania klasy przez użytkownika lub dawania niezaznaczonego błędu, takiego jak próba rzutowania obiektu na ciąg. Jak chłód, nie jestem najlepszy w tym, co robię i nie chodziłem do szkoły na programowanie, ale myślę, że nadal zasługuję na trochę wkładu, a nie bycia przeklętym przez inne dziecko w Internecie.
Mgławica Kraba

Zgadzam się z Sotiros. Istnieją dwa sposoby myślenia o odpowiedzi. Albo jest to odpowiedź na inne pytanie, albo próba uogólnienia pytania. Oba są złe / nie są pomocne. Ludzie, którzy szukają wskazówek, jak zaimplementować klasę „tablicy ogólnej”, przestaną czytać, gdy czytają tytuł pytania. A kiedy znajdą Q z 30 odpowiedziami, jest mało prawdopodobne, aby przewinął do końca i przeczytał odpowiedź zero głosów od nowego SO.
Stephen C

0

Według vnportnoy składnia

GenSet<Integer> intSet[] = new GenSet[3];

tworzy tablicę pustych referencji, które należy wypełnić jako

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

który jest bezpieczny dla typu.


-1
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}

Zawsze powinieneś dodawać wyjaśnienia do swojego kodu i wyjaśniać, dlaczego rozwiązuje ono pierwotnie opublikowane pytanie.
mjuarez

-1

Ogólne tworzenie tablic jest niedozwolone w Javie, ale można to zrobić w podobny sposób

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.