java 通用类型转换

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31960987/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 19:25:10  来源:igfitidea点击:

Generic Type cast

javagenericscasting

提问by gregseth

I have the following class (simplified but still a working example):

我有以下课程(简化但仍然是一个工作示例):

class Test<T> {
    List<T> l = new ArrayList<>();

    public Test() {
    }

    public void add(Object o) {
        l.add((T)o);
    }
}

And the test code:

和测试代码:

Test<Double> t = new Test<>();
t.add(1);
t.add(1.2);
t.add(-5.6e-2);
t.add("hello");

Everything is working fine, and that's not what I was expecting. Shouldn't the addmethod throw a ClassCastException? If I add a getmethod that's more or less the same thing:

一切正常,这不是我所期望的。该add方法不应该抛出一个ClassCastException吗?如果我添加一个get或多或少相同的方法:

    public T get(int i) {
        return l.get(i);
    }
.../...
t.get(1);             // OK.
t.get(3);             // OK (?)
Double d = t.get(3);  // throws ClassCastException

Why is it only on variable assignment that the exception is thrown? How can I enforce type consistency if the (T)cast doesn't work?

为什么只有在变量赋值时才会抛出异常?如果强制转换(T)不起作用,我如何强制类型一致性?

回答by dasblinkenlight

Shouldn't the add method throw a ClassCastException?

add 方法不应该抛出一个ClassCastException吗?

No, it shouldn't (although I wish it did). Long story short, Java implementation of generics discards type information after compiling your code, so List<T>is allowed to take any Object, and the cast inside your addmethod is not checked.

不,它不应该(虽然我希望它这样做)。长话短说,泛型的 Java 实现在编译您的代码后会丢弃类型信息,因此List<T>允许使用 any Object,并且add不会检查您的方法中的强制转换。

Why is it only on variable assignment that the exception is thrown?

为什么只有在变量赋值时才会抛出异常?

Because the cast to Doublethere is inserted by the compiler. Java compiler knows that the return type of getis T, which is Double, so it inserts a cast to match the type of the variable d, to which the result is being assigned.

因为到Double那里的演员表是由编译器插入的。Java 编译器知道getis的返回类型T,即 is Double,因此它插入一个强制转换以匹配d要分配结果的变量的类型。

Here is how you can implement a generic-safe cast:

以下是实现泛型安全转换的方法:

class Test<T> {
    private final Class<T> cl;
    List<T> l = new ArrayList<>();

    public Test(Class<T> c) {
        cl = c;
    }

    public void add(Object o) {
        l.add(cl.cast(o));
    }
}

Now the cast is performed by a Class<T>object, so you will get a ClassCastExceptionon an attempt to insert an object of an incorrect type.

现在转换是由Class<T>对象执行的,因此您将ClassCastException尝试插入类型不正确的对象。

回答by Tagir Valeev

As an alternative solution you can use Collections.checkedList:

作为替代解决方案,您可以使用Collections.checkedList

class Test<T> {
    List<T> l;

    public Test(Class<T> c) {
        l = Collections.checkedList(new ArrayList<T>(), c);
    }

    public void add(Object o) {
        l.add((T) o);
    }
}

This way you will get the following exception:

这样你会得到以下异常:

Exception in thread "main" java.lang.ClassCastException: Attempt to insert 
  class java.lang.Integer element into collection with element type class java.lang.Double
    at java.util.Collections$CheckedCollection.typeCheck(Collections.java:3037)
    at java.util.Collections$CheckedCollection.add(Collections.java:3080)
    at Test.add(Test.java:13)

回答by Purag

For the completeness of this resource, here's the difference in compiled bytecode between a cast to a generic:

为了该资源的完整性,以下是转换为泛型之间编译字节码的差异:

public void add(java.lang.Object);
  Code:
     0: aload_0
     1: getfield      #4                  // Field l:Ljava/util/List;
     4: aload_1
     5: invokeinterface #7,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    10: pop
    11: return

And an explicit cast to a Doublewith no generics:

和一个Double没有泛型的显式转换:

public void add(java.lang.Object);
  Code:
     0: aload_0
     1: getfield      #4                  // Field l:Ljava/util/List;
     4: aload_1
     5: checkcast     #7                  // class java/lang/Double
     8: invokeinterface #8,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    13: pop
    14: return

You can see that the version with generics doesn't perform the checkcastinstruction at all (thanks to type erasure, so you shouldn't expect an exception when giving it data with an unmatched class. It's unfortunate that this isn't more strictly enforced, but this makes sense as generics are for making stricter compile-timetype checks, and aren't much help at runtime due to type erasure.

您可以看到具有泛型的版本根本不执行checkcast指令(由于类型擦除,因此在向其提供具有不匹配类的数据时不应期望异常。不幸的是,这并没有得到更严格的执行,但这是有道理的,因为泛型用于进行更严格的编译时类型检查,并且由于类型擦除而在运行时没有太大帮助。

Java will check the types of function arguments to see if there is a type match, or if a type promotion can be performed. In your case, Stringis the type of the argument, and that can be promoted to Object, which is the extent of the compile-time type checks that ensure that function call works.

Java 将检查函数参数的类型以查看是否存在类型匹配,或者是否可以执行类型提升。在您的情况下,String是参数的类型,可以提升为Object,这是确保函数调用有效的编译时类型检查的范围。

There are a few options, and dasblinkenlight's solution is probably the most elegant. (You may not be able to change the method signature, say, if you are overriding an inherited addmethod, or plan on passing down the addmethod, etc).

有几个选项,dasblinkenlight 的解决方案可能是最优雅的。(您可能无法更改方法签名,例如,如果您要覆盖继承的add方法,或计划传递该add方法等)。

Another option that may help is using a boundedtype parameter instead of an unbounded one. Unbounded type parameters are completely lost after compilation due to type erasure, but using a bounded type parameter will replace instances of the generic type with that/those it must extend.

另一个可能有帮助的选项是使用有类型参数而不是无界参数。由于类型擦除,无界类型参数在编译后完全丢失,但使用有界类型参数将用它必须扩展的实例替换泛型类型的实例。

class Test<T extends Number> {

Of course, Tis not truly generic at this point, but using this class definition will enforce types at runtime since the cast willbe checked against the Numbersuperclass. Here's the bytecode to prove it:

当然,此时T并不是真正的通用,但是使用这个类定义将在运行时强制类型,因为根据Number超类检查强制转换。这是证明它的字节码:

public void add(java.lang.Object);
  Code:
     0: aload_0
     1: getfield      #4                  // Field l:Ljava/util/List;
     4: aload_1
     5: checkcast     #7                  // class java/lang/Number
     8: invokeinterface #8,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    13: pop
    14: return

This class definition generates the desired ClassCastExceptionwhen trying to add the string.

此类定义ClassCastException在尝试添加字符串时生成所需的。