제네릭 형식 T의 클래스 인스턴스를 어떻게 얻습니까?


700

제네릭 클래스가 Foo<T>있습니다. 의 메소드에서 Foo유형의 클래스 인스턴스를 가져오고 T싶지만 호출 할 수는 없습니다 T.class.

를 사용하여 해결하는 가장 좋은 방법은 무엇입니까 T.class?


2
이 질문에 대한 답변을 시도해보십시오. stackoverflow.com/questions/1942644/…
Emil



1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

답변:


569

짧은 대답은 Java에서 일반 유형 매개 변수의 런타임 유형을 찾을 수있는 방법이 없다는 것입니다. 자세한 내용 은 Java Tutorial 에서 유형 삭제에 관한 장을 읽는 것이 좋습니다 .

이에 대한 대중적인 해결책 Class은 type 매개 변수를 제네릭 형식의 생성자에 전달하는 것 입니다.

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

73
이 답변은 유효한 솔루션을 제공하지만 런타임에 제네릭 형식을 찾을 수있는 방법이 없다고 말하는 것은 정확하지 않습니다. 유형 소거는 담요 소거보다 훨씬 더 복잡하다는 것이 밝혀졌습니다. 내 대답은 클래스에 제네릭 형식을 얻는 방법을 보여줍니다.
벤 서리

3
@ BenThurley 깔끔한 트릭이지만 볼 수있는 한 일반적인 수퍼 타입이있는 경우에만 작동합니다. 이 예에서는 Foo <T>에서 T 유형을 검색 할 수 없습니다.
Zsolt Török 2016 년

@webjockey 아니요,해서는 안됩니다. typeParameterClass생성자에 기본 할당없이 할당하는 것은 완벽합니다. 두 번째로 설정할 필요가 없습니다.
도우 라스

이것은 가장 먼저 떠오르는 솔루션이지만 때때로 객체를 생성 / 시작할 사람이 아닙니다. 따라서 생성자를 사용할 수 없습니다. 예를 들어 데이터베이스에서 JPA 엔티티를 검색하는 동안.
Paramvir Singh Karwal

234

클래스 경로에 추가 종속성을 추가하지 않고 직접 수행 할 수있는 방법을 찾고있었습니다. 일부 조사 후 나는 것을 발견 하다 만큼 당신이 일반 슈퍼를 가지고 가능. 일반 레이어 수퍼 타입 이있는 DAO 레이어 로 작업 할 때도 괜찮습니다 . 이것이 귀하의 시나리오에 적합하면 IMHO가 가장 가까운 접근법입니다.

내가 본 대부분의 제네릭 유스 케이스에는 List<T>for ArrayList<T>또는 GenericDAO<T>for DAO<T>등 의 일반적인 수퍼 유형이 있습니다 .

순수한 자바 솔루션

Java 런타임에서 일반 유형 액세스 기사 는 순수 Java를 사용하여이를 수행하는 방법을 설명합니다.

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

스프링 솔루션

내 프로젝트는 Spring 을 Spring에는 유형을 찾기위한 편리한 유틸리티 방법이 있으므로 더 좋습니다. 가장보기 흉한 것처럼 보이기 때문에 이것이 최선의 방법입니다. Spring을 사용하지 않았다면 자신 만의 유틸리티 메소드를 작성할 수 있다고 생각합니다.

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

1
resolveTypeArgument 인수의 의미를 명확히하십시오
gstackoverflow

getClass ()는 java.lang.Object의 메소드로 런타임에 특정 오브젝트의 클래스를 리턴합니다. 이것은 유형을 분석하려는 오브젝트입니다. AbstractHibernateDao.class는 제네릭 형식 클래스 계층 구조의 기본 클래스 또는 수퍼 클래스의 이름 일뿐입니다. import 문이 포함되어 있으므로 쉽게 문서를 찾고 확인할 수 있습니다. 이것은 docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley

버전 4.3.6 이상인 스프링 솔루션은 무엇입니까? 스프링 4.3.6에서는 작동하지 않습니다.
Erlan

1
"Pure Java 솔루션"의 링크가 깨졌습니다. 이제 blog.xebia.com/acessing-generic-types-at-run-in-java
Nick Breen

1
@ AlikElzin-kilaka는 스프링 클래스 GenericTypeResolver를 사용하여 생성자에서 초기화됩니다.
Ben Thurley

103

그러나 작은 허점이 있습니다 Foo. 클래스를 추상으로 정의하면 . 즉, 클래스를 다음과 같이 인스턴스화해야합니다.

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

(끝 부분에 이중 괄호가 있습니다.)

이제 T런타임시 유형을 검색 할 수 있습니다 .

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

그러나 이것이 mySuperclass실제로 최종 유형을 정의하는 클래스 정의의 수퍼 클래스 여야합니다.T .

또한 우아하지는 않지만 코드 를 선호하는지 new Foo<MyType>(){}또는 new Foo<MyType>(MyType.class);코드에서 결정 해야합니다.


예를 들면 다음과 같습니다.

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

그때:

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

5
이것은 여기에 가장 좋은 대답입니다! 또한 가치가있는 것은 Google Guice가 클래스 바인딩에 사용하는 전략입니다.TypeLiteral
ATG

14
이 객체 생성 방법을 사용할 때마다 새로운 익명 클래스가 생성됩니다. 즉, 두 객체에서 ab모두 같은 클래스를 확장하지만 동일한 인스턴스 수업을합니다 생성이 방법. a.getClass() != b.getClass()
Martin Serrano

3
이것이 작동하지 않는 시나리오가 있습니다. Foo가 Serializable과 같은 인터페이스를 구현해야하는 경우 클래스 인스턴스화가 아닌 한 익명 클래스는 Serializable이 될 수 없습니다. Foo에서 파생 된 익명 클래스를 만드는 직렬화 가능한 팩토리 클래스를 만들어서 해결 방법을 시도했지만 어떤 이유로 getActualTypeArguments가 실제 클래스 대신 일반 유형을 반환합니다. 예를 들면 다음과 같습니다. (new FooFactory <MyType> ()). createFoo ()
Lior Chaga

38

표준 접근법 / 해결 방법 / 솔루션은 class다음과 같이 생성자에 객체를 추가하는 것입니다.

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

1
그러나 실제로 사용할 때 @autowired를 사용할 수없는 것처럼 보였습니까?
Alfred Huang

@AlfredHuang 해결 방법은 자동 배선에 의존하지 않는 클래스를위한 빈을 만드는 것입니다.
Calebj

20

일반적인 추상 슈퍼 클래스가 있다고 상상해보십시오.

public abstract class Foo<? extends T> {}

그런 다음 T를 확장하는 일반 막대로 Foo를 확장하는 두 번째 클래스가 있습니다.

public class Second extends Foo<Bar> {}

(bert bruynooghe 답변에서) Bar.class을 선택하고 인스턴스를 Type사용하여 추론 하여 Foo 클래스 에서 클래스 를 얻을 수 있습니다 Class.

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

이 작업이 이상적이지는 않으므로 여러 계산을 피하기 위해 계산 된 값을 캐시하는 것이 좋습니다. 일반적인 용도 중 하나는 일반적인 DAO 구현입니다.

최종 구현 :

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

반환 된 값은 다른 함수의 Foo 클래스 또는 Bar 클래스에서 호출 될 때 Bar.class입니다.


1
toString().split(" ")[1]문제는 피하십시오"class "
IgniteCoders

16

작동하는 솔루션은 다음과 같습니다.

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

참고 : 수퍼 클래스로만 사용할 수 있습니다

  1. 형식화 된 클래스 ( Child extends Generic<Integer>) 로 확장해야합니다.

또는

  1. 익명 구현으로 작성해야합니다 ( new Generic<Integer>() {};)

3
getTypeName은 String을 호출하므로 .getActualTypeArguments () [0] .toString ()으로 대체 될 수 있습니다.
Yaroslav Kovbas


9

다른 사람들이 제안한 클래스보다 더 나은 경로는 클래스로 수행 한 작업을 수행 할 수있는 객체를 전달하는 것입니다 (예 : 새 인스턴스 만들기).

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 :이 팩토리가 어떻게 매개 변수화 된 foo를 반환해야하는지 모르겠습니다. 이것으로부터 Foo <T>를 얻는 방법을 설명해 주시겠습니까? 이것은 매개 변수화되지 않은 Foo 만 제공하는 것 같습니다. make10의 T가 단순히 Foo가 아닙니까?
ib84

@ ib84 코드를 수정했습니다. 원래 답변을 쓸 때 Foo가 매개 변수화 된 것을 놓친 것 같습니다.
Ricky Clarkson

9

