为什么这个成员字段的泛型类型信息在java中没有被删除?

fkvaft9z  于 2021-07-06  发布在  Java
关注(0)|答案(2)|浏览(356)
  1. import java.lang.reflect.Field;
  2. import java.lang.reflect.Type;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. public class Foo<T> {
  6. public List<Integer> aGenericList;
  7. public T item;
  8. public Foo() {
  9. aGenericList = new ArrayList<>();
  10. }
  11. public static void main(String[] args) throws NoSuchFieldException {
  12. Foo foo = new Foo<String>();
  13. System.out.println(foo.aGenericList.getClass());
  14. Field testField = Foo.class.getField("aGenericList");
  15. Type genericType1 = testField.getGenericType();
  16. System.out.println(genericType1.getTypeName());
  17. }
  18. }

结果是:

  1. class java.util.ArrayList
  2. java.util.List<java.lang.Integer>

这意味着使用反射方法,可以获得擦除的类型信息。
现在我的问题是:
这种行为是在jls/jvms规范中正式定义的(如果是,在哪里?),还是由实现该语言的不同供应商定义的?
是否可以将反射方法应用于局部变量 foo 得到像这样的东西 Foo<java.lang.String> ?

zyfwsgd6

zyfwsgd61#

类型 List<Integer> 是编译时常量。当这种情况发生时,编译器将烘焙类型。

qncylg1j

qncylg1j2#

你的问题

1. 这种行为是在jls/jvms规范中正式定义的(如果是,在哪里?),还是由实现该语言的不同供应商定义的?

java语言规范似乎没有具体描述反射:
因此,本说明书不详细描述反射。
但是,将反射的全部行为留给api(即javadoc)来记录。
但是,java虚拟机规范确实解释了编译器必须发出一般信息:

4.7.9. 签名属性

这个 Signature 属性是属性表中的固定长度属性 ClassFile , field_info ,或 method_info 结构(§4.1, §4.5, §4.6). 一 Signature 属性记录签名(§4.7.9.1)对于类、接口、构造函数、方法或字段,其在java编程语言中的声明使用类型变量或参数化类型。有关这些构造的详细信息,请参阅java语言规范JavaSE15版。
[...]

4.7.9.1. 签名

签名对用java编程语言编写的声明进行编码,这些声明使用java虚拟机类型系统之外的类型。它们支持反射和调试,以及仅在 class 文件可用。
java编译器必须为其声明使用类型变量或参数化类型的任何类、接口、构造函数、方法或字段发出签名。具体来说,java编译器必须发出:
任何类或接口声明的类签名,该类或接口声明要么是泛型的,要么具有作为超类或超接口的参数化类型,要么两者兼有。
任何方法或构造函数声明的方法签名,该方法或构造函数声明要么是泛型的,要么以类型变量或参数化类型作为返回类型或形式参数类型,要么在 throws 条款,或其任何组合。
如果 throws 方法或构造函数声明的子句不涉及类型变量,则编译器可以将该声明视为没有类型变量 throws 用于发出方法签名的子句。
任何类型使用类型变量或参数化类型的字段、形式参数或局部变量声明的字段签名。
[...]

2. 是否可以将反射方法应用于局部变量foo以获得foo<java.lang.string>之类的内容?

不,因为局部变量是不可反射访问的。至少不是直接用java语言。但假设他们是。您有:

  1. Foo foo = new Foo<String>();

反射的是左手边。这是一个原始类型,所以你只知道 fooFoo . 您无法判断右侧创建的示例是用参数化的 String .

一些澄清(希望如此)

当我们说“泛型在运行时被删除”时,我们并不是指在这个上下文中。静态定义的可反射访问的构造类型(如字段)保存在字节码中。例如,以下内容:

  1. import java.lang.reflect.Field;
  2. import java.lang.reflect.ParameterizedType;
  3. import java.lang.reflect.Type;
  4. import java.lang.reflect.WildcardType;
  5. import java.util.ArrayList;
  6. import java.util.List;
  7. public class Main {
  8. private static List<? extends Number> list = new ArrayList<Integer>();
  9. public static void main(String[] args) throws Exception {
  10. Field field = Main.class.getDeclaredField("list");
  11. // Due to List being a generic type the returned Type is actually
  12. // an instance of java.lang.reflect.ParameterizedType
  13. Type genericType = field.getGenericType();
  14. System.out.println("Generic Type = " + genericType);
  15. // The raw type can be gotten from the ParameterizedType. Here the
  16. // returned Type will actually be an instance of java.lang.Class
  17. Type rawType = ((ParameterizedType) genericType).getRawType();
  18. System.out.println("Raw Type = " + rawType);
  19. // The ParameterizedType gives us access to the actual type
  20. // arguments declared. Also, since a bounded wildcard was used
  21. // the returned Type is actually an instance of
  22. // java.lang.reflect.WildcardType
  23. Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
  24. System.out.println("Type Argument = " + typeArgument);
  25. // We know in this case that there is a single upper bound. Here
  26. // the returned Type will actually be an instance of java.lang.Class
  27. Type upperBound = ((WildcardType) typeArgument).getUpperBounds()[0];
  28. System.out.println("Upper Bound = " + upperBound);
  29. }
  30. }

将输出:

  1. Generic Type = java.util.List<? extends java.lang.Number>
  2. Raw Type = interface java.util.List
  3. Type Argument = ? extends java.lang.Number
  4. Upper Bound = class java.lang.Number

所有这些信息都在源代码中。请注意,我们正在反射地查看 list 现场。我们不是在看由所述字段引用的示例(即运行时对象)。知道字段的泛型类型与知道字段的名称是 list .
我们不知道的是 ArrayList 参数化为 Integer . 将上述更改为:

  1. import java.lang.reflect.TypeVariable;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class Main {
  5. private static List<? extends Number> list = new ArrayList<Integer>();
  6. public static void main(String[] args) {
  7. Class<?> clazz = list.getClass();
  8. System.out.println("Class = " + clazz);
  9. TypeVariable<?> typeParameter = clazz.getTypeParameters()[0];
  10. System.out.println("Type Parameter = " + typeParameter);
  11. }
  12. }

输出:

  1. Class = class java.util.ArrayList
  2. Type Parameter = E

我们可以看到,我们知道 list 是的示例 java.util.ArrayList . 但从这里我们能确定的是 ArrayList 类是泛型的,只有一个类型参数 E . 我们无法确定 list 字段被分配了 ArrayList 类型参数为 Integer . 换言之 ArrayList 示例本身不知道它声明包含该信息的元素的类型已被删除。
换言之 list 字段的类型在运行时是已知的,但是 ArrayList 示例(即在运行时创建的对象)只知道它是一个 ArrayList .

展开查看全部

相关问题