Bagaimana cara membuat array generik di Java?

1091

Karena penerapan generik Java, Anda tidak dapat memiliki kode seperti ini:

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

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

Bagaimana saya bisa menerapkan ini sambil menjaga keamanan jenis?

Saya melihat solusi di forum Java yang berjalan seperti ini:

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

Tetapi saya benar-benar tidak mengerti apa yang sedang terjadi.

tatsuhirosatou
sumber
14
Apakah Anda benar - benar perlu menggunakan array di sini? Bagaimana dengan menggunakan Koleksi?
matt b
12
Ya, saya juga berpikir bahwa koleksi lebih elegan untuk masalah ini. Tapi ini untuk tugas kelas dan mereka diperlukan :(
tatsuhirosatou
3
Saya tidak mengerti mengapa saya perlu refleksi di sini. Tata bahasa Java adalah aneh: seperti java.util.HashMap <String, String> baru tidak valid. java.util.HashMap baru <long, long> (10) tidak valid. new long [] [10] tidak valid, new long [10] [] valid. Hal-hal itu membuat menulis sebuah program yang dapat menulis program java lebih sulit daripada kelihatannya.
pria perunggu

Jawaban:

704

Sebagai gantinya, saya harus mengajukan pertanyaan: apakah GenSet"dicentang" atau "tidak dicentang"? Apa artinya?

  • Diperiksa : ketikan yang kuat . GenSettahu secara eksplisit jenis objek yang dikandungnya (yaitu konstruktornya secara eksplisit dipanggil dengan Class<E>argumen, dan metode akan mengeluarkan pengecualian ketika mereka memberikan argumen yang bukan tipe E. Lihat Collections.checkedCollection.

    -> dalam hal ini, Anda harus menulis:

    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];
        }
    }
    
  • Tidak dicentang : pengetikan lemah . Tidak ada pemeriksaan tipe yang benar-benar dilakukan pada objek yang dilewatkan sebagai argumen.

    -> dalam hal ini, Anda harus menulis

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

    Perhatikan bahwa tipe komponen array harus menjadi penghapusan parameter tipe:

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

Semua ini merupakan hasil dari kelemahan generik yang diketahui dan disengaja di Jawa: ini diimplementasikan dengan menggunakan penghapusan, jadi kelas "generik" tidak tahu argumen tipe apa yang mereka buat pada saat run time, dan oleh karena itu tidak dapat memberikan tipe- keamanan kecuali jika beberapa mekanisme eksplisit (pengecekan tipe) diimplementasikan.

Varkhan
sumber
7
Apa yang akan menjadi pilihan terbaik? Saya perlu mendapatkan elemen dari array ini cukup sering (dalam satu lingkaran). Jadi koleksi mungkin lebih lambat, tetapi yang mana yang paling cepat?
user1111929
3
Dan jika tipe generik dibatasi, array backing harus dari tipe pembatas.
Mordechai
5
@ AaronDigulla Hanya untuk mengklarifikasi itu bukan tugas, tetapi inisialisasi variabel lokal. Anda tidak dapat membuat anotasi ekspresi / pernyataan.
kennytm
1
@ Varkhan Apakah ada cara untuk mengubah ukuran array ini dari dalam implementasi kelas. Sebagai contoh jika saya ingin mengubah ukuran setelah overflow seperti ArrayList. Saya mencari Implementasi ArrayList yang mereka miliki Object[] EMPTY_ELEMENTDATA = {}untuk penyimpanan. Bisakah saya menggunakan mekanisme ini untuk mengubah ukuran tanpa mengetahui jenisnya menggunakan obat generik?
JourneyMan
2
Bagi mereka yang ingin membuat metode dengan tipe generik (yang saya cari), gunakan ini:public void <T> T[] newArray(Class<T> type, int length) { ... }
Daniel Kvist
225

Kamu bisa melakukan ini:

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