나는 추상 제네릭 클래스 에서이 문제가있었습니다. 이 특별한 경우 솔루션이 더 간단합니다.

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

나중에 파생 클래스에서 :

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}

예, 그러나이 클래스를 확장하는 동안 수행해야 할 최소한의 것을 남기고 싶습니다. 확인 droidpl의 대답
Paramvir 싱 Karwal

5

나는 최근에 사용한이 문제에 대한 (못하지만 효과적인) 해결책을 가지고 있습니다.

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 __()
    {
    }
}


3

나는 그것을 할 수있는 일반적이고 간단한 방법을 찾았습니다. 내 클래스에서 클래스 정의에서의 위치에 따라 제네릭 형식을 반환하는 메서드를 만들었습니다. 다음과 같은 클래스 정의를 가정 해 봅시다.

public class MyClass<A, B, C> {

}

이제 유형을 유지하기위한 몇 가지 속성을 만들어 보겠습니다.

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)

    } 

그런 다음 일반 정의의 색인을 기반으로 유형을 리턴하는 일반 메소드를 작성할 수 있습니다.

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

마지막으로 생성자에서 메소드를 호출하고 각 유형에 대한 색인을 보냅니다. 완전한 코드는 다음과 같아야합니다.

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

2

다른 답변에서 설명 했듯이이 ParameterizedType접근법 을 사용 하려면 클래스를 확장해야하지만 클래스를 확장하는 완전히 새로운 클래스를 만드는 추가 작업처럼 보입니다 ...

따라서 클래스를 추상화하면 클래스를 확장하여 하위 클래스 요구 사항을 충족시킵니다. (lombok의 @Getter 사용).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

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

이제 새 클래스를 정의하지 않고 확장하십시오. (마지막으로 {}에 유의하십시오 ... 확장되었지만 아무 것도 덮어 쓰지 마십시오. 원치 않는 한).

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

2

제네릭 클래스가 있으므로 다음과 같은 변수가 있다고 가정합니다.

private T t;

(이 변수는 생성자에서 값을 가져와야합니다)

이 경우 간단히 다음 방법을 만들 수 있습니다.

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

그것이 도움이되기를 바랍니다!


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

1

generics를 사용하는 클래스 / 인터페이스를 확장하거나 구현하는 경우 기존 클래스 / 인터페이스를 전혀 수정하지 않고 부모 클래스 / 인터페이스의 일반 유형을 얻을 수 있습니다.

세 가지 가능성이있을 수 있습니다.

사례 1 클래스가 제네릭을 사용하는 클래스를 확장하는 경우

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

사례 2 클래스가 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){}
}

사례 3 인터페이스가 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> {
}

1

꽤 간단합니다. 같은 수업 내에서 필요한 경우 :

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

0

실제로 클래스에 T 유형의 필드가 있다고 가정합니다. T 유형의 필드가 없으면 일반 유형의 요점은 무엇입니까? 따라서 단순히 해당 필드에서 instanceof를 수행 할 수 있습니다.

내 경우에는

List <T> 항목;
내 수업에서 클래스 유형이 "지역"인지 확인합니다.

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

물론 이것은 가능한 총 수업 수가 제한되어있는 경우에만 작동합니다.


4
items.isEmpty ()가 true 인 경우 어떻게해야합니까?
chaotic3quilibrium

0

이 질문은 오래되었지만 이제는 google 사용하는 것이 가장 좋습니다 Gson.

custom을 얻는 예제 viewModel입니다.

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

제네릭 형식 클래스

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

0

Generics를 사용하는 메소드에 T.class를 전달하고 싶었습니다.

readFile 메소드는 fullPath 를 사용하여 fileName으로 지정된 .csv 파일을 읽습니다. 내용이 다른 csv 파일이있을 수 있으므로 적절한 객체를 얻을 수 있도록 모델 파일 클래스를 전달해야합니다. 이것은 CSV 파일을 읽고 있기 때문에 일반적인 방법으로하고 싶었습니다. 어떤 이유로 든 위의 해결책 중 어느 것도 나를 위해 일하지 않았습니다. Class<? extends T> type작동 하려면 사용해야 합니다. CSV 파일을 구문 분석하기 위해 opencsv 라이브러리를 사용합니다.

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

이것이 readFile 메소드가 호출되는 방식입니다

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);

-4

이 해결 방법을 사용하고 있습니다.

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

MyClass myClassInstance = MyClass.class.newInstance();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.