解析泛型类型
获取字段泛型类型
**java.lang.reflect.Field#getGenericType**
:- 作用:返回字段的泛型类型。
- 返回类型:
Type
。 - 如果字段是一个泛型类型,这个方法将返回一个表示这个泛型类型的
Type
对象,比如ParameterizedType
,TypeVariable
等等。如果字段不是泛型类型,这个方法将返回字段的具体类型,即Class
对象。 - 示例:
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
public class MyClass<K, V> {
private K key;
private V value;
private List<String> list;
public static void main(String[] args) throws Exception {
Field field = MyClass.class.getDeclaredField("list");
Type fieldType = field.getGenericType();
System.out.println(fieldType + ": class " + fieldType.getClass());
if (fieldType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
System.out.println("ActualType: "+parameterizedType.getActualTypeArguments()[0]);
}
}
}
输出结果
java.util.List<java.lang.String>: class class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
ActualType: class java.lang.String
获取对象泛型类型
**java.lang.Class#getTypeParameters**
:- 作用:返回类或接口的类型参数。
- 返回类型:
TypeVariable<?>[]
。 - 如果类或接口没有定义类型参数,这个方法将返回一个空数组。如果类或接口有类型参数,这个方法将返回这些类型参数的数组。
- 示例:
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
public class MyClass<K, V> {
private K key;
private V value;
private List<String> list;
public static void main(String[] args) throws Exception {
TypeVariable<?>[] typeParameters = MyClass.class.getTypeParameters();
for (TypeVariable<?> typeParameter : typeParameters) {
System.out.println(typeParameter.getName() + ": class" + typeParameter.getClass());
System.out.println("Bounds:");
for (Type bound : typeParameter.getBounds()) {
System.out.println(bound);
}
}
}
}
输出结果,此处的范型被擦除了,后续会讲解具体的解决方案
K: classclass sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Bounds:
class java.lang.Object
V: classclass sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Bounds:
class java.lang.Object
总结
Field#getGenericType
是针对类的字段,返回字段的泛型类型或具体类型。Class#getTypeParameters
是针对类或接口,返回定义在类或接口上的类型参数。
如果你想获取一个类中字段的实际类型,Field#getGenericType
是合适的方法。如果你想获取类本身定义的类型参数,Class#getTypeParameters
是正确的方法。
ParameterizedType与TypeVariable区别
ParameterizedType
和 TypeVariable
是 Java 反射 API 中的两个接口,用于表示不同类型的泛型类型信息。它们之间的区别在于它们表示的泛型类型的不同方面。
ParameterizedType
ParameterizedType
表示的是一个参数化的类型,即:使用具体类型参数化的泛型类型。它包含了泛型类型的完整信息,包括原始类型和实际类型参数。例如,对于 List<String>
,ParameterizedType
会表示 List<String>
这个具体的类型。
主要方法:
Type[] getActualTypeArguments()
:返回实际的类型参数数组。Type getRawType()
:返回原始类型(即未参数化的类型,例如List
)。Type getOwnerType()
:返回拥有者类型,如果类型是一个内部类,这个方法返回其外部类;否则返回null
。
示例:
import java.lang.reflect.*;
public class ParameterizedTypeExample {
public static void main(String[] args) throws NoSuchFieldException {
Field field = MyClass.class.getDeclaredField("list");
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
System.out.println("Raw type: " + parameterizedType.getRawType());
System.out.println("Actual type arguments:");
for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) {
System.out.println(actualTypeArgument);
}
}
}
static class MyClass {
List<String> list;
}
}
输出结果
Raw type: interface java.util.List
Actual type arguments:
class java.lang.String
TypeVariable
TypeVariable
表示的是一个类型变量(泛型类型参数),通常在泛型类、接口或方法的定义中使用。例如,对于 T
在 class MyClass<T>
中,TypeVariable
就表示 T
。
主要方法:
Type[] getBounds()
:返回类型变量的上边界(可能有多个),默认为Object
。D getGenericDeclaration()
:返回声明这个类型变量的泛型声明(通常是Class
、Method
或Constructor
)。String getName()
:返回类型变量的名称。
示例:
import java.lang.reflect.*;
public class TypeVariableExample {
public static void main(String[] args) {
TypeVariable<Class<MyClass>>[] typeParameters = MyClass.class.getTypeParameters();
for (TypeVariable<Class<MyClass>> typeParameter : typeParameters) {
System.out.println("Name: " + typeParameter.getName());
System.out.println("Bounds:");
for (Type bound : typeParameter.getBounds()) {
System.out.println(bound);
}
}
}
static class MyClass<T extends Number> {
T value;
}
}
输出结果
Name: T
Bounds:
class java.lang.Number
区别总结
**ParameterizedType**
表示的是已经用实际类型参数化的泛型类型。例如,List<String>
是一个ParameterizedType
。**TypeVariable**
表示的是泛型类型参数本身。例如,T
在class MyClass<T>
中是一个TypeVariable
。
这两个接口的主要区别在于它们表示的类型信息的不同:ParameterizedType
表示完整的参数化类型,而 TypeVariable
表示泛型类型参数。
泛型擦除
有时候为了扩展,需要动态的根据泛型获取对应的处理类,此时涉及到类型擦除时需要强制转换支持
- List<Pipeline<?, MyOutput>>无法直接强转为List<Pipeline<T, MyOutput>>
解决方法
- (List<?>)将List<Pipeline<?, MyOutput>>中的类型擦除,然后再次强转为List<Pipeline<T, MyOutput>>
- 遍历列表将元素强转为目标类型之后添加到列表
private static final Map<Object, List<Pipeline<?, MyOutput>>> PIPELINE = Maps.newHashMap();
@SuppressWarnings("unchecked")
public <T> List<Pipeline<T, MyOutput>> get(T input) {
return (List<Pipeline<T, MyOutput>>) (List<?>) PIPELINE.get(input);
}
@SuppressWarnings("unchecked")
public <T> List<Pipeline<T, MyOutput>> getCanBuyPipeLine(T input) {
List<Pipeline<T, MyOutput>> result = Lists.newArrayList();
for (Pipeline<?, MyOutput> pipeline : PIPELINE.get(input)) {
result.add((Pipeline<T, MyOutput>) pipeline);
}
return result;
}
第一种方式
GenericTypeDemo<Integer> demo = new GenericTypeDemo<>();
Type t = new TypeToken<T>(demo.getClass()) {}.getType();
- 创建实例:
GenericTypeDemo<Integer> demo = new GenericTypeDemo<>();
创建了一个 GenericTypeDemo<Integer>
的实例。
- 使用 TypeToken 获取类型:
Type t = new TypeToken<T>(demo.getClass()) {}.getType();
这段代码试图通过 TypeToken
获取 demo
的泛型类型。这里的 TypeToken<T>
依赖于 demo.getClass()
,但是 demo.getClass()
返回的是 GenericTypeDemo
的运行时类信息,其中的泛型类型已经被擦除。因此,TypeToken<T>
无法捕获到实际的泛型类型 Integer
。
第二种方式
import com.google.common.reflect.TypeToken;
public class GenericTypeDemo<T> {
private final TypeToken<T> typeToken;
public GenericTypeDemo() {
this.typeToken = new TypeToken<T>(getClass()) {};
}
public TypeToken<T> getTypeToken() {
return this.typeToken;
}
public static void main(String[] args) {
GenericTypeDemo<Integer> demo = new GenericTypeDemo<>() {};
System.out.println("泛型类型是: " + demo.getTypeToken().getType());
}
}
- 定义类和构造函数:
public class GenericTypeDemo<T> {
private final TypeToken<T> typeToken;
public GenericTypeDemo() {
this.typeToken = new TypeToken<T>(getClass()) {};
}
public TypeToken<T> getTypeToken() {
return this.typeToken;
}
}
在这个类中,TypeToken<T>
被用来捕获 GenericTypeDemo
的泛型类型。关键在于 new TypeToken<T>(getClass()) {}
,这是在匿名子类中使用 TypeToken
捕获泛型类型信息。
- 使用泛型类型实例化对象:
public static void main(String[] args) {
GenericTypeDemo<Integer> demo = new GenericTypeDemo<>() {};
System.out.println("泛型类型是: " + demo.getTypeToken().getType());
}
在 main
方法中,GenericTypeDemo<Integer>
被实例化为匿名子类,因此 TypeToken
可以捕获到实际的泛型类型 Integer
。当调用 demo.getTypeToken().getType()
时,会正确输出 Integer
的类型。
总结
- 第一种方式:
- 直接通过
demo.getClass()
获取类型,类型信息在运行时已经被擦除,TypeToken<T>
无法获取到实际的泛型类型。 - 最终得到的
Type t
只是一个原始的类型GenericTypeDemo
,没有泛型信息。
- 直接通过
- 第二种方式:
- 在
GenericTypeDemo
类的构造函数中,通过匿名子类和TypeToken<T>
捕获泛型类型信息。 - 实例化
GenericTypeDemo<Integer>
时,匿名子类保留了泛型类型信息,TypeToken<T>
成功捕获到实际的泛型类型Integer
。 - 结果是
demo.getTypeToken().getType()
能够正确输出泛型类型Integer
。
- 在
因此,第二种方式能够正确捕获和输出泛型类型信息,而第一种方式由于泛型类型擦除问题无法正确捕获泛型类型。
spring中获取泛型
抽象类与泛型捕获
假设我们有一个抽象类 AbstractComponent
:
import com.google.common.reflect.TypeToken;
public abstract class AbstractComponent<T> {
private final Class<T> type = (Class<T>) new TypeToken<T>(getClass()) {}.getType();
public AbstractComponent() {
}
public Class<T> getType() {
return this.type;
}
}
关键点
**TypeToken**
** 的使用**:
new TypeToken<T>(getClass()) {}
- `TypeToken` 是 Guava 库中的一个工具类,它用于在运行时捕获和处理泛型类型信息。
- `TypeToken` 的构造函数接收一个 `Class` 对象,这里是 `getClass()`,它在这个上下文中返回 `AbstractComponent` 的运行时类。
- 为什么能够捕获泛型类型:
- 在
AbstractComponent<T>
类中,getClass()
返回的是AbstractComponent
的运行时类,而不是具体的子类。 - 当创建具体子类(例如
ConcreteComponent
)时,AbstractComponent
的构造函数会执行,并且TypeToken
实例会使用当前运行时类(即ConcreteComponent
)来初始化。
- 在
- 类型擦除与泛型捕获:
- 类型擦除:Java 的泛型在编译时会进行类型擦除,这意味着在运行时,泛型类型
T
的具体信息会丢失。但在创建子类时,匿名子类的TypeToken
实例会捕获并保留泛型类型信息。 - 具体子类的影响:当具体子类(例如
ConcreteComponent
)被实例化时,它会调用父类的构造函数。在这种情况下,TypeToken
能够通过匿名子类的方式捕获实际的泛型类型。
- 类型擦除:Java 的泛型在编译时会进行类型擦除,这意味着在运行时,泛型类型
示例
假设我们有以下具体子类:
import org.springframework.stereotype.Component;
@Component
public class ConcreteComponent extends AbstractComponent<String> {
// Concrete implementation
}
执行流程
- 创建子类实例:
ConcreteComponent component = new ConcreteComponent();
- 这会调用 `ConcreteComponent` 的构造函数,而 `ConcreteComponent` 的构造函数会调用 `AbstractComponent` 的构造函数。
**AbstractComponent**
** 的构造函数**:
public AbstractComponent() {
// Initializer of the final field 'type'
}
- 在这个构造函数中,`TypeToken` 实例使用 `getClass()` 返回 `ConcreteComponent` 的运行时类。这是由于在 `AbstractComponent` 的 `type` 字段初始化时,`new TypeToken<T>(getClass()) {}` 被调用。
- 捕获泛型类型:
new TypeToken<T>(getClass()) {}
创建了一个匿名子类,并且这个匿名子类通过反射机制获取到了泛型类型String
。- 因此,
type
字段被正确初始化为String
的Class
对象。
总结
- 运行时类信息:在
AbstractComponent
中,通过getClass()
返回具体子类(如ConcreteComponent
)的运行时类信息。 - 匿名子类和泛型捕获:
TypeToken
的匿名子类能够在创建时捕获并保留泛型类型信息,即使在运行时泛型信息被擦除。 - 实例化时的行为:当具体子类被实例化时,它会调用父类的构造函数,
TypeToken
实例通过具体子类的运行时类信息正确捕获泛型类型。
因此,在你的抽象类 AbstractComponent<T>
中,final
字段 type
能够正确保存具体泛型类型信息,是因为匿名子类机制允许 TypeToken
捕获到实际的泛型类型 T
。
spring能够获取到泛型,是因为spring使用了代理超类,bean对于spring来说是子类化了,所以可以通过反射或者TypeToken方式获取到具体的泛型
Spring 能够获取到泛型类型信息,主要是因为它在处理 bean 时会使用一些技巧来保持泛型的类型信息。具体来说:
- 代理超类:Spring 在创建代理对象时,通常会使用自定义的代理超类,这样可以在运行时通过反射获取到泛型参数的信息。
- TypeToken:Spring 通过使用
TypeToken
(类似于 Google Guava 的TypeToken
类)来保持泛型信息。在类型擦除的情况下,TypeToken
提供了一个获取具体泛型类型的方法。 - 反射:Spring 使用反射机制来获取泛型参数的类型信息。例如,Spring 的
@Autowired
注解可以自动注入泛型类型的 Bean,Spring 在进行注入时会解析泛型参数的信息。 - 自定义 Bean 实现:在一些情况下,Spring 可能会使用自定义的 Bean 实现来保持泛型类型信息。这种方法通常是通过在 Bean 定义中显式声明泛型类型。
总之,Spring 通过这些机制确保了泛型类型信息可以在运行时被正确地获取和使用。
普通对象获取泛型
new Pipeline<>()
创建实例时,Java 的类型擦除特性会导致你无法直接获取泛型的具体类型。类型擦除是指在编译时,Java 泛型信息会被移除,导致在运行时无法直接获取泛型参数的实际类型。
Spring 在处理泛型时,能够获取到泛型类型参数,是因为 Spring 在实例化 bean 的时候,通常使用了代理或其他机制来保存泛型信息。以下是一些解决方法,可以帮助你获取泛型参数的实际类型:
使用 TypeToken
获取泛型类型
你可以使用 TypeToken
(Guava 库中的一个工具类)来获取泛型类型信息,但要注意,TypeToken
需要在 Pipeline
子类中使用才能正确地获得泛型类型。示例如下:
import com.google.common.reflect.TypeToken;
public class Pipeline<I, O> {
private final Class<I> inputClass;
@SuppressWarnings("unchecked")
public Pipeline() {
TypeToken<Pipeline<I, O>> typeToken = new TypeToken<Pipeline<I, O>>(getClass()) {};
this.inputClass = (Class<I>) typeToken.resolveType(typeToken.getType().getActualTypeArguments()[0]).getRawType();
}
public Class<I> getInputClass() {
return inputClass;
}
// Other methods...
}
使用反射获取泛型类型
如果你不想使用 Guava 库,你可以在 Pipeline
的子类中获取泛型类型信息。示例如下:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class Pipeline<I, O> {
private final Class<I> inputClass;
@SuppressWarnings("unchecked")
public Pipeline() {
this.inputClass = (Class<I>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
}
public Class<I> getInputClass() {
return inputClass;
}
// Other methods...
}
确保子类化 Pipeline
确保 Pipeline
是通过子类化的方式实例化的,这样可以保留泛型信息。例如:
public class StringPipeline extends Pipeline<String, Integer> {
// Implementation details...
}
在 StringPipeline
中,你可以通过反射或者 TypeToken
来获取 String
类型信息。
结论
Java 的类型擦除特性使得泛型类型信息在运行时不可用。使用 TypeToken
或者在子类中通过反射获取泛型类型是解决这个问题的常用方法。如果你使用 Spring 或其他框架,它们通常会在创建 bean 时保留泛型信息,这样你可以在运行时访问这些信息。