Ini adalah salah satu cara yang disarankan untuk mengimplementasikan koleksi generik di Java yang Efektif; Butir 26 . Tidak ada kesalahan jenis, tidak perlu melemparkan array berulang kali. Namun ini memicu peringatan karena berpotensi berbahaya, dan harus digunakan dengan hati-hati. Sebagaimana dirinci dalam komentar, ini Object[]sekarang menyamar sebagai E[]tipe kami , dan dapat menyebabkan kesalahan tak terduga ClassCastExceptionjika digunakan secara tidak aman.

Sebagai aturan praktis, perilaku ini aman selama array cor digunakan secara internal (misalnya untuk mendukung struktur data), dan tidak dikembalikan atau diekspos ke kode klien. Jika Anda perlu mengembalikan larik tipe generik ke kode lain, Arraykelas refleksi yang Anda sebutkan adalah cara yang tepat.


Layak disebutkan bahwa jika memungkinkan, Anda akan memiliki waktu yang jauh lebih bahagia bekerja dengan Lists daripada array jika Anda menggunakan obat generik. Tentu saja kadang-kadang Anda tidak punya pilihan, tetapi menggunakan kerangka koleksi jauh lebih kuat.

dimo414
sumber
47
Ini tidak akan berfungsi jika array diperlakukan sebagai array yang diketik dalam bentuk apa pun, seperti String[] s=b;pada test()metode di atas . Itu karena array E tidak benar-benar, itu Object []. Ini penting jika Anda ingin, misalnya a List<String>[]- Anda tidak dapat menggunakan Object[]untuk itu, Anda harus memiliki List[]khusus. Itulah sebabnya Anda perlu menggunakan kreasi array Kelas <?> Yang direfleksikan.
Lawrence Dol
8
Kasus sudut / masalah adalah jika Anda ingin melakukan, misalnya, public E[] toArray() { return (E[])internalArray.clone(); }ketika internalArraydiketik sebagai E[], dan karena itu sebenarnya merupakan Object[]. Ini gagal saat runtime dengan pengecualian tipe-casting karena suatu Object[]tidak dapat ditugaskan ke array dari jenis apa pun yang Eterjadi.
Lawrence Dol
17
Pada dasarnya, pendekatan ini akan berfungsi selama Anda tidak mengembalikan array atau meneruskannya atau menyimpannya di beberapa tempat di luar kelas yang membutuhkan array dari tipe tertentu. Selama Anda berada di dalam kelas, Anda baik-baik saja karena E terhapus. Ini "berbahaya" karena jika Anda mencoba mengembalikannya atau sesuatu, Anda tidak mendapat peringatan bahwa itu tidak aman. Tetapi jika Anda berhati-hati maka itu berhasil.
newacct
3
Cukup aman. Di E[] b = (E[])new Object[1];Anda dapat dengan jelas melihat bahwa satu-satunya referensi ke array yang dibuat adalah bdan jenisnya badalah E[]. Oleh karena itu tidak ada bahaya Anda secara tidak sengaja mengakses array yang sama melalui variabel berbeda dari tipe yang berbeda. Jika sebaliknya, Object[] a = new Object[1]; E[]b = (E[])a; maka Anda harus paranoid tentang cara Anda menggunakannya a.
Aaron McDaid
5
Setidaknya di Jawa 1.6, ini menghasilkan peringatan: "Pemain yang tidak dicentang dari Object [] ke T []"
Quantum7
61

Berikut ini cara menggunakan obat generik untuk mendapatkan larik tipe tepat yang Anda cari sambil menjaga keamanan tipe (berbeda dengan jawaban lain, yang akan mengembalikan Objectarray atau menghasilkan peringatan pada waktu kompilasi):

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

Itu mengkompilasi tanpa peringatan, dan seperti yang Anda lihat main, untuk tipe apa pun yang Anda nyatakan instance GenSetas, Anda bisa menetapkan ake array tipe itu, dan Anda bisa menetapkan elemen dari ake variabel tipe itu, artinya array itu dan nilai-nilai dalam array adalah tipe yang benar.

Ia bekerja dengan menggunakan literal kelas sebagai token jenis runtime, seperti yang dibahas dalam Tutorial Java . Literal kelas diperlakukan oleh kompiler sebagai instance dari java.lang.Class. Untuk menggunakannya, cukup ikuti nama kelas dengan .class. Jadi, String.classbertindak sebagai Classobjek yang mewakili kelas String. Ini juga berfungsi untuk antarmuka, enum, array dimensi apa pun (misalnya String[].class), primitif (mis. int.class), Dan kata kunci void(yaitu void.class).

Classitu sendiri generik (dideklarasikan sebagai Class<T>, di mana Tsingkatan dari tipe yang Classdiwakili objek), artinya tipe String.classis Class<String>.

Jadi, setiap kali Anda memanggil konstruktor GenSet, Anda meneruskan literal kelas untuk argumen pertama yang mewakili array dari tipe GenSetinstance yang dinyatakan (misalnya String[].classuntuk GenSet<String>). Perhatikan bahwa Anda tidak akan bisa mendapatkan array primitif, karena primitif tidak dapat digunakan untuk variabel tipe.

Di dalam konstruktor, memanggil metode castmengembalikan Objectargumen yang dilewatkan ke kelas yang diwakili oleh Classobjek tempat metode dipanggil. Memanggil metode statis newInstancedalam java.lang.reflect.Arraypengembalian sebagai Objectarray dari tipe yang diwakili oleh Classobjek yang dilewati sebagai argumen pertama dan panjang yang ditentukan oleh yang intdilewati sebagai argumen kedua. Memanggil metode getComponentTypemengembalikan Classobjek yang mewakili jenis komponen array yang diwakili oleh Classobjek tempat metode dipanggil (misalnya String.classuntuk String[].class, nulljika Classobjek tidak mewakili array).

Kalimat terakhir itu tidak sepenuhnya akurat. Memanggil String[].class.getComponentType()mengembalikan Classobjek yang mewakili kelas String, tetapi tipenya adalah Class<?>, bukan Class<String>, itulah sebabnya Anda tidak bisa melakukan sesuatu seperti berikut ini.

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

Hal yang sama berlaku untuk setiap metode Classyang mengembalikan Classobjek.

Mengenai komentar Joachim Sauer tentang jawaban ini (saya sendiri tidak memiliki cukup reputasi untuk mengomentarinya), contoh menggunakan pemeran T[]akan menghasilkan peringatan karena kompiler tidak dapat menjamin keamanan jenis dalam kasus itu.


Edit tentang komentar Ingo:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}
gdejohn
sumber
5
Ini tidak berguna, ini hanya cara yang rumit untuk menulis String baru [...]. Tetapi yang benar-benar dibutuhkan adalah sesuatu seperti public static <T> T [] newArray (ukuran int) {...}, dan ini tidak ada di java noir dapatkah itu disimulasikan dengan refleksi - alasannya adalah informasi tentang bagaimana tipe generik yang dipakai tidak tersedia saat runtime.
Ingo
4
@Ingo Apa yang kamu bicarakan? Kode saya dapat digunakan untuk membuat array jenis apa pun.
gdejohn
3
@ Charlatan: Tentu, tapi begitu juga yang baru []. Pertanyaannya adalah: siapa yang tahu tipe dan kapan. Karena itu, jika semua yang Anda miliki adalah tipe generik, Anda tidak bisa.
Ingo
2
Saya tidak meragukannya. Intinya adalah, Anda tidak mendapatkan objek Class saat runtime untuk generik tipe X.
Ingo
2
Hampir. Saya akui ini lebih dari apa yang bisa dicapai dengan yang baru []. Dalam praktiknya, ini hampir selalu akan melakukan pekerjaan. Namun, masih tidak mungkin, misalnya, untuk menulis kelas kontainer parameter dengan E yang memiliki metode E [] toArray () dan yang memang mengembalikan array E [] yang sebenarnya. Kode Anda hanya dapat diterapkan ketika ada setidaknya satu objek-E dalam koleksi. Jadi, solusi umum tidak mungkin.
Ingo
42

Ini adalah satu-satunya jawaban yang aman jenis

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}
tak dapat disangkal
sumber
Saya harus mencarinya, tapi ya, argumen "panjang" kedua Arrays#copyOf()tidak tergantung pada panjang array yang disediakan sebagai argumen pertama. Itu pintar, meskipun itu membayar biaya panggilan ke Math#min()dan System#arrayCopy(), tidak ada yang benar-benar diperlukan untuk menyelesaikan pekerjaan ini. docs.oracle.com/javase/7/docs/api/java/util/…
seh
8
Ini tidak berfungsi jika Emerupakan variabel tipe. The varargs menciptakan sebuah array penghapusan Eketika Eadalah variabel tipe, membuatnya tidak jauh berbeda dari (E[])new Object[n]. Silakan lihat http://ideone.com/T8xF91 . Itu tidak berarti lebih aman daripada jawaban lainnya.
Radiodef
1
@Radiodef - solusinya terbukti aman-tipe pada waktu kompilasi. perhatikan bahwa penghapusan bukan bagian dari spesifikasi bahasa; spec ditulis dengan cermat sehingga kita bisa memiliki reifikasi penuh di masa depan - dan kemudian solusi ini akan bekerja dengan sempurna saat runtime juga, tidak seperti solusi lain.
ZhongYu
@Radiodef - Dapat diperdebatkan apakah melarang pembuatan array generik adalah ide yang bagus. bagaimanapun, bahasa tidak meninggalkan backdoor - vararg membutuhkan pembuatan array generik. Ini sama baiknya dengan jika bahasa telah diizinkan new E[]. Masalah yang Anda tunjukkan dalam contoh Anda adalah masalah penghapusan umum, tidak unik untuk pertanyaan ini dan jawaban ini.
ZhongYu
2
@Radiodef - Ada beberapa perbedaan. Kebenaran dari solusi ini diperiksa oleh kompiler; itu tidak bergantung pada alasan manusia dari pemeran paksa. Perbedaannya tidak signifikan untuk masalah khusus ini. Beberapa orang hanya ingin menjadi sedikit mewah, itu saja. Jika ada yang disesatkan oleh kata-kata OP, itu diklarifikasi oleh komentar Anda dan saya.
ZhongYu
33

Untuk memperluas ke dimensi yang lebih, cukup tambahkan []parameter 's dan dimensi ke newInstance()( Tadalah parameter tipe, clsadalah a Class<T>, d1through d5adalah bilangan bulat):

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

Lihat Array.newInstance()detailnya.

Jason C
sumber
4
+1 Ada beberapa pertanyaan tentang pembuatan array multi-dimensi yang ditutup sebagai dupes dari pos ini - tetapi tidak ada jawaban yang secara khusus membahasnya.
Paul Bellora
1
@JordanC Mungkin; meskipun sama semangatnya dengan stackoverflow.com/a/5671304/616460 ; Saya akan memikirkan cara terbaik untuk menangani besok. Saya mengantuk.
Jason C
14

Di Java 8, kita bisa melakukan semacam pembuatan array generik menggunakan lambda atau referensi metode. Ini mirip dengan pendekatan reflektif (yang melewati a Class), tetapi di sini kita tidak menggunakan refleksi.

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

Sebagai contoh, ini digunakan oleh <A> A[] Stream.toArray(IntFunction<A[]>).

Ini bisa juga dilakukan pre-Jawa 8 menggunakan kelas anonim tapi lebih rumit.

