Bagaimana saya mendapatkan instance kelas dari tipe T generik?

700

Saya memiliki kelas generik Foo<T>,. Dalam suatu metode Foo, saya ingin mendapatkan instance kelas dari tipe T, tapi saya tidak bisa menelepon T.class.

Apa cara yang disukai untuk menggunakannya T.class?

robinmag
sumber
2
Coba jawaban dalam pertanyaan ini. Saya pikir itu mirip. stackoverflow.com/questions/1942644/…
Emil
3
kemungkinan duplikat Dapatkan tipe generik kelas saat runtime
7hi4g0
1
kemungkinan rangkap dari Instantiating kelas generik di Jawa
matiasg
1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

Jawaban:

567

Jawaban singkatnya adalah, bahwa tidak ada cara untuk mengetahui tipe runtime dari parameter tipe generik di Jawa. Saya sarankan membaca bab tentang penghapusan tipe dalam Tutorial Java untuk lebih jelasnya.

Solusi populer untuk ini adalah untuk melewatkan Classparameter tipe ke konstruktor dari tipe generik, misalnya

class Foo<T> {
    final Class<T> typeParameterClass;

    public Foo(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    public void bar() {
        // you can access the typeParameterClass here and do whatever you like
    }
}
Zsolt Török
sumber
73
Jawaban ini memang memberikan solusi yang valid tetapi tidak akurat untuk mengatakan tidak ada cara untuk menemukan tipe generik saat runtime. Ternyata tipe erasure jauh lebih kompleks daripada erasure blanket. Jawaban saya menunjukkan kepada Anda cara mendapatkan tipe generik kelas.
Ben Thurley
3
Tren @BenThurley Rapi, tapi sejauh yang saya bisa lihat itu hanya berfungsi jika ada supertype generik untuk digunakan. Dalam contoh saya, Anda tidak dapat mengambil jenis T di Foo <T>.
Zsolt Török
@webjockey Tidak, tidak seharusnya. Menetapkan typeParameterClasstanpa penugasan default di konstruktor baik-baik saja. Tidak perlu mengaturnya untuk yang kedua kalinya.
Adowrath
Ini adalah solusi pertama yang muncul di pikiran, tetapi kadang-kadang bukan Anda yang akan membuat / memulai objek. Jadi Anda tidak akan dapat menggunakan konstruktor. Untuk misalnya saat mengambil entitas JPA dari database.
Paramvir Singh Karwal
233

Saya mencari cara untuk melakukan ini sendiri tanpa menambahkan ketergantungan ekstra ke classpath. Setelah beberapa penyelidikan saya menemukan bahwa itu adalah mungkin selama Anda memiliki supertype generik. Ini tidak masalah bagi saya karena saya bekerja dengan layer DAO dengan supertype layer generik. Jika ini cocok dengan skenario Anda, maka itu adalah pendekatan IMHO yang paling rapi.

Sebagian besar kasus penggunaan generik yang saya temui memiliki semacam supertipe generik misalnya List<T>untuk ArrayList<T>atau GenericDAO<T>untukDAO<T> , dll.

Solusi Java murni

Artikel Mengakses tipe generik saat runtime di Java menjelaskan bagaimana Anda dapat melakukannya menggunakan Java murni.

@SuppressWarnings("unchecked")
public GenericJpaDao() {
  this.entityBeanType = ((Class) ((ParameterizedType) getClass()
      .getGenericSuperclass()).getActualTypeArguments()[0]);
}

Solusi musim semi

Proyek saya menggunakan Spring yang bahkan lebih baik karena Spring memiliki metode utilitas praktis untuk menemukan tipe. Ini adalah pendekatan terbaik bagi saya karena terlihat rapi. Saya kira jika Anda tidak menggunakan Spring Anda bisa menulis metode utilitas Anda sendiri.

import org.springframework.core.GenericTypeResolver;

public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{

    @Autowired
    private SessionFactory sessionFactory;

    private final Class<T> genericType;

    private final String RECORD_COUNT_HQL;
    private final String FIND_ALL_HQL;

    @SuppressWarnings("unchecked")
    public AbstractHibernateDao()
    {
        this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
        this.RECORD_COUNT_HQL = "select count(*) from " + this.genericType.getName();
        this.FIND_ALL_HQL = "from " + this.genericType.getName() + " t ";
    }
Ben Thurley
sumber
1
silahkan menjelaskan makna dari resolveTypeArgument argumen
gstackoverflow
getClass () adalah metode java.lang.Object yang akan mengembalikan kelas objek spesifik saat runtime, ini adalah objek yang Anda ingin atur jenisnya. AbstractHibernateDao.class hanyalah nama kelas dasar atau superclass dari hierarki kelas tipe generik. Pernyataan impor disertakan sehingga Anda harus dapat dengan mudah menemukan dokumen dan memeriksa diri Anda sendiri. Ini adalah halaman docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley
Apa solusi pegas dengan versi 4.3.6 dan lebih tinggi. Itu tidak bekerja dengan pegas 4.3.6.
Erlan
1
Tautan dalam "solusi Java Murni" rusak, sekarang blog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen
1
@ AlikElzin-kilaka akan diinisialisasi dalam konstruktor menggunakan kelas Spring GenericTypeResolver.
Ben Thurley
103

Namun ada celah kecil: jika Anda mendefinisikan Fookelas Anda sebagai abstrak. Itu berarti Anda harus membuat instantiate kelas Anda sebagai:

Foo<MyType> myFoo = new Foo<MyType>(){};

(Perhatikan kurung ganda di bagian akhir.)

Sekarang Anda dapat mengambil jenis Tsaat runtime:

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];

Namun perlu dicatat bahwa mySuperclassitu harus menjadi superkelas dari definisi kelas sebenarnya mendefinisikan tipe final untuk T.

Ini juga tidak terlalu elegan, tetapi Anda harus memutuskan apakah Anda lebih suka new Foo<MyType>(){}atau new Foo<MyType>(MyType.class);dalam kode Anda.


Sebagai contoh:

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

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;

/**
 * Captures and silently ignores stack exceptions upon popping.
 */
public abstract class SilentStack<E> extends ArrayDeque<E> {
  public E pop() {
    try {
      return super.pop();
    }
    catch( NoSuchElementException nsee ) {
      return create();
    }
  }

