简体   繁体   English

为什么在 Java 的类型转换期间允许在右侧使用(已擦除)泛型类型?

[英]Why is usage of the (erased) generic type in right-hand side allowed during type casting in Java?

This is disallowed, which I believe is due to type erasure.这是不允许的,我认为这是由于类型擦除。 ( T is erased and cannot be accessed in runtime to read its class like T.class ). T被擦除并且无法在运行时访问以读取其 class ,如T.class )。

class Cup<T> {
    private T t;
    public T[] getArray(int size) {
        Class<T> cls = T.class;
        return (T[]) Array.newInstance(cls, size);
    }
}

But how come this compiles?但是这是怎么编译的呢? I thought the type conversion of (T)val would happen at runtime, thus the JVM would know nothing about if T is String or anything else.我认为(T)val的类型转换会在运行时发生,因此 JVM 不会知道TString还是其他任何东西。 So javac should prevent the program from being compiled at all.所以 javac 应该完全阻止程序被编译。 would error out.会出错。

public class Cup<T> {
    public T get() {
        Integer val = 1;
        T result = (T)val;
        return result;
    }

    public static void main(String[] args) {
        Cup<String> cup = new Cup<>();
        System.out.println(cup.get());
    }
}

Did I miss anything on compile time vs runtime?我错过了编译时间与运行时的任何内容吗? Why this design choice?为什么选择这个设计? What is the intuition behind that?这背后的直觉是什么?

This is called an unchecked narrowing reference conversion by the Java Lanaguage Spec .这被Java 语言规范称为未经检查的缩小参考转换 Allowing these casts is important for building some generic code.允许这些转换对于构建一些通用代码很重要。 For example, they are used widely in implementing ArrayList , where the elements are stored in an Object[] and so static type checking is lost as items are added to and retrieved from the underlying array.例如,它们广泛用于实现ArrayList ,其中元素存储在Object[]中,因此 static 类型检查丢失,因为项目被添加到底层数组并从底层数组中检索。 However, because the compiler cannot perform the check statically nor at runtime, use of these conversions results in an unchecked warning :但是,由于编译器无法静态执行检查,也无法在运行时执行检查,因此使用这些转换会导致未经检查的警告

If a narrowing reference conversion is unchecked, then the Java Virtual Machine will not be able to fully validate its type correctness, possibly leading to heap pollution.如果未选中缩小引用转换,则 Java 虚拟机将无法完全验证其类型正确性,可能导致堆污染。 To flag this to the programmer, an unchecked narrowing reference conversion causes a compile-time unchecked warning, unless suppressed by @SuppressWarnings为了向程序员标记这一点,未经检查的缩小引用转换会导致编译时未经检查的警告,除非被 @SuppressWarnings 抑制

These conversions exist solely in source code;这些转换仅存在于源代码中; they are not compiled into the bytecode and have no effect on the runtime.它们不会编译成字节码,对运行时没有影响。 They are required solely to force the programmer to declare that they know (better than the compiler) that the conversion is safe.它们只需要强制程序员声明他们知道(比编译器更好)转换是安全的。

You are correct that "the JVM would know nothing about if T is String or anything else", and as a result nothing will happen at runtime .您是正确的,“JVM 对TString还是其他任何东西一无所知”,因此在运行时不会发生任何事情

Normally, the runtime evaluation of a (reference type) cast would involve checking whether the object that you are casting actually is of the type that you are casting to.通常,(参考类型)转换的运行时评估将涉及检查实际转换的 object 是否属于您要转换的类型。 For example:例如:

Object o = new Object();
String s = (String)o;

At runtime, a check is performed on o , and it will be found that o is actually of type Object and not String , and as such, a ClassCastException will be thrown.在运行时对o进行检查,会发现o实际上是Object类型而不是String类型,因此会抛出ClassCastException

On the other hand, if you are casting to a type parameter T , the runtime has no idea what T is, and so does not do any checking, hence this is an "unchecked cast", as the warning says.另一方面,如果您要转换为类型参数T ,则运行时不知道T是什么,因此不会进行任何检查,因此这是一个“未经检查的转换”,如警告所述。

So if val isn't actually of type T , no exceptions will be thrown:因此,如果val实际上不是T类型,则不会抛出异常:

Cup<String> c = new Cup<>();
c.get();

Even though I called get in the above code, and the line with the cast is executed, there will be no exceptions, because there is no runtime check.即使我在上面的代码中调用了get ,并且执行了强制转换的行,也不会出现异常,因为没有运行时检查。 The runtime thinks that get returns an Object .运行时认为get返回一个Object It is only when the runtime knows what type to cast, does the cast happen, and the exception get thrown:只有当运行时知道要转换的类型、转换是否发生并抛出异常时:

Cup<String> c = new Cup<>();
String s = c.get(); // this throws an exception

The compiler inserts the cast in the second line like so String s = (String)c.get();编译器像这样在第二行插入String s = (String)c.get();

As you can see, it doesn't really matter that the runtime doesn't know what T is at the line where the cast is, because you don't need the cast there anyway.如您所见,运行时不知道演员表所在行的T是什么并不重要,因为无论如何您都不需要演员表。 Consider the type-erased version of your code:考虑代码的类型擦除版本:

public class Cup {
    public Object get() {
        Integer val = 1;
        Object result = val;
        return result;
    }
    public static void main(String[] args) {
        Cup cup = new Cup();
        System.out.println(cup.get());
    }
}

You'll notice that this is perfectly fine code that will compile!您会注意到这是可以编译的非常好的代码!

(T)val here is mostly here to make the compiler happy, to convince the compiler that val is indeed of type T . (T)val这里主要是为了让编译器开心,让编译器相信val确实是T类型。

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM