我有一个泛型类 Foo<T>
。在 Foo
的方法中,我想获取 T
类型的类实例,但我无法调用 T.class
。
使用 T.class
绕过它的首选方法是什么?
import com.fasterxml.jackson.core.type.TypeReference;
new TypeReference<T>(){}
new TypeReference<Foo<Bar>>() {}
作为我的反序列化模型阅读器方法的参数来解决它。
简短的回答是,没有办法找出 Java 中泛型类型参数的运行时类型。我建议阅读 Java Tutorial 中有关类型擦除的章节以了解更多详细信息。
一个流行的解决方案是将类型参数的 Class
传递给泛型类型的构造函数,例如
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
}
}
我正在寻找一种方法来自己做到这一点,而无需向类路径添加额外的依赖项。经过一番调查,我发现只要你有一个通用的超类型是可能的。这对我来说没问题,因为我正在使用具有通用图层超类型的 DAO 图层。如果这适合您的情况,那么这是最简洁的方法恕我直言。
我遇到的大多数泛型用例都有某种泛型超类型,例如 List<T>
代表 ArrayList<T>
或 GenericDAO<T>
代表 DAO<T>
等。
纯Java解决方案
文章Accessing generic types at runtime in 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 ";
}
完整代码示例
有些人在评论中苦苦挣扎,所以我写了一个小应用程序来展示这两种方法的实际效果。 https://github.com/benthurley82/generic-type-resolver-test
但是有一个小漏洞:如果您将 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 );
}
}
TypeLiteral
绑定的策略
a
和 b
都将扩展同一个类,但不具有相同的实例类。 a.getClass() != b.getClass()
标准方法/解决方法/解决方案是将 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();
}
}
想象一下,你有一个通用的抽象超类:
public abstract class Foo<? extends T> {}
然后你有第二个类,它使用扩展 T 的通用 Bar 扩展 Foo:
public class Second extends Foo<Bar> {}
您可以通过选择 Type
(来自 bert bruynooghe 答案)并使用 Class
实例推断它来获取 Foo 类中的类 Bar.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。
toString().split(" ")[1]
是问题所在,请避免使用 "class "
这是一个有效的解决方案:
@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 <> ");
}
}
注意:只能用作超类
必须使用类型化类进行扩展(Child extends Generic
或者
必须创建为匿名实现 (new Generic
我在一个抽象的泛型类中遇到了这个问题。在这种特殊情况下,解决方案更简单:
abstract class Foo<T> {
abstract Class<T> getTClass();
//...
}
以及后来的派生类:
class Bar extends Foo<Whatever> {
@Override
Class<T> getTClass() {
return Whatever.class;
}
}
与大多数答案相反,有可能(没有外部库!)
以下是我对这个问题的(丑陋但有效的)解决方案:
import java.lang.reflect.TypeVariable;
public static <T> Class<T> getGenericClass() {
__<T> instance = new __<T>();
TypeVariable<?>[] parameters = instance.getClass().getTypeParameters();
return (Class<T>)parameters[0].getClass();
}
// Generic helper class which (only) provides type information. This avoids
// the usage of a local variable of type T, which would have to be initialized.
private final class __<T> {
private __() { }
}
TypeVariableImpl<T>
而不是 T
本身的类类型
TypeVariableImpl<T>
似乎可以分配给 Class<T>
,不是吗?至少,我没有得到任何编译器错误......我不得不承认,我不是一个 java 人......所以我可能会遗漏一些明显的缺陷/缺点/后果。
比其他人建议的 Class 更好的方法是传入一个对象,该对象可以执行您对 Class 所做的事情,例如,创建一个新实例。
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>());
我假设,因为你有一个泛型类,你会有一个像这样的变量:
private T t;
(这个变量需要在构造函数中取值)
在这种情况下,您可以简单地创建以下方法:
Class<T> getClassOfInstance()
{
return (Class<T>) t.getClass();
}
希望能帮助到你!
这是可能的:
class Foo<T> {
Class<T> clazz = (Class<T>) DAOUtil.getTypeArguments(Foo.class, this.getClass()).get(0);
}
您需要 hibernate-generic-dao/blob/master/dao/src/main/java/com/googlecode/genericdao/dao/DAOUtil.java 中的两个函数。
有关详细说明,请参阅 Reflecting generics。
我找到了一种通用且简单的方法来做到这一点。在我的类中,我创建了一个根据泛型类型在类定义中的位置返回泛型类型的方法。让我们假设一个这样的类定义:
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];
}
}
正如其他答案中所解释的,要使用这种 ParameterizedType
方法,您需要扩展类,但这似乎需要额外的工作来创建一个扩展它的全新类......
因此,使类抽象它会迫使您扩展它,从而满足子类化要求。 (使用龙目岛的@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();
这很简单。如果您需要来自同一个班级:
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() );
}
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 当你的类扩展一个使用泛型的类时
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 当您的类正在实现使用泛型的接口时
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 当您的接口扩展使用泛型的接口时
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> {
}
这个技巧很多人都不知道!其实我今天才发现!它像梦一样工作!只需查看此示例:
public static void main(String[] args) {
Date d=new Date(); //Or anything you want!
printMethods(d);
}
public static <T> void printMethods(T t){
Class<T> clazz= (Class<T>) t.getClass(); // There you go!
for ( Method m : clazz.getMethods()){
System.out.println( m.getName() );
}
}
public static void printMethods(Object t)
,它的作用完全相同。这个答案没有“技巧”,只是一个过时的类型参数。
clazz
上调用 getConstructures()
来执行此操作。现在的问题是,为什么 OP 想要将 T.class
放在他可以做 t.getClass()
的地方?
Class
对象上调用 getConstructors
。 1) 但你的代码没有 2) 但它仍然不能证明使用类型参数是合理的。无论您将参数声明为 T
还是 Object
,t.getClass()
的结果都是 Class<? extends Object>
。没什么区别。您的代码中有一个未经检查的类型转换 (Class<T>)
。这并不比使用 getClass().getConstructor().newInstance()
将结果转换为您想要的任何内容更好。
我已经根据这个问题的两个最有希望的解决方案之一创建了一个示例。
然而,结果并不那么有希望,至少对于我的用例而言。
只有一种方法有效,但是您需要一个包含该方法的超类,并且必须在子类中设置泛型并且不能动态分配(我的用例可悲的是)
import org.junit.jupiter.api.Test;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class GenericTest {
/**
* only this will work!
*/
@Test
void testGetGenericTypeClassFromChildClassWithSpecifiedType() {
TestClassWithSpecifiedType parent = new TestClassWithSpecifiedType();
assertEquals(SomeGenericType.class, parent.getGenericTypeClass());
}
/**
* won't work!
*/
@Test
void testGetGenericTypeClassFromChildClassWithUnspecifiedType() {
TestClassWithUnspecifiedType<SomeGenericType> parent = new TestClassWithUnspecifiedType<>();
assertThrows(IllegalStateException.class, parent::getGenericTypeClass);
}
/**
* won't work
*/
@Test
void testGetGenericTypeClassWithUnspecifiedType() {
SomeGenericTypedClass<SomeGenericType> parent = new SomeGenericTypedClass<>();
assertThrows(IllegalStateException.class, parent::getGenericTypeClass);
}
/**
* won't work
* returns object instead!
*/
@Test
void testGetLoadedClassFromObject() {
Foo<SomeGenericType> foo = new Foo<>();
Class<?> barClass = foo.getBarClass();
assertEquals(SomeGenericType.class, barClass);
}
/**
* A class that has specified the type parameter
*/
public static class TestClassWithSpecifiedType extends AbstractGenericTypedClass<SomeGenericType> {
}
/**
* A class where the type parameter will be specified on demand
*
* @param <T>
*/
public static class TestClassWithUnspecifiedType<T> extends AbstractGenericTypedClass<T> {
}
/**
* An abstract class, because otherwise finding the parameter will not work
*/
@SuppressWarnings("unchecked")
public static abstract class AbstractGenericTypedClass<T> {
@SuppressWarnings("unchecked")
public 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 <> ");
}
}
}
/**
* A typed class without abstract super class
*
* @param <T>
*/
public static class SomeGenericTypedClass<T> {
@SuppressWarnings("unchecked")
public 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 <> ");
}
}
}
/**
* Some generic type - won't work with primitives such as String, Integer, Double!
*/
public static class SomeGenericType {
}
public static class Foo<T> {
// The class:
private final Class<?> barClass;
public Foo() {
try {
// Im giving it [0] cuz Bar is the first TypeParam
Type[] bounds = getClass().getTypeParameters()[0].getBounds();
// Here, we get the class now:
barClass = Class.forName(bounds[0].getTypeName());
} catch (ClassNotFoundException e) {
// will never happen!
throw new Error("Something impossible happened!", e);
}
}
public Class<?> getBarClass() {
return barClass;
}
}
}
我真的不明白为什么这必须如此复杂,但我敢打赌,动态设置参数必须有一些技术限制。
实际上,我想你的类中有一个类型为 T 的字段。如果没有类型为 T 的字段,那么拥有泛型类型的意义何在?因此,您可以简单地在该字段上执行一个 instanceof。
就我而言,我有一个
List<T> items;
if (items.get(0) instanceof Locality) ...
当然,这仅在可能的类总数有限的情况下才有效。
这个问题很老,但现在最好的是使用谷歌Gson
。
获取自定义 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>
}
}
我想将 T.class 传递给使用泛型的方法
readFile 方法使用完整路径读取由 fileName 指定的 .csv 文件。可以有不同内容的 csv 文件,因此我需要传递模型文件类,以便我可以获得适当的对象。由于这是读取 csv 文件,因此我想以通用方式进行操作。出于某种原因或其他原因,上述解决方案都不适合我。我需要使用 Class<? extends T> type
使其工作。我使用 opencsv 库来解析 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;
}
这就是调用 readFile 方法的方式
List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);
我正在为此使用解决方法:
class MyClass extends Foo<T> {
....
}
MyClass myClassInstance = MyClass.class.newInstance();
不定期副业成功案例分享
typeParameterClass
非常好。无需再次设置。