简体   繁体   English

为什么在 Java 中没有删除成员字段的此通用类型信息?

[英]Why is this generic type information for member field not erased in Java?

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

The result is:结果是:

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

which means with the reflection approach, it is possible to get the erased type information.这意味着使用反射方法,可以获得擦除的类型信息。

Now my questions are:现在我的问题是:

  1. Is this behavior formally defined in the JLS/JVMS spec (if it is, where?), or up to different vendors implementing the language?这种行为是在 JLS/JVMS 规范中正式定义的(如果是,在哪里?),还是由实现该语言的不同供应商决定?
  2. Is it possible to apply the reflection approach to the local variable foo to get something like Foo<java.lang.String> ?是否可以将反射方法应用于局部变量foo以获得类似Foo<java.lang.String>的东西?

Your Questions你的问题

1. Is this behavior formally defined in the JLS/JVMS spec (if it is, where?), or up to different vendors implementing the language? 1. 这种行为是在 JLS/JVMS 规范中正式定义的(如果是,在哪里?),还是由实现该语言的不同供应商决定?

The Java Language Specification appears to specifically not describe reflection : Java 语言规范似乎没有明确描述反射

Consequently, this specification does not describe reflection in any detail.因此,本规范没有详细描述反射。

But instead leaves the full behavior of reflection to be documented by the API (ie in the Javadoc).但是,将反射的完整行为留给 API(即在 Javadoc 中)记录。

However, the Java Virtual Machine Specification does explain that generic information must be emitted by a compiler :但是, Java 虚拟机规范确实解释了通用信息必须由编译器发出

4.7.9. 4.7.9。 The Signature Attribute Signature属性

The Signature attribute is a fixed-length attribute in the attributes table of a ClassFile , field_info , or method_info structure (§4.1, §4.5, §4.6). Signature属性是ClassFilefield_infomethod_info结构(§4.1、§4.5、§4.6)的属性表中的固定长度属性。 A Signature attribute records a signature (§4.7.9.1) for a class, interface, constructor, method, or field whose declaration in the Java programming language uses type variables or parameterized types. Signature属性记录 class、接口、构造函数、方法或字段的签名 (§4.7.9.1),其在 Java 编程语言中的声明使用类型变量或参数化类型。 See The Java Language Specification, Java SE 15 Edition for details about these constructs.有关这些结构的详细信息,请参阅Java 语言规范,Java SE 15 版

[...] [...]

4.7.9.1. 4.7.9.1。 Signatures签名

Signatures encode declarations written in the Java programming language that use types outside the type system of the Java Virtual Machine.签名对使用 Java 虚拟机类型系统之外的类型的 Java 编程语言编写的声明进行编码。 They support reflection and debugging, as well as compilation when only class files are available.它们支持反射和调试,以及只有class文件可用时的编译。

A Java compiler must emit a signature for any class, interface, constructor, method, or field whose declaration uses type variables or parameterized types. Java 编译器必须为声明使用类型变量或参数化类型的任何 class、接口、构造函数、方法或字段发出签名。 Specifically, a Java compiler must emit:具体来说,Java 编译器必须发出:

  • A class signature for any class or interface declaration which is either generic, or has a parameterized type as a superclass or superinterface, or both.任何 class 或接口声明的 class 签名或接口声明,它要么是通用的,要么具有作为超类或超接口的参数化类型,或两者兼而有之。

  • A method signature for any method or constructor declaration which is either generic, or has a type variable or parameterized type as the return type or a formal parameter type, or has a type variable in a throws clause, or any combination thereof.任何方法或构造函数声明的方法签名,它要么是泛型的,要么具有类型变量或参数化类型作为返回类型或形式参数类型,或者在throws子句中具有类型变量,或其任意组合。

    If the throws clause of a method or constructor declaration does not involve type variables, then a compiler may treat the declaration as having no throws clause for the purpose of emitting a method signature.如果方法或构造函数声明的throws子句不涉及类型变量,则编译器可以将声明视为没有throws子句以发出方法签名。

  • A field signature for any field, formal parameter, or local variable declaration whose type uses a type variable or a parameterized type.其类型使用类型变量或参数化类型的任何字段、形式参数或局部变量声明的字段签名。

[...] [...]

2. Is it possible to apply the reflection approach to the local variable foo to get something like Foo<java.lang.String> ? 2.是否可以将反射方法应用于局部变量foo以获得类似Foo<java.lang.String>的东西?

No, because local variables are not reflectively accessible.不,因为局部变量不可反射访问。 At least not directly by the Java Language.至少不是直接通过 Java 语言。 But let's say they were.但是让我们说他们是。 You have:你有:

Foo foo = new Foo<String>();

What would be reflected is the left-hand-side.反映的是左侧。 That is a raw type, so all you would know is that the type of foo is Foo .那是一个原始类型,所以你只知道foo的类型是Foo You would not be able to tell that the instance created by the right-hand-side was parameterized with String .您将无法判断右侧创建的实例是用String参数化的。


Some Clarification (Hopefully)一些澄清(希望)

When we say "generics are erased at run-time" we don't mean in this context.当我们说“泛型在运行时被擦除”时,我们并不是指在这种情况下。 The statically defined type of reflectively accessible constructs, such as fields, is saved in the byte-code.静态定义的反射可访问结构类型,例如字段,保存在字节码中。 For example, the following:例如,以下内容:

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

Will output:请问output:

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

All that information is there in the source code.所有这些信息都在源代码中。 Note that we are reflectively looking at the list field .请注意,我们正在反思地查看list字段 We are not looking at an instance (ie a run-time object) referenced by said field.我们不是在查看由所述字段引用的实例(即运行时对象)。 Knowing the generic type of the field is really no different than knowing that the field's name is list .知道该字段的泛型类型实际上与知道该字段的名称是list没有什么不同。

What we don't know is that the ArrayList was parameterized with Integer .我们不知道的是ArrayList是用Integer参数化的。 Changing the above to:将上述更改为:

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

Outputs:输出:

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

We can see we know that the instance referenced by list is an instance of java.util.ArrayList .我们可以看到我们知道list引用的实例是java.util.ArrayList的实例。 But from there all we can determine is that the ArrayList class is generic and has a single type parameter E .但是从那里我们可以确定的是ArrayList class 是通用的并且具有单个类型参数E We have no way of determining that the list field was assigned an ArrayList with a type argument of Integer .我们无法确定list字段是否分配了ArrayList类型参数为Integer In other words, the ArrayList instance itself does not know what type of elements it's declared to contain—that information has been erased.换句话说, ArrayList实例本身不知道它声明包含什么类型的元素——该信息已被删除。

To put it another way, the list field's type is known at run-time but the ArrayList instance (ie the object created at run-time ) only knows it's an ArrayList .换句话说, list字段的类型在运行时是已知的,但ArrayList实例(即在运行时创建的 object )只知道它是ArrayList

The type of List<Integer> is a compile time constant. List<Integer>的类型是编译时间常数。 When this happens, the compiler bakes in the type.发生这种情况时,编译器会写入类型。

No, you can't find the erased type information, still (unless via a trick if you want to get that Foo<java.lang.String> ).不,您仍然找不到已擦除的类型信息(除非您想获得该Foo<java.lang.String> 的技巧)。 I don't know why, but this seems much easier to answer.我不知道为什么,但这似乎更容易回答。 All you have to do is read the documentation of getGenericType :您所要做的就是阅读getGenericType的文档:

a Type object that represents the declared type for the field represented by this Field object.一个类型 object 表示此字段 object 表示的字段的声明类型

Not the actual type , but the declared type .不是实际的类型,而是声明的类型

If you de-compile the code ( javap -v -p -c ), you will see two important fields under aGenericList :如果您反编译代码( 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 and descriptor . Signaturedescriptor The second one is what it is used at call sites , the first one makes sure that generics are actually used correctly by the compiler.第二个是在调用站点使用的,第一个确保编译器实际上正确使用了 generics。 Let's say such an example:让我们说这样一个例子:

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

If you decompile get , you will see (among other things):如果你反编译get ,你会看到(除其他外):

4: invokeinterface #19,  2 // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
9: checkcast     #23      // class java/lang/String

how does the compiler know to insert that checkcast ?编译器如何知道插入该checkcast Because the full generic information is retained in the Signature field.因为完整的通用信息保留在Signature字段中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Java通用成员类型在子类中删除了? - Java Generic member type erased in subclass? 为什么在运行时Java中没有删除所有类型信息? - Why are not all type information erased in Java at runtime? 为什么超类通用类型不被擦除/铸造 - Why super classes generic type not erased/casted 为什么在 Java 的类型转换期间允许在右侧使用(已擦除)泛型类型? - Why is usage of the (erased) generic type in right-hand side allowed during type casting in Java? 为什么超类字段的泛型类型不会被擦除到子类型的具体绑定中? - Why does the generic type of a superclass field not get erased to the concrete bound in a subtype? 当Java 8中的方法参数未经检查转换时,为什么会删除返回类型的泛型? - Why is generic of a return type erased when there is an unchecked conversion of a method parameter in Java 8? Java中字段类型实现的接口的通用信息 - Generic information about interface implemented by field type in Java Java 8-向期望通用类型的方法提供原始类型参数会导致其返回类型被擦除 - Java 8 - Providing raw type argument to a method expecting generic type causes its return type to be erased Java泛型,类型擦除和泛型成员的类型 - Java generics, type erasure and type of a generic member 泛型类中擦除了所有HashMap类型值? - All HashMap type values erased in generic class?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM