Jak uzyskać instancję klasy ogólnego typu T?

700

Mam klasy leków generycznych Foo<T>. W metodzie Foochcę uzyskać instancję klasy typu T, ale po prostu nie mogę wywoływać T.class.

Jaki jest preferowany sposób obejścia tego problemu T.class?

robinmag
źródło
2
Spróbuj odpowiedzi w tym pytaniu. Myślę, że jest podobnie. stackoverflow.com/questions/1942644/…
Emil
1
możliwy duplikat tworzenia instancji klasy ogólnej w Javie
matiasg
1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

Odpowiedzi:

569

Krótka odpowiedź brzmi, że nie ma sposobu, aby dowiedzieć się, jakie parametry wykonawcze mają ogólne parametry typu w Javie. Sugeruję przeczytanie rozdziału o usuwaniu czcionek w samouczku Java, aby uzyskać więcej informacji.

Popularnym rozwiązaniem tego jest przekazanie Classparametru typu do konstruktora typu ogólnego, np

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
źródło
73
Ta odpowiedź stanowi prawidłowe rozwiązanie, ale niedokładne jest stwierdzenie, że nie można znaleźć typu ogólnego w czasie wykonywania. Okazuje się, że kasowanie typu jest o wiele bardziej skomplikowane niż kasowanie ogólne. Moja odpowiedź pokazuje, jak uzyskać ogólny typ zajęć.
Ben Thurley,
3
@BenThurley Neat trick, ale o ile widzę, działa tylko, jeśli istnieje ogólny nadtyp, którego można użyć. W moim przykładzie nie można pobrać typu T w Foo <T>.
Zsolt Török
@webjockey Nie, nie powinieneś. Przypisanie typeParameterClassdomyślnego przypisania do konstruktora jest w porządku. Nie ma potrzeby ustawiania go po raz drugi.
Adowrath,
To pierwsze rozwiązanie, które przychodzi na myśl, ale czasami to nie Ty będziesz tworzył / inicjował obiekty. Nie będziesz mógł używać konstruktora. Na przykład podczas pobierania jednostek JPA z bazy danych.
Paramvir Singh Karwal
234

Szukałem sposobu na zrobienie tego sam, bez dodawania dodatkowej zależności do ścieżki klas. Po pewnym dochodzeniu stwierdziłem, że jest to możliwe, dopóki masz ogólny nadtyp. Było to dla mnie OK, ponieważ pracowałem z warstwą DAO z nadtypem warstwy ogólnej. Jeśli to pasuje do twojego scenariusza, to jest to najładniejsze podejście IMHO.

Większość ogólnych przypadków użycia, z którymi się spotkałem, ma jakiś rodzaj ogólny, np. List<T>Dla ArrayList<T>lub GenericDAO<T>dlaDAO<T> itp.

Rozwiązanie Pure Java

Artykuł Uzyskiwanie dostępu do typów ogólnych w środowisku wykonawczym w Javie wyjaśnia, jak to zrobić za pomocą czystej Java.

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

Wiosenne rozwiązanie

Mój projekt wykorzystywał Spring, co jest jeszcze lepsze, ponieważ Spring ma przydatną metodę użyteczną do znalezienia tego typu. Jest to dla mnie najlepsze podejście, ponieważ wygląda najładniej. Myślę, że gdybyś nie używał Springa, mógłbyś napisać własną metodę narzędzia.

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
źródło
1
proszę wyjaśnić sens resolveTypeArgument argumentów
gstackoverflow
getClass () jest metodą java.lang.Object, która zwróci klasę określonego obiektu w czasie wykonywania, jest to obiekt, dla którego chcesz rozwiązać typ. Klasa AbstractHibernateEXE.class to tylko nazwa klasy bazowej lub nadklasy ogólnej hierarchii klas typów. Dołączona jest instrukcja importu, więc powinieneś być w stanie łatwo znaleźć dokumenty i sprawdzić się. To jest strona docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley
Co to jest rozwiązanie sprężynowe w wersji 4.3.6 i wyższej. Nie działa ze sprężyną 4.3.6.
Erlan,
1
Link w „Pure Java solution” jest zepsuty, teraz jest blog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen
1
@ AlikElzin-kilaka zostaje zainicjowany w konstruktorze za pomocą klasy Spring GenericTypeResolver.
Ben Thurley,
103

Istnieje jednak niewielka luka: jeśli zdefiniujesz swoją Fooklasę jako abstrakcyjną. Oznaczałoby to, że musisz stworzyć swoją klasę jako:

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