  public E create() {
    try {
      Type sooper = getClass().getGenericSuperclass();
      Type t = ((ParameterizedType)sooper).getActualTypeArguments()[ 0 ];

      return (E)(Class.forName( t.toString() ).newInstance());
    }
    catch( Exception e ) {
      return null;
    }
  }
}

Kemudian:

public class Main {
    // Note the braces...
    private Deque<String> stack = new SilentStack<String>(){};

    public static void main( String args[] ) {
      // Returns a new instance of String.
      String s = stack.pop();
      System.out.printf( "s = '%s'\n", s );
    }
}
bert bruynooghe
sumber
5
Ini dengan mudah jawaban terbaik di sini! Juga, untuk apa nilainya, ini adalah strategi yang digunakan Google Guice untuk mengikat kelas denganTypeLiteral
ATG
14
Perhatikan bahwa setiap kali metode konstruksi objek ini digunakan, kelas anonim baru dibuat. Dengan kata lain, dua objek adan bdibuat dengan cara ini akan memperluas kelas yang sama tetapi tidak memiliki kelas instance yang identik. a.getClass() != b.getClass()
Martin Serrano
3
Ada satu skenario di mana ini tidak berhasil. Jika Foo harus mengimplementasikan antarmuka, seperti Serializable, dari kelas anonim tidak akan Serializable kecuali kelas instantiating adalah. Saya mencoba untuk mengatasinya dengan membuat kelas pabrik serializable yang menciptakan kelas anonim yang berasal dari Foo, tetapi kemudian, untuk beberapa alasan, getActualTypeArguments mengembalikan tipe generik alih-alih kelas aktual. Misalnya: (FooFactory <MyType> ()) baru. CreateFoo ()
Lior Chaga
38

Pendekatan / solusi / solusi standar adalah dengan menambahkan classobjek ke konstruktor, seperti:

