Dapatkan tipe parameter generik di Java dengan refleksi

143

Apakah mungkin untuk mendapatkan jenis parameter generik?

Sebuah contoh:

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}
cimnine
sumber

Jawaban:

156

Satu konstruksi, saya pernah tersandung pada tampak seperti

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

Jadi sepertinya ada beberapa sihir refleksi di sekitar yang sayangnya saya tidak sepenuhnya mengerti ... Maaf.

DerMike
sumber
2
Tapi nyatakan itu sebagai abstrak. ketika Anda ingin menggunakannya, maka subkelas itu sebaris.
Snicolas
42
Meskipun ini sangat tua dan diterima untuk beberapa alasan, saya telah downvoted karena tidak menjawab pertanyaan. Itu tidak akan memberikan "SpiderMan" dalam contoh yang diberikan dalam pertanyaan. Tidak diragukan lagi ini berguna dalam beberapa situasi, tetapi tidak berfungsi untuk pertanyaan yang diajukan.
Jon Skeet
15
Tidak ada yang datang sejauh ini untuk menerima "Itu tidak bisa dilakukan" untuk jawaban. Kami di sini karena kami putus asa.
Addison
14
Saya mendapatkan pengecualian ini: Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType tidak yakin apa kendalanya.
Hidangan
4
Seperti @Dish, saya baru saja mendapatjava.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
Kenny Wyland
133

Saya ingin mencoba menjabarkan jawaban dari @DerMike untuk menjelaskan:

Pertama, tipe erasure tidak berarti bahwa JDK dihilangkan informasi tipe saat runtime. Ini adalah metode untuk memungkinkan kompatibilitas jenis waktu kompilasi dan jenis runtime untuk hidup berdampingan dalam bahasa yang sama. Seperti yang disiratkan oleh blok kode ini, JDK menyimpan informasi tipe yang terhapus - hanya saja tidak terkait dengan gips dan barang yang diperiksa.

Kedua, ini memberikan informasi jenis generik ke kelas generik tepat satu tingkat di atas hirarki dari jenis beton yang diperiksa - yaitu kelas induk abstrak dengan parameter tipe generik dapat menemukan jenis beton yang sesuai dengan parameter jenisnya untuk implementasi beton itu sendiri. yang secara langsung mewarisi darinya. Jika kelas ini non-abstrak dan instantiated, atau implementasi konkret adalah dua tingkat turun, ini tidak akan berhasil (walaupun sedikit jimmying dapat membuatnya berlaku untuk jumlah level yang telah ditentukan di luar satu, atau naik ke kelas terendah dengan parameter tipe X generik, dan lain-lain).

Pokoknya, lanjut ke penjelasan. Ini kodenya lagi, dipisahkan menjadi beberapa baris untuk memudahkan referensi:

1 # Kelas genericParameter0OfThisClass = 
2 # (Kelas)
3 # ((ParameterizedType)
4 # getClass ()
5 # .getGenericSuperclass ())
6 # .getActualTypeArguments () [0];

Mari 'kita' menjadi kelas abstrak dengan tipe generik yang berisi kode ini. Bacalah ini dengan kasar dari dalam ke luar:

  • Baris 4 mendapatkan instance Kelas konkret saat ini. Ini mengidentifikasi tipe konkret keturunan langsung kita.
  • Baris 5 mendapatkan supertype kelas itu sebagai Tipe; Inilah kita. Karena kita adalah tipe parametrik, kita dapat dengan aman mengarahkan diri kita ke ParameterizedType (baris 3). Kuncinya adalah bahwa ketika Java menentukan objek Tipe ini, ia menggunakan informasi tipe yang ada pada anak untuk mengaitkan informasi tipe dengan parameter tipe kami dalam instance ParameterizedType yang baru. Jadi sekarang kita dapat mengakses jenis beton untuk obat generik kita.
  • Baris 6 mendapatkan array tipe yang dipetakan ke dalam obat generik kami, seperti yang dinyatakan dalam kode kelas. Untuk contoh ini kita tarik parameter pertama. Ini kembali sebagai Type.
  • Baris 2 menampilkan Tipe terakhir yang dikembalikan ke Kelas. Ini aman karena kita tahu tipe apa yang bisa diambil oleh parameter tipe generik kita dan dapat mengonfirmasi bahwa semuanya akan menjadi kelas (saya tidak yakin bagaimana di Jawa kita bisa mendapatkan parameter generik yang tidak memiliki instance Class terkait dengan itu, sebenarnya).