Radiodef
sumber
Anda tidak benar-benar membutuhkan antarmuka khusus seperti ArraySupplierini, Anda dapat mendeklarasikan konstruktor GenSet(Supplier<E[]> supplier) { ...dan menyebutnya dengan garis yang sama seperti yang Anda miliki.
Lii
4
@ Lii Untuk menjadi sama dengan contoh saya, itu akan menjadi IntFunction<E[]>, tapi ya itu benar.
Radiodef
12

Ini dicakup dalam Bab 5 (Generik) dari Java Efektif, Edisi ke-2 , item 25 ... Memilih daftar ke array

Kode Anda akan berfungsi, meskipun akan menghasilkan peringatan yang tidak dicentang (yang dapat Anda tekan dengan anotasi berikut:

@SuppressWarnings({"unchecked"})

Namun, mungkin akan lebih baik menggunakan Daftar daripada Array.

Ada diskusi menarik tentang bug / fitur ini di situs proyek OpenJDK .

Jeff Olson
sumber
8

Anda tidak perlu meneruskan argumen Kelas ke konstruktor. Coba ini.

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

dan

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

hasil:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]
saka1029
sumber
7

Java generics bekerja dengan memeriksa jenis pada waktu kompilasi dan memasukkan gips yang sesuai, tetapi menghapus jenis-jenis tersebut di file yang dikompilasi. Ini membuat pustaka generik dapat digunakan oleh kode yang tidak memahami generik (yang merupakan keputusan desain yang disengaja) tetapi yang berarti Anda biasanya tidak bisa mengetahui apa jenisnya saat dijalankan.

Masyarakat Stack(Class<T> clazz,int capacity)konstruktor mengharuskan Anda untuk melewati sebuah objek Kelas pada waktu berjalan, mana informasi berarti kelas yang tersedia saat runtime ke kode yang membutuhkannya. Dan Class<T>bentuk itu berarti bahwa kompiler akan memeriksa bahwa objek Kelas yang Anda lewati adalah objek Kelas yang tepat untuk tipe T. Bukan subkelas T, bukan superclass T, tetapi tepatnya T.

Ini kemudian berarti bahwa Anda dapat membuat objek array dari tipe yang sesuai di konstruktor Anda, yang berarti bahwa jenis objek yang Anda simpan di koleksi Anda akan diperiksa tipenya pada titik mereka ditambahkan ke koleksi.

Bill Michell
sumber
6

Hai meskipun utasnya sudah mati, saya ingin menarik perhatian Anda pada ini:

Generik digunakan untuk memeriksa tipe selama waktu kompilasi:

  • Karena itu tujuannya adalah untuk memeriksa apa yang masuk adalah yang Anda butuhkan.
  • Apa yang Anda kembalikan adalah apa yang dibutuhkan konsumen.
  • Periksa ini:

masukkan deskripsi gambar di sini

Jangan khawatir tentang peringatan typecasting ketika Anda menulis kelas generik. Khawatir saat Anda menggunakannya.

puneeth
sumber
6

Bagaimana dengan solusi ini?

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

Ini bekerja dan terlihat terlalu sederhana untuk menjadi kenyataan. Apakah ada kekurangan?

Benjamin M
sumber
3
Rapi, tetapi hanya berfungsi jika Anda menyebutnya 'secara manual', yaitu meneruskan elemen secara individual. Jika Anda tidak dapat membuat instance baru T[], maka Anda tidak dapat secara sistematis membangun a T[] elemsuntuk diteruskan ke fungsi. Dan jika Anda bisa, Anda tidak perlu fungsinya.
orlade
5

Lihat juga kode ini:

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

Itu mengkonversi daftar jenis objek ke array dari jenis yang sama.

MatheusJardimB
sumber
Ya, Anda mengembalikan nol, yang bukan array kosong yang diharapkan. Ini adalah yang terbaik yang dapat Anda lakukan, tetapi tidak ideal.
Kevin Cox
Ini juga bisa gagal jika Listmemiliki lebih dari satu jenis objek di dalamnya misalnya toArray(Arrays.asList("abc", new Object()))akan melempar ArrayStoreException.
Radiodef
Saya menggunakan versi ini; Hal pertama yang saya bisa gunakan yang berhasil, meskipun diakui saya tidak mencoba beberapa solusi yang lebih terlibat. Untuk menghindari satu forlingkaran dan lainnya saya gunakan Arrays.fill(res, obj);karena saya ingin nilai yang sama untuk setiap indeks.
bbarker
5

Saya telah menemukan cara cepat dan mudah yang berfungsi untuk saya. Perhatikan bahwa saya hanya menggunakan ini pada Java JDK 8. Saya tidak tahu apakah ini akan bekerja dengan versi sebelumnya.

Meskipun kami tidak dapat membuat instance array generik dari parameter tipe tertentu, kami dapat meneruskan array yang sudah dibuat ke konstruktor kelas generik.

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

    // ...

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

    // Do whatever with the array...
}