(Zwróć uwagę na podwójne nawiasy klamrowe na końcu.)

Teraz możesz pobrać typ Tw czasie wykonywania:

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

Zauważ jednak, że mySuperclassmusi to być nadklasa definicji klasy, która faktycznie określa ostateczny typ T.

Nie jest również zbyt elegancki, ale musisz zdecydować, czy wolisz, new Foo<MyType>(){}czy new Foo<MyType>(MyType.class);w swoim kodzie.


Na przykład:

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

Następnie:

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
źródło
5
To jest najlepsza odpowiedź tutaj! Co do wartości, jest to strategia, którą Google Guice stosuje do wiązania klas zTypeLiteral
ATG
14
Zauważ, że za każdym razem, gdy używana jest ta metoda konstrukcji obiektu, tworzona jest nowa anonimowa klasa. Innymi słowy, dwa obiekty ai butworzone w ten sposób będą rozszerzać tę samą klasę, ale nie będą miały identycznych klas instancji. a.getClass() != b.getClass()
Martin Serrano,
3
Jest jeden scenariusz, w którym to nie działa. Jeśli Foo powinien zaimplementować interfejs, taki jak Serializable, anonimowa klasa nie byłaby Serializable, chyba że tworzenie instancji klasy jest. Próbowałem to obejść, tworząc możliwą do serializacji klasę fabryczną, która tworzy anonimową klasę pochodzącą od Foo, ale z jakiegoś powodu getActualTypeArguments zwraca typ ogólny zamiast faktycznej klasy. Na przykład: (new FooFactory <MyType> ()). CreateFoo ()
Lior Chaga
38

Standardowym podejściem / obejściem / rozwiązaniem jest dodanie classobiektu do konstruktora (-ów), na przykład:

 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
źródło
1
Ale wydawało się, że nie można @autowired w rzeczywistym użyciu, w jakikolwiek sposób obejść?
Alfred Huang
@AlfredHuang Obejściem problemu będzie stworzenie fasoli dla klasy, która to robi i nie będzie polegać na automatycznym zasilaniu.
Calebj
20

Wyobraź sobie, że masz abstrakcyjną nadklasę, która jest ogólna:

public abstract class Foo<? extends T> {}

A potem masz drugą klasę, która rozszerza Foo za pomocą ogólnego paska, który rozszerza T:

public class Second extends Foo<Bar> {}

Możesz uzyskać klasę Bar.classw klasie Foo, wybierając Type(z odpowiedzi bert bruynooghe) i wnioskując ją za pomocą Classinstancji:

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

Trzeba pamiętać, że ta operacja nie jest idealna, dlatego dobrym pomysłem jest buforowanie obliczonej wartości, aby uniknąć wielokrotnych obliczeń. Jednym z typowych zastosowań jest ogólna implementacja DAO.

Ostateczne wdrożenie:

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

Zwracana wartość to Bar.class po wywołaniu z klasy Foo w innej funkcji lub z klasy Bar.

droidpl
źródło
1
toString().split(" ")[1]to był problem, unikaj"class "
IgniteCoders
16

Oto działające rozwiązanie:

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

UWAGI: Może być używany tylko jako nadklasa

  1. Musi zostać rozszerzony o typ klasowy ( Child extends Generic<Integer>)

LUB

  1. Musi zostać utworzony jako anonimowa implementacja ( new Generic<Integer>() {};)
Jarosław Kovbas
źródło
3
wywołania getTypeName toString, więc można je zastąpić .getActualTypeArguments () [0] .toString ();
Yaroslav Kovbas
9

Lepszą drogą niż klasą, którą sugerowali inni, jest przejście do obiektu, który może zrobić to, co zrobiłbyś z klasą, np. Utworzyć nową instancję.

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
źródło
@ Ricky Clarkson: Nie rozumiem, jak ta fabryka powinna zwracać sparametryzowane pianki. Czy mógłbyś wyjaśnić, w jaki sposób można uzyskać z tego Foo <T>? Wydaje mi się, że daje to tylko niesparametryzowane Foo. Czy T w make10 nie jest po prostu Foo?
ib84 13.03.13
@ ib84 Naprawiłem kod; Wydaje mi się, że przegapiłem to, że Foo zostało sparametryzowane, kiedy pierwotnie napisałem odpowiedź.
Ricky Clarkson
9