... dan itu sudah cukup. Jadi kami mendorong info jenis dari implementasi konkret kami sendiri kembali ke diri kami sendiri, dan menggunakannya untuk mengakses pegangan kelas. kita bisa menggandakan getGenericSuperclass () dan naik dua level, atau menghilangkan getGenericSuperclass () dan mendapatkan nilai untuk diri kita sendiri sebagai tipe konkret (peringatan: Saya belum menguji skenario ini, mereka belum mendatangi saya).

Akan menjadi rumit jika anak-anak Anda yang konkret menjadi jumlah hop yang sewenang-wenang pergi, atau jika Anda konkret dan tidak final, dan terutama rumit jika Anda mengharapkan anak-anak Anda (yang sangat dalam) memiliki obat generik sendiri. Tetapi Anda biasanya dapat mendesain pertimbangan-pertimbangan itu, jadi ini yang paling membantu Anda.

Semoga ini bisa membantu seseorang! Saya tahu posting ini kuno. Saya mungkin akan memotong penjelasan ini dan menyimpannya untuk pertanyaan lain.

Mark McKenna
sumber
3
Hanya dengan menjawab, "Saya tidak yakin bagaimana di Jawa Anda akan mendapatkan parameter generik yang tidak memiliki instance Class yang terkait dengannya, sebenarnya)": Ini dimungkinkan jika parameter ke kelas generik adalah " ekspresi wildcard "(katakan:? ext SomeClass) atau" variabel tipe "(katakan: <T> di mana T berasal dari subkelas generik). dalam kedua kasus instance "Type" yang dikembalikan tidak dapat dilemparkan ke "Class" karena setiap VM mungkin memiliki implementasi yang berbeda untuk keduanya yang mengimplementasikan antarmuka Type tetapi tidak berasal langsung dari java.lang.Class.
Roberto Andrade
19

Sebenarnya saya berhasil ini. Pertimbangkan cuplikan berikut:

Method m;
Type[] genericParameterTypes = m.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
     if( genericParameterTypes[i] instanceof ParameterizedType ) {
                Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains java.lang.String for method like "method(List<String> value)"

     }
 }

Saya menggunakan jdk 1.6

Andrey Rodionov
sumber
12
-1: ini tidak menyelesaikan masalah dalam pertanyaan; itu hanya memberi Anda tipe yang dideklarasikan dalam tanda tangan metode, bukan tipe runtime yang sebenarnya.
Michael Borgwardt
5
Mungkin tidak menjawab pertanyaan OP, tapi ini teknik yang berguna.
dnault
3
Ini adalah jawaban untuk pertanyaan yang sama sekali berbeda.
Dawood ibn Kareem
Anda dapat menghubungi m.getParameterTypes () untuk mendapatkan semua tipe aktual. Ini akan mengembalikan "Daftar" sebagai contoh.
Lee Meador
Jika GenericParameterTypes [i] bukan turunan dari TipeizedType, itu bisa menjadi Kelas. Itu berarti bahwa argumen tertentu untuk metode ini tidak parameter sama sekali.
Lee Meador
18

Sebenarnya ada solusi, dengan menerapkan trik "kelas anonim" dan ide-ide dari Token Jenis Super :

public final class Voodoo {
    public static void chill(final List<?> aListWithSomeType) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
        System.out.println(((ParameterizedType) aListWithSomeType
            .getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]);
    }
    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>() {});
    }
}
class SpiderMan {
}

Trik terletak pada penciptaan kelas anonim , new ArrayList<SpiderMan>() {}, di tempat aslinya (sederhana) new ArrayList<SpiderMan>(). Penggunaan kelas anoymous (jika mungkin) memastikan bahwa kompiler menyimpan informasi tentang argumen tipe yang SpiderMandiberikan ke parameter tipe List<?>. Voa!

Yann-Gaël Guéhéneuc
sumber
Ini secara tidak langsung menjawab pertanyaan: Masalah asli tidak memiliki solusi. Agar tipe dapat mempertahankan parameter generiknya, ia perlu disematkan pada tipe lain, seperti subkelas. Contoh ini menunjukkan bagaimana Anda perlu mengubah kode panggilan agar memungkinkan untuk memulihkan parameter tipe generik di chill ().
Florian F
FYI, tautan pertama sudah mati
Ashvin Sharma
@AshvinSharma Saya percaya bahwa bahan yang sama tersedia di sini: rgomes.info/using-typetokens-to-retrieve-generic-parameters
Yann-Gaël Guéhéneuc
7

Karena jenis penghapusan, satu-satunya cara untuk mengetahui jenis daftar adalah dengan mengirimkan jenis sebagai parameter ke metode:

public class Main {

    public static void main(String[] args) {
        doStuff(new LinkedList<String>(), String.class);

    }

    public static <E> void doStuff(List<E> list, Class<E> clazz) {

    }

}
Jared Russell
sumber
7

Lampiran untuk jawaban @ DerMike untuk mendapatkan parameter generik dari antarmuka yang diparameterisasi (menggunakan metode #getGenericInterfaces () di dalam metode default Java-8 untuk menghindari duplikasi):

import java.lang.reflect.ParameterizedType; 

public class ParametrizedStuff {

@SuppressWarnings("unchecked")
interface Awesomable<T> {
    default Class<T> parameterizedType() {
        return (Class<T>) ((ParameterizedType)
        this.getClass().getGenericInterfaces()[0])
            .getActualTypeArguments()[0];
    }
}

static class Beer {};
static class EstrellaGalicia implements Awesomable<Beer> {};

public static void main(String[] args) {
    System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
    // --> Type is: ParameterizedStuff$Beer
}
Campa
sumber
Ini bukan yang diminta oleh pos asli. Di sini, Anda telah membuat subkelas dari Awesomeable<Beer>. Dalam hal itu, informasi jenis disimpan. Jika Anda meneruskan new Awesomable<Beer> ()ke metode wt tidak akan berfungsi.
Florian F
@FlorianF Jika Anda meneruskan Awesomable<Beer>dengan cepat tanpa definisi eksplisit dari subkelas beton seperti EstrellaGaliciadalam kasus ini, masih mengeluarkan tipe parameter: Saya menjalankannya sekarang: System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());---> Jenisnya adalah: ParameterizedStuff $ Beer
Campa
6

Tidak, itu tidak mungkin. Karena masalah kompatibilitas ke bawah, generik Java didasarkan pada penghapusan tipe , ia saat runtime, semua yang Anda miliki adalah objek non-generik List. Ada beberapa informasi tentang parameter tipe saat runtime, tetapi ia berada di definisi kelas (yaitu Anda dapat bertanya " tipe generik apa yang digunakan definisi bidang ini? "), Bukan dalam instance objek.

Michael Borgwardt
sumber
Meskipun java bisa menyimpan ini sebagai data meta selama runtime, bukan? Saya berharap demikian. Nasib buruk.
cimnine
1
@ user99475: Tidak. Saya benar dan Andrey salah. Dia merujuk pada tipe informasi yang saya sebutkan di bagian kedua dari jawaban saya, tetapi bukan itu yang ditanyakan.
Michael Borgwardt
5

Seperti yang ditunjukkan oleh @bertolami, kita tidak mungkin mendapatkan tipe variabel dan mendapatkan nilainya di masa depan (konten dari variabel typeOfList).

Namun demikian, Anda dapat melewatkan kelas sebagai parameter di atasnya seperti ini:

public final class voodoo {
    public static void chill(List<T> aListWithTypeSpiderMan, Class<T> clazz) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = clazz;
    }

    public static void main(String... args) {
        chill(new List<SpiderMan>(), Spiderman.class );
    }
}