Sekarang di main kita bisa membuat array seperti ini:

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

        // ...

    }
}

Untuk lebih fleksibel dengan array Anda, Anda dapat menggunakan daftar tertaut misalnya. ArrayList dan metode lain yang ditemukan di kelas Java.util.ArrayList.

Nikos
sumber
4

Contohnya menggunakan refleksi Java untuk membuat array. Melakukan ini umumnya tidak dianjurkan, karena itu bukan typesafe. Sebagai gantinya, yang harus Anda lakukan hanyalah menggunakan Daftar internal, dan hindari array sama sekali.

Ola Bini
sumber
13
Contoh kedua (menggunakan Array.newInstance ()) adalah sebenarnya typesafe. Ini dimungkinkan karena tipe T dari objek Class harus cocok dengan T dari array. Ini pada dasarnya memaksa Anda untuk memberikan informasi yang dibuang runtime Java untuk obat generik.
Joachim Sauer
4

Melewati daftar nilai ...

public <T> T[] array(T... values) {
    return values;
}
Rodrigo Asensio
sumber
3

Saya membuat potongan kode ini untuk secara reflektif membuat instance kelas yang dilewatkan untuk utilitas tes otomatis sederhana.

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

Perhatikan segmen ini:

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

untuk array yang memulai di mana Array.newInstance (kelas array, ukuran array) . Kelas dapat berupa primitif (int.class) dan objek (Integer.class).

BeanUtils adalah bagian dari Spring.

Bobster
sumber
3

Sebenarnya cara yang lebih mudah untuk melakukannya, adalah membuat array objek dan melemparkannya ke tipe yang Anda inginkan seperti contoh berikut:

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

di mana SIZEkonstanta dan Tpengenal tipe

Pedram Esmaeeli
sumber
1

Para pemeran paksa yang disarankan oleh orang lain tidak bekerja untuk saya, kecuali pengecoran ilegal.

Namun, pemeran implisit ini bekerja dengan baik:

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

di mana Item adalah kelas yang saya definisikan mengandung anggota:

private K value;

Dengan cara ini Anda mendapatkan larik tipe K (jika item hanya memiliki nilai) atau tipe generik apa pun yang ingin Anda tetapkan dalam Item kelas.

vnportnoy
sumber
1

Tidak ada orang lain yang menjawab pertanyaan tentang apa yang terjadi dalam contoh yang Anda posting.

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

Seperti yang orang lain katakan generik "dihapus" selama kompilasi. Jadi pada saat runtime instance dari generik tidak tahu apa tipe komponennya. Alasan untuk ini adalah historis, Sun ingin menambahkan obat generik tanpa merusak antarmuka yang ada (baik sumber dan biner).

Array di sisi lain do tahu jenis komponen mereka pada saat runtime.