Miałem ten problem w abstrakcyjnej klasie ogólnej. W tym konkretnym przypadku rozwiązanie jest prostsze:

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

a później klasa pochodna:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
użytkownik1154664
źródło
Tak, ale chciałbym pozostawić bardzo minimum, które należy zrobić, rozszerzając tę ​​klasę. Sprawdź odpowiedź
droidpl
5

Mam (brzydkie, ale skuteczne) rozwiązanie tego problemu, z którego ostatnio korzystałem:

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 __()
    {
    }
}
nieznany6656
źródło
3

Znalazłem ogólny i prosty sposób na zrobienie tego. W mojej klasie stworzyłem metodę, która zwraca typ ogólny zgodnie z jego pozycją w definicji klasy. Załóżmy taką definicję klasy:

public class MyClass<A, B, C> {

}

Teraz utwórzmy niektóre atrybuty, aby zachować typy:

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)

    } 

Następnie możesz utworzyć ogólną metodę, która zwraca typ na podstawie indeksu ogólnej definicji:

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

Na koniec w konstruktorze wystarczy wywołać metodę i wysłać indeks dla każdego typu. Cały kod powinien wyglądać następująco:

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
źródło
2

Jak wyjaśniono w innych odpowiedziach, użyj tego ParameterizedType podejście, musisz rozszerzyć klasę, ale wydaje się to dodatkową pracą, aby stworzyć zupełnie nową klasę, która ją rozszerza ...

Tak więc abstrakcja klasy zmusza cię do jej rozszerzenia, spełniając w ten sposób wymóg podklasy. (używając @Getter lomboka).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

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

Teraz, aby go rozszerzyć bez definiowania nowej klasy. (Uwaga {} na końcu ... rozszerzone, ale nie zastępuj niczego - chyba że chcesz).

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
Riaan Schutte
źródło
2

Zakładam, że skoro masz klasę ogólną, miałbyś taką zmienną:

private T t;

(ta zmienna musi przyjąć wartość w konstruktorze)

W takim przypadku możesz po prostu utworzyć następującą metodę:

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

Mam nadzieję, że to pomoże!

Pontios
źródło
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
źródło
1

Jeśli rozszerzasz lub implementujesz dowolną klasę / interfejs, który używa ogólnych, możesz uzyskać Ogólny typ klasy / interfejsu nadrzędnego, bez modyfikowania żadnej istniejącej klasy / interfejsu.

Mogą być trzy możliwości,

Przypadek 1 Gdy twoja klasa rozszerza klasę, która używa 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> {
}

Przypadek 2 Gdy twoja klasa implementuje interfejs korzystający z 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){}
}

Przypadek 3 Gdy interfejs rozszerza interfejs korzystający z 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
źródło
1

To całkiem proste. Jeśli potrzebujesz z tej samej klasy:

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
źródło
0

Właściwie przypuszczam, że masz pole w swojej klasie typu T. Jeśli nie ma pola typu T, po co mieć ogólny typ? Możesz więc po prostu zrobić instancję na tym polu.

W moim przypadku mam

Wyświetl elementy <T>;
w mojej klasie i sprawdzam, czy typem klasy jest „Lokalizacja” wg

if (items.get (0) instanceof Locality) ...

Oczywiście działa to tylko wtedy, gdy całkowita liczba możliwych klas jest ograniczona.

Christine
źródło
4
Co mam zrobić, jeśli item.isEmpty () jest prawdziwy?
chaotic3quilibrium
0

To pytanie jest stare, ale teraz najlepsze jest korzystanie z Google Gson .

Przykład uzyskania niestandardowego viewModel.

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

Ogólna klasa typów

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
źródło
0

Chciałem przekazać T.classowi metodę, która korzysta z Generics

Metoda readFile czyta plik .csv określony przez fileName z pełną ścieżką. Mogą istnieć pliki csv o różnej zawartości, dlatego muszę przekazać klasę pliku modelu, aby uzyskać odpowiednie obiekty. Ponieważ to czyta plik csv, chciałem zrobić w ogólny sposób. Z tego czy innego powodu żadne z powyższych rozwiązań nie działało dla mnie. Muszę użyć, Class<? extends T> typeżeby to działało. Używam biblioteki opencsv do parsowania plików 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;
}

W ten sposób wywoływana jest metoda readFile

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);
Madhu Tomy
źródło
-4

Korzystam z obejścia tego:

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

MyClass myClassInstance = MyClass.class.newInstance();
nikolai.serdiuk
źródło