Itu kurang lebih apa yang dilakukan Google ketika Anda harus melewatkan variabel kelas ke konstruktor ActivityInstrumentationTestCase2 .

Snicolas
sumber
3

Tidak itu tidak mungkin.

Anda bisa mendapatkan jenis bidang generik yang diberikan kelas adalah satu-satunya pengecualian untuk aturan itu dan bahkan itu sedikit peretasan.

Lihat Mengetahui jenis generik di Jawa untuk contohnya.

cletus
sumber
1

Anda bisa mendapatkan jenis parameter generik dengan refleksi seperti dalam contoh ini yang saya temukan di sini :

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Home<E> {
    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass(){
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>{}
    private static class StringBuilderHome extends Home<StringBuilder>{}
    private static class StringBufferHome extends Home<StringBuffer>{}   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }
}
madx
sumber
bagaimana Anda melakukan hal yang sama untuk mendapatkan V of Home <K, V>?
Rafael Sanches
1

Jawaban cepat atas Pertanyaannya adalah Anda tidak dapat melakukannya, karena penghapusan tipe generik Java.

Jawaban yang lebih panjang adalah jika Anda telah membuat daftar seperti ini:

new ArrayList<SpideMan>(){}

Maka dalam hal ini tipe generik dipertahankan dalam superclass generik dari kelas anonim baru di atas.

Bukannya saya merekomendasikan melakukan ini dengan daftar, tetapi ini adalah implementasi pendengar:

new Listener<Type>() { public void doSomething(Type t){...}}

Dan karena mengekstrapolasi tipe generik dari kelas super dan antarmuka super berubah di antara JVM, solusi generiknya tidak semaju yang disarankan oleh beberapa jawaban.

Di sini sekarang saya melakukannya.

TacB0sS
sumber
0

Ini tidak mungkin karena obat generik di Jawa hanya dianggap pada waktu kompilasi. Jadi, generik Java hanyalah semacam pra-prosesor. Namun Anda bisa mendapatkan kelas aktual dari anggota daftar.

bertolami
sumber
Ya itulah yang saya lakukan sekarang. Tapi sekarang saya ingin tahu tipenya walaupun daftar itu kosong. Tapi empat pria tidak mungkin salah. Terima kasih semua)!
cimnine
19
Ini tidak benar. Meskipun rumit, Anda dapat melihat di posting berikutnya bahwa ParameterizedType memungkinkan untuk melakukan itu.
Edmondo1984
1
Setuju, itu bisa dilakukan. Contoh dari DerMike menguraikannya (bekerja untuk saya).
mac
LOL di "empat cowok tidak mungkin salah". Jawaban ini benar.
Dawood ibn Kareem
0

Saya telah mengkodekan ini untuk metode yang ingin menerima atau kembali Iterable<?...>. Ini kodenya:

/**
 * Assuming the given method returns or takes an Iterable<T>, this determines the type T.
 * T may or may not extend WindupVertexFrame.
 */
private static Class typeOfIterable(Method method, boolean setter)
{
    Type type;
    if (setter) {
        Type[] types = method.getGenericParameterTypes();
        // The first parameter to the method expected to be Iterable<...> .
        if (types.length == 0)
            throw new IllegalArgumentException("Given method has 0 params: " + method);
        type = types[0];
    }
    else {
        type = method.getGenericReturnType();
    }

    // Now get the parametrized type of the generic.
    if (!(type instanceof ParameterizedType))
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
    ParameterizedType pType = (ParameterizedType) type;
    final Type[] actualArgs = pType.getActualTypeArguments();
    if (actualArgs.length == 0)
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);

    Type t = actualArgs[0];
    if (t instanceof Class)
        return (Class<?>) t;

    if (t instanceof TypeVariable){
        TypeVariable tv =  (TypeVariable) actualArgs[0];
        AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
        GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
        return (Class) tv.getAnnotatedBounds()[0].getType();
    }

    throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}
Ondra Žižka
sumber
0