Contoh ini mengatasi masalah dengan meminta kode yang memanggil konstruktor (yang tahu tipe) melewati parameter yang memberi tahu kelas tipe yang diperlukan.

Jadi aplikasi akan membangun kelas dengan sesuatu seperti

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

dan konstruktor sekarang tahu (saat runtime) apa tipe komponen itu dan dapat menggunakan informasi itu untuk membangun array melalui API refleksi.

Array.newInstance(clazz, capacity);

Akhirnya kita memiliki tipe cast karena kompiler tidak memiliki cara untuk mengetahui bahwa array yang dikembalikan oleh Array#newInstance()adalah tipe yang benar (walaupun kita tahu).

Gaya ini agak jelek tapi kadang-kadang bisa menjadi solusi paling buruk untuk membuat tipe generik yang perlu mengetahui tipe komponen mereka saat runtime untuk alasan apa pun (membuat array, atau membuat contoh dari jenis komponen mereka, dll.).

plugwash
sumber
1

Saya menemukan semacam pekerjaan untuk masalah ini.

Baris di bawah ini menampilkan kesalahan pembuatan array generik

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

Namun jika saya merangkum List<Person>dalam kelas yang terpisah, itu berfungsi.

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


public class PersonList {

    List<Person> people;

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

Anda dapat mengekspos orang-orang di kelas PersonList melalui pengambil. Baris di bawah ini akan memberi Anda sebuah array, yang memiliki List<Person>dalam setiap elemen. Dengan kata lain susunan List<Person>.

PersonList[] personLists=new PersonList[10];

Saya membutuhkan sesuatu seperti ini dalam beberapa kode yang sedang saya kerjakan dan inilah yang saya lakukan untuk membuatnya bekerja. Sejauh ini tidak ada masalah.

developer747
sumber
0

Anda bisa membuat array Obyek dan melemparkannya ke E di mana-mana. Ya, itu bukan cara yang sangat bersih untuk melakukannya tetapi setidaknya harus bekerja.

Esko
sumber
"Kami mencari jawaban panjang yang memberikan penjelasan dan konteks. Jangan hanya memberikan jawaban satu baris; jelaskan mengapa jawaban Anda benar, idealnya dengan kutipan. Jawaban tanpa penjelasan dapat dihapus."
gparyani
Tapi itu tidak akan bekerja dalam beberapa kasus seperti jika kelas generik Anda ingin mengimplementasikan antarmuka yang sebanding.
RamPrasadBismil
Selamat datang di tujuh tahun yang lalu, saya kira.
Esko
1
Ini tidak akan berfungsi jika Anda mencoba mengembalikan array dari kode generik ke penelepon non-generik. Akan ada classcasteksepsi kepala-scrating.
plugwash
0

coba ini.

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>();
        }
    }
}
David Bernard
sumber
Saya tidak bisa menjalankan kode Anda, dari mana Elementkelas Anda berasal?
0

Sebuah solusi yang mudah, meskipun berantakan untuk ini adalah untuk bersarang kelas "pemegang" kedua di dalam kelas utama Anda, dan menggunakannya untuk menyimpan data Anda.

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
StarMonkey
sumber
3
Ini sebenarnya tidak berhasil. new Holder<Thing>[10]adalah pembuatan array generik.
Radiodef
0

Mungkin tidak terkait dengan pertanyaan ini tetapi ketika saya mendapatkan " generic array creation" kesalahan untuk menggunakan

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

Saya menemukan karya-karya berikut (dan bekerja untuk saya) dengan @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];
Mohsen Afshin
sumber
Ya, ini tidak terlalu terkait, tetapi berakar pada masalah yang sama (penghapusan, array kovarians). Berikut adalah contoh posting tentang membuat array tipe parameter: stackoverflow.com/questions/9542076/…
Paul Bellora
0

Saya ingin tahu apakah kode ini akan membuat array generik yang efektif?

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

