如何解决为什么在 Java 中没有删除成员字段的通用类型信息? 1.这种行为是在 JLS/JVMS 规范中正式定义的如果是,在哪里?,还是由实现该语言的不同供应商决定?4.7.9.1签名2.是否可以将反射方法应用于局部变量 foo 以获得类似 Foo<java.lang.String> 的内容?
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class Foo<T> {
public List<Integer> aGenericList;
public T item;
public Foo() {
aGenericList = new ArrayList<>();
}
public static void main(String[] args) throws NoSuchFieldException {
Foo foo = new Foo<String>();
System.out.println(foo.aGenericList.getClass());
Field testField = Foo.class.getField("aGenericList");
Type genericType1 = testField.getGenericType();
System.out.println(genericType1.getTypeName());
}
}
结果是:
class java.util.ArrayList
java.util.List<java.lang.Integer>
这意味着通过反射方法,可以获得擦除的类型信息。
现在我的问题是:
- 这种行为是在 JLS/JVMS 规范中正式定义的(如果是,在哪里?),还是由实现该语言的不同供应商决定?
- 是否可以将反射方法应用于局部变量
foo
以获得类似Foo<java.lang.String>
的内容?
解决方法
您的问题
1.这种行为是在 JLS/JVMS 规范中正式定义的(如果是,在哪里?),还是由实现该语言的不同供应商决定?
Java 语言规范似乎特别not describe reflection:
因此,本规范没有详细描述反射。
而是将反射的完整行为留给 API 记录(即在 Javadoc 中)。
然而,Java 虚拟机规范确实解释了generic information must be emitted by a compiler:
4.7.9。 Signature
属性
Signature
属性是 ClassFile
、field_info
或 method_info
结构的属性表中的固定长度属性(§4.1、§4.5、§4.6 )。 Signature
属性为在 Java 编程语言中声明使用类型变量或参数化类型的类、接口、构造函数、方法或字段记录签名(第 4.7.9.1 节)。有关这些结构的详细信息,请参阅Java 语言规范,Java SE 15 版。
[...]
4.7.9.1。签名
签名对用 Java 编程语言编写的声明进行编码,这些声明使用 Java 虚拟机类型系统之外的类型。它们支持反射和调试,以及只有 class
文件可用时的编译。
Java 编译器必须为其声明使用类型变量或参数化类型的任何类、接口、构造函数、方法或字段发出签名。具体来说,Java 编译器必须发出:
-
任何类或接口声明的类签名,它要么是泛型,要么具有作为超类或超接口的参数化类型,或两者兼有。
-
任何方法或构造函数声明的方法签名,它要么是泛型的,要么具有类型变量或参数化类型作为返回类型或形参类型,或者在
throws
中具有类型变量子句,或它们的任意组合。如果方法或构造函数声明的
throws
子句不涉及类型变量,那么编译器可能会将声明视为没有throws
子句以发出方法签名。> -
类型使用类型变量或参数化类型的任何字段、形式参数或局部变量声明的字段签名。
[...]
2.是否可以将反射方法应用于局部变量 foo
以获得类似 Foo<java.lang.String>
的内容?
不,因为局部变量不能反射访问。至少不是直接通过 Java 语言。但让我们说他们是。你有:
Foo foo = new Foo<String>();
将反映的是左侧。这是一个原始类型,所以您只知道 foo
的类型是 Foo
。您将无法判断右侧创建的实例是使用 String
参数化的。
一些澄清(希望如此)
当我们说“泛型在运行时被删除”时,我们并不是指在这种情况下。静态定义类型的可反射访问结构,例如字段,保存在字节码中。例如,以下内容:
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.List;
public class Main {
private static List<? extends Number> list = new ArrayList<Integer>();
public static void main(String[] args) throws Exception {
Field field = Main.class.getDeclaredField("list");
// Due to List being a generic type the returned Type is actually
// an instance of java.lang.reflect.ParameterizedType
Type genericType = field.getGenericType();
System.out.println("Generic Type = " + genericType);
// The raw type can be gotten from the ParameterizedType. Here the
// returned Type will actually be an instance of java.lang.Class
Type rawType = ((ParameterizedType) genericType).getRawType();
System.out.println("Raw Type = " + rawType);
// The ParameterizedType gives us access to the actual type
// arguments declared. Also,since a bounded wildcard was used
// the returned Type is actually an instance of
// java.lang.reflect.WildcardType
Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
System.out.println("Type Argument = " + typeArgument);
// We know in this case that there is a single upper bound. Here
// the returned Type will actually be an instance of java.lang.Class
Type upperBound = ((WildcardType) typeArgument).getUpperBounds()[0];
System.out.println("Upper Bound = " + upperBound);
}
}
将输出:
Generic Type = java.util.List<? extends java.lang.Number>
Raw Type = interface java.util.List
Type Argument = ? extends java.lang.Number
Upper Bound = class java.lang.Number
所有这些信息都在源代码中。请注意,我们正在反思list
字段。我们不查看由所述字段引用的实例(即运行时对象)。知道字段的通用类型与知道字段的名称是 list
没有什么不同。
我们不知道的是 ArrayList
是用 Integer
参数化的。将以上更改为:
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
public class Main {
private static List<? extends Number> list = new ArrayList<Integer>();
public static void main(String[] args) {
Class<?> clazz = list.getClass();
System.out.println("Class = " + clazz);
TypeVariable<?> typeParameter = clazz.getTypeParameters()[0];
System.out.println("Type Parameter = " + typeParameter);
}
}
输出:
Class = class java.util.ArrayList
Type Parameter = E
我们可以看到我们知道list
引用的实例是java.util.ArrayList
的实例。但是从那里我们可以确定的是 ArrayList
类是泛型的,并且只有一个类型参数 E
。我们无法确定 list
字段被分配了一个 ArrayList
,类型参数为 Integer
。换句话说,ArrayList
实例本身不知道它声明包含什么类型的元素——该信息已被删除。
换句话说,list
字段的类型在运行时是已知的,但 ArrayList
实例(即在运行时创建的对象)只知道它是一个 ArrayList
。
List<Integer>
的类型是编译时常量。发生这种情况时,编译器会烘焙该类型。
不,您仍然无法找到已擦除的类型信息(除非您想获得 Foo<java.lang.String>
,否则 via a trick)。我不知道为什么,但这似乎更容易回答。您所要做的就是阅读 getGenericType
的文档:
一个 Type 对象,表示该 Field 对象表示的字段的声明类型。
不是实际类型,而是声明类型。
如果反编译代码(javap -v -p -c
),会在aGenericList
下看到两个重要的字段:
public java.util.List<java.lang.Integer> aGenericList;
descriptor: Ljava/util/List;
flags: (0x0001) ACC_PUBLIC
Signature: #17 // Ljava/util/List<Ljava/lang/Integer;>;
Signature
和 descriptor
。第二个是调用站点使用的,第一个确保编译器实际正确使用泛型。比如说这样一个例子:
static Map<Integer,String> map = new HashMap<>();
public static void add(Integer x,String y) {
map.put(x,y);
}
public String get(Integer x) {
return map.get(x);
}
如果你反编译 get
,你会看到(除其他外):
4: invokeinterface #19,2 // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
9: checkcast #23 // class java/lang/String
编译器怎么知道插入那个checkcast
?因为完整的通用信息保留在 Signature
字段中。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。