Anda tidak bisa mendapatkan parameter generik dari variabel. Tapi Anda bisa dari deklarasi metode atau bidang:

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();
Anton Pogonets
sumber
Ini memberi saya Pengecualian di utas "utama" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Lluis Martinez
'params' adalah 'Type's yang memiliki beberapa subinterfaces. TypeVariableImpl adalah salah satu yang digunakan untuk argumen metode dan memberitahu nama generik di dalam <> s, bukan Kelas yang diwakilinya.
Lee Meador
0

Bagi saya membaca potongan kode ini sulit, saya hanya membaginya menjadi 2 baris yang dapat dibaca:

// assuming that the Generic Type parameter is of type "T"
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

Saya ingin membuat instance dari parameter Type tanpa memiliki parameter apa pun pada metode saya:

publc T getNewTypeInstance(){
    ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
    Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

    // for me i wanted to get the type to create an instance
    // from the no-args default constructor
    T t = null;
    try{
        t = c.newInstance();
    }catch(Exception e){
        // no default constructor available
    }
    return t;
}
Ahmed Adel Ismail
sumber
0

Ini trik lain. Gunakan array vararg generik

import java.util.ArrayList;

class TypedArrayList<E> extends ArrayList<E>
{
    @SafeVarargs
    public TypedArrayList (E... typeInfo)
    {
        // Get generic type at runtime ...
        System.out.println (typeInfo.getClass().getComponentType().getTypeName());
    }
}

public class GenericTest
{
    public static void main (String[] args)
    {
        // No need to supply the dummy argument
        ArrayList<Integer> ar1 = new TypedArrayList<> ();
        ArrayList<String> ar2 = new TypedArrayList<> ();
        ArrayList<?> ar3 = new TypedArrayList<> ();
    }
}
paul
sumber
0

Saya perhatikan bahwa banyak orang condong ke arah getGenericSuperclass()solusi:

class RootGeneric<T> {
  public Class<T> persistentClass = (Class<T>)
    ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
}

Namun, solusi ini rawan kesalahan. Ini tidak akan berfungsi dengan baik jika ada obat generik di keturunan. Pertimbangkan ini:

class Foo<S> extends RootGeneric<Integer> {}

class Bar extends Foo<Double> {}

Jenis apa yang akan Bar.persistentClassdimiliki? Class<Integer>? Tidak akan Class<Double>. Ini akan terjadi karena getClass()selalu mengembalikan kelas paling atas, yang Bardalam hal ini, dan kelas super generiknya Foo<Double>. Oleh karena itu, tipe argumennya adalah Double.

Jika Anda membutuhkan solusi andal yang tidak gagal saya dapat menyarankan dua.

  1. Gunakan Guava. Ia memiliki kelas yang dibuat tepat untuk tujuan ini: com.google.common.reflect.TypeToken. Ini menangani semua kasus sudut dengan baik dan menawarkan beberapa fungsionalitas yang lebih bagus. Downside adalah ketergantungan ekstra. Karena Anda telah menggunakan kelas ini, kode Anda akan terlihat sederhana dan jelas, seperti ini:
class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. Gunakan metode khusus di bawah ini. Ini mengimplementasikan logika yang disederhanakan secara signifikan mirip dengan kelas Guava, yang disebutkan di atas. Namun, saya tidak menjamin itu rawan kesalahan. Itu memang memecahkan masalah dengan keturunan generik sekalipun.
abstract class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  private Class<T> getTypeOfT() {
    Class<T> type = null;
    Class<?> iter = getClass();
    while (iter.getSuperclass() != null) {
      Class<?> next = iter.getSuperclass();
      if (next != null && next.isAssignableFrom(RootGeneric.class)) {
        type =
            (Class<T>)
                ((ParameterizedType) iter.getGenericSuperclass()).getActualTypeArguments()[0];
        break;
      }
      iter = next;
    }
    if (type == null) {
      throw new ClassCastException("Cannot determine type of T");
    }
    return type;
  }
}
real4x
sumber
-1

Menggunakan:

Class<?> typeOfTheList = aListWithTypeSpiderMan.toArray().getClass().getComponentType();
pengguna4845560
sumber
Ini salah. toArray()kembali Object[]. Anda tidak dapat dan tidak seharusnya bergantung padanya sebagai subtipe tertentu.
Radiodef