Sunting: Mungkin cara alternatif untuk membuat array seperti itu, jika ukuran yang Anda butuhkan diketahui dan kecil, akan dengan cukup memberi makan jumlah "null" yang diperlukan ke dalam perintah zeroArray?

Meskipun jelas ini tidak serbaguna seperti menggunakan kode createArray.

Cambot
sumber
Tidak, ini tidak berhasil. The varargs menciptakan penghapusan Tkapan Tvariabel tipe, yaitu zeroArraymengembalikan sebuah Object[]. Lihat http://ideone.com/T8xF91 .
Radiodef
0

Anda bisa menggunakan gips:

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

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}
samir benzenine
sumber
Jika Anda akan menyarankan ini, Anda benar-benar perlu menjelaskan keterbatasannya. Jangan pernah terpapar ake luar kelas!
Radiodef
0

Saya benar-benar menemukan solusi yang cukup unik untuk memotong ketidakmampuan untuk memulai array generik. Yang harus Anda lakukan adalah membuat kelas yang mengambil variabel T generik seperti:

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

dan kemudian di kelas array Anda mulai saja seperti ini:

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

memulai new Generic Invoker[]akan menyebabkan masalah dengan tidak dicentang tetapi seharusnya tidak ada masalah.

Untuk mendapatkan dari array, Anda harus memanggil array [i] .variable seperti:

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

Sisanya, seperti mengubah ukuran array dapat dilakukan dengan Arrays.copyOf () seperti:

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

Dan fungsi add dapat ditambahkan seperti ini:

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++;
}
Nebula kepiting
sumber
1
Pertanyaannya adalah tentang membuat array dari tipe parameter tipe generik T, bukan array dari beberapa tipe parameter.
Sotirios Delimanolis
Ini menyelesaikan tugas yang sama dan tidak mengharuskan Anda mendorong di kelas membuat koleksi khusus Anda lebih mudah digunakan.
Crab Nebula
Tugas apa ? Ini benar-benar tugas yang berbeda: array tipe paramaterized vs array parameter tipe generik.
Sotirios Delimanolis
Anda dapat membuat array dari tipe generik? Masalah aslinya adalah menginisialisasi array menggunakan tipe generik yang menggunakan metode saya memungkinkan Anda untuk melakukannya tanpa harus mendorong pengguna di kelas atau memberikan kesalahan yang tidak dicentang seperti mencoba untuk melemparkan Obyek ke String. Seperti dingin, saya bukan yang terbaik dalam apa yang saya lakukan, dan saya belum pergi ke sekolah untuk pemrograman tetapi saya pikir saya masih pantas mendapat sedikit masukan daripada diberi tahu oleh beberapa anak lain di internet.
Kepiting Nebula
Saya setuju dengan Sotiros. Ada dua cara untuk memikirkan jawabannya. Entah itu jawaban untuk pertanyaan yang berbeda, atau itu adalah upaya untuk menggeneralisasikan pertanyaan itu. Keduanya salah / tidak membantu. Orang-orang yang mencari panduan tentang bagaimana menerapkan kelas "array generik" akan / berhenti membaca ketika mereka membaca judul pertanyaan. Dan ketika mereka menemukan Q dengan 30 jawaban, mereka sangat tidak mungkin untuk menggulir sampai akhir dan membaca jawaban nol suara dari pendatang SO baru.
Stephen C
0

Menurut vnportnoy sintaks

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

membuat array referensi nol, untuk diisi sebagai

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

yang merupakan tipe aman.

Sam Ginrich
sumber
-1
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}
Zubair Ibrhaim
sumber
Anda harus selalu menambahkan penjelasan ke kode Anda, dan menjelaskan mengapa itu memecahkan pertanyaan asli yang diposting.
mjuarez
-1

Pembuatan array generik tidak diizinkan di java tetapi Anda dapat melakukannya seperti

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
Irfan Ul Haq
sumber