 public class Foo<T> {

    private Class<T> type;
    public Foo(Class<T> type) {
      this.type = type;
    }

    public Class<T> getType() {
      return type;
    }

    public T newInstance() {
      return type.newInstance();
    }
 }
Andreas Dolk
sumber
1
Tapi sepertinya tidak bisa @autowired dalam penggunaan aktual, cara apa pun untuk mengatasinya?
Alfred Huang
@AlfredHuang Pekerjaan di sekitar adalah membuat kacang untuk kelas yang melakukan ini dan tidak bergantung pada autowiring.
Calebj
20

Bayangkan Anda memiliki superclass abstrak yang generik:

public abstract class Foo<? extends T> {}

Dan kemudian Anda memiliki kelas kedua yang memanjang Foo dengan Bar generik yang memanjang T:

public class Second extends Foo<Bar> {}

Anda bisa mendapatkan kelas Bar.classdi kelas Foo dengan memilih Type(dari jawaban bert bruynooghe) dan menyimpulkannya menggunakan Classinstance:

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
//Parse it as String
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);

Anda harus mencatat bahwa operasi ini tidak ideal, sehingga merupakan ide bagus untuk men-cache nilai yang dihitung untuk menghindari beberapa perhitungan. Salah satu kegunaan khas adalah dalam implementasi DAO generik.

Implementasi akhir:

public abstract class Foo<T> {

    private Class<T> inferedClass;

    public Class<T> getGenericClass(){
        if(inferedClass == null){
            Type mySuperclass = getClass().getGenericSuperclass();
            Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
            String className = tType.toString().split(" ")[1];
            inferedClass = Class.forName(className);
        }
        return inferedClass;
    }
}

Nilai yang dikembalikan adalah Bar.class ketika dipanggil dari kelas Foo di fungsi lain atau dari kelas Bar.

droidpl
sumber
1
toString().split(" ")[1]itulah masalahnya, hindari"class "
IgniteCoders
16

Berikut ini solusi yang berhasil:

@SuppressWarnings("unchecked")
private Class<T> getGenericTypeClass() {
    try {
        String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
        Class<?> clazz = Class.forName(className);
        return (Class<T>) clazz;
    } catch (Exception e) {
        throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
    }
} 

CATATAN: Dapat digunakan hanya sebagai superclass

  1. Harus diperluas dengan kelas yang diketik ( Child extends Generic<Integer>)

ATAU

  1. Harus dibuat sebagai implementasi anonim ( new Generic<Integer>() {};)
Yaroslav Kovbas
sumber
3
getTypeName memanggil toString, jadi bisa diganti dengan .getActualTypeArguments () [0] .toString ();
Yaroslav Kovbas
9

Rute yang lebih baik daripada Kelas yang disarankan orang lain adalah lewat di objek yang dapat melakukan apa yang akan Anda lakukan dengan Kelas, misalnya, membuat instance baru.

interface Factory<T> {
  T apply();
}

<T> void List<T> make10(Factory<T> factory) {
  List<T> result = new ArrayList<T>();
  for (int a = 0; a < 10; a++)
    result.add(factory.apply());
  return result;
}

class FooFactory<T> implements Factory<Foo<T>> {
  public Foo<T> apply() {
    return new Foo<T>();
  }
}

List<Foo<Integer>> foos = make10(new FooFactory<Integer>());
Ricky Clarkson
sumber
@ Ricky Clarkson: Saya tidak melihat bagaimana pabrik ini harus mengembalikan foo parametrized. Bisakah Anda jelaskan bagaimana Anda mendapatkan Foo <T> dari ini? Sepertinya saya ini hanya memberikan Foo yang tidak diparameterisasi. Bukankah T di make10 hanya Foo di sini?
ib84
@ ib84 Saya sudah memperbaiki kode; Sepertinya saya telah melewatkan bahwa Foo parameterisasi ketika saya menulis jawaban awalnya.
Ricky Clarkson
9

Saya punya masalah ini di kelas generik abstrak. Dalam kasus khusus ini, solusinya lebih sederhana:

abstract class Foo<T> {
    abstract Class<T> getTClass();
    //...
}

dan kemudian pada kelas turunan:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
pengguna1154664
sumber
Ya itu, tetapi saya ingin meninggalkan sangat minimum yang perlu dilakukan saat memperpanjang kelas ini. Periksa jawaban droidpl
Paramvir Singh Karwal
5

Saya punya solusi (jelek tapi efektif) untuk masalah ini, yang saya gunakan baru-baru ini:

import java.lang.reflect.TypeVariable;


public static <T> Class<T> getGenericClass()
{
    __<T> ins = new __<T>();
    TypeVariable<?>[] cls = ins.getClass().getTypeParameters(); 

    return (Class<T>)cls[0].getClass();
}

private final class __<T> // generic helper class which does only provide type information
{
    private __()
    {
    }
}
tidak diketahui6656
sumber
3

Saya menemukan cara umum dan sederhana untuk melakukan itu. Di kelas saya, saya membuat metode yang mengembalikan tipe generik sesuai dengan posisinya dalam definisi kelas. Mari kita asumsikan definisi kelas seperti ini:

public class MyClass<A, B, C> {

}

Sekarang mari kita membuat beberapa atribut untuk tetap mengetik:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;

// Getters and setters (not necessary if you are going to use them internally)

    } 

Kemudian Anda bisa membuat metode generik yang mengembalikan tipe berdasarkan indeks definisi generik:

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {
        // To make it use generics without supplying the class type
        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }

Akhirnya, dalam konstruktor cukup panggil metode dan kirim indeks untuk setiap jenis. Kode lengkap akan terlihat seperti:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;


    public MyClass() {
      this.aType = (Class<A>) getGenericClassType(0);
      this.bType = (Class<B>) getGenericClassType(1);
      this.cType = (Class<C>) getGenericClassType(2);
    }

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {

        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }
}
Juan Urrego
sumber
2

Seperti yang dijelaskan dalam jawaban lain, gunakan ini ParameterizedType pendekatan , Anda perlu memperluas kelas, tapi itu sepertinya pekerjaan ekstra untuk membuat kelas baru yang meluas ...

Jadi, membuat abstrak kelas memaksa Anda untuk memperpanjangnya, sehingga memenuhi persyaratan subklasifikasi. (menggunakan @Getter lombok).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

    public ConfigurationDefinition(...) {
        this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        ...
    }
}

Sekarang untuk memperluasnya tanpa mendefinisikan kelas baru. (Catat {} di bagian akhir ... diperpanjang, tetapi jangan menimpa apa pun - kecuali jika Anda mau).

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
Riaan Schutte
sumber
2

Saya berasumsi bahwa, karena Anda memiliki kelas generik, Anda akan memiliki variabel seperti itu:

private T t;

(variabel ini perlu mengambil nilai pada konstruktor)

Dalam hal ini, Anda cukup membuat metode berikut:

Class<T> getClassOfInstance()
{
    return (Class<T>) t.getClass();
}

Semoga ini bisa membantu!

Pontios
sumber
1
   public <T> T yourMethodSignature(Class<T> type) {

        // get some object and check the type match the given type
        Object result = ...            

        if (type.isAssignableFrom(result.getClass())) {
            return (T)result;
        } else {
            // handle the error
        }
   }
Abel ANEIROS
sumber
1

Jika Anda memperluas atau mengimplementasikan kelas / antarmuka apa saja yang menggunakan generik, Anda bisa mendapatkan Generic Type dari kelas induk / antarmuka, tanpa memodifikasi kelas / antarmuka yang ada sama sekali.

Mungkin ada tiga kemungkinan,

Kasus 1 Saat kelas Anda memperluas kelas yang menggunakan Generics

public class TestGenerics {
    public static void main(String[] args) {
        Type type = TestMySuperGenericType.class.getGenericSuperclass();
        Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
        for(Type gType : gTypes){
            System.out.println("Generic type:"+gType.toString());
        }
    }
}

class GenericClass<T> {
    public void print(T obj){};
}

class TestMySuperGenericType extends GenericClass<Integer> {
}

Kasus 2 Saat kelas Anda mengimplementasikan antarmuka yang menggunakan Generics

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

class TestMySuperGenericType implements GenericClass<Integer> {
    public void print(Integer obj){}
}

Kasus 3 Saat antarmuka Anda memperluas antarmuka yang menggunakan Generics

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

interface TestMySuperGenericType extends GenericClass<Integer> {
}
Anil Agrawal
sumber
1

Itu cukup lurus ke depan. Jika Anda membutuhkan dari dalam kelas yang sama:

Class clazz = this.getClass();
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
try {
        Class typeClass = Class.forName( parameterizedType.getActualTypeArguments()[0].getTypeName() );
        // You have the instance of type 'T' in typeClass variable

        System.out.println( "Class instance name: "+  typeClass.getName() );
    } catch (ClassNotFoundException e) {
        System.out.println( "ClassNotFound!! Something wrong! "+ e.getMessage() );
    }
Gana
sumber
0

Sebenarnya, saya kira Anda memiliki bidang di kelas tipe T. Jika tidak ada bidang tipe T, apa gunanya memiliki Tipe generik? Jadi, Anda cukup melakukan instanceof pada bidang itu.

Dalam kasus saya, saya punya

Daftar item <T>;
di kelas saya, dan saya memeriksa apakah tipe kelas "Lokalitas" oleh

if (items.get (0) instance of Locality) ...

Tentu saja, ini hanya berfungsi jika jumlah total kelas yang mungkin terbatas.

Christine
sumber
4
Apa yang harus saya lakukan jika items.isEmpty () benar?
chaotic3quilibrium
0

Pertanyaan ini sudah lama, tetapi sekarang yang terbaik adalah menggunakan google Gson.

Contoh untuk mendapatkan kustom viewModel.

Class<CustomViewModel<String>> clazz = new GenericClass<CustomViewModel<String>>().getRawType();
CustomViewModel<String> viewModel = viewModelProvider.get(clazz);

Kelas tipe generik

class GenericClass<T>(private val rawType: Class<*>) {

    constructor():this(`$Gson$Types`.getRawType(object : TypeToken<T>() {}.getType()))

    fun getRawType(): Class<T> {
        return rawType as Class<T>
    }
}
Vahe Gharibyan
sumber
0

Saya ingin meneruskan T.class ke metode yang menggunakan Generics

Metode readFile membaca file .csv yang ditentukan oleh fileName dengan fullpath. Mungkin ada file csv dengan konten yang berbeda maka saya harus lulus kelas model file sehingga saya bisa mendapatkan objek yang sesuai. Karena ini membaca file csv yang ingin saya lakukan dengan cara yang umum. Untuk beberapa alasan atau tidak ada solusi di atas yang berfungsi untuk saya. Saya perlu menggunakannya Class<? extends T> typeuntuk membuatnya bekerja. Saya menggunakan pustaka opencsv untuk mem-parsing file CSV.

private <T>List<T> readFile(String fileName, Class<? extends T> type) {

    List<T> dataList = new ArrayList<T>();
    try {
        File file = new File(fileName);

        Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        Reader headerReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));

        CSVReader csvReader = new CSVReader(headerReader);
        // create csv bean reader
        CsvToBean<T> csvToBean = new CsvToBeanBuilder(reader)
                .withType(type)
                .withIgnoreLeadingWhiteSpace(true)
                .build();

        dataList = csvToBean.parse();
    }
    catch (Exception ex) {
        logger.error("Error: ", ex);
    }

    return dataList;
}

Ini adalah bagaimana metode readFile dipanggil

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);
Madhu Tomy
sumber
-4

Saya menggunakan solusi untuk ini:

class MyClass extends Foo<T> {
....
}

MyClass myClassInstance = MyClass.class.newInstance();
nikolai.serdiuk
sumber