将变量转换为通用类型变量

11

我理解泛型和强制转换,但我不理解什么是泛型转换。我原以为只能通过继承树向上或向下转换到特定类型,但这证明了我的错误:

ArrayList<?> cislo = (ArrayList<? extends Number>) new ArrayList<Integer>();

这可能不是最好的例子,但希望你能理解我的意思。它是如何工作的?被分配到哪种类型?


1
在运行时根本没有强制转换,因为泛型信息已经丢失(类型擦除)。在编译时,您告诉编译器将ArrayList<Integer>处理为ArrayList<? extends Number>并保持安静(除了警告之外,基本上与将Number变量强制转换为Integer相同 - 您告诉编译器您知道自己在做什么)。对于ArrayList<?>的赋值在任何情况下都可以工作。 - Thomas
有了泛型,根本不需要进行强制类型转换。如果你觉得需要对泛型类型进行强制转换,也许你应该检查一下是否有“干净”的解决方案,只需利用泛型的巨大灵活性即可。如果你有具体的例子,我相信很多人都愿意帮助你。 - martinhh
我有一个具体的例子。我使用的框架允许注释JUnit测试以选择我想要发送结果的存储,例如@ReesmoConfiguration(storage = RestApiStorage.class),在抽象超类Storage中,我找到了工厂方法newInstance(Object configuration){ Class clazz = null; if (Bool.FALSE.equals(Property.ENABLED.get(configuration))) { clazz = DummyStorage.class; } else { clazz = (Class) Property.STORAGE.get(configuration); } if (clazz.isAssignableFrom(DummyStorage.class)) { return new DummyStorage(); }} - Petr Beneš
好的,这基本上是 Class<? extends Storage> clazz = (Class<? extends Storage>) Property.STORAGE.get(configuration); - 如果 get 返回的对象是 Storage 子类型的类对象,则此代码将起作用。如果 get 的返回类型是其超类型(例如 Object 或某个接口),则需要进行强制转换。 - Hulk
1个回答

4

演员阵容不必要。

分配任务

ArrayList<?> list = new ArrayList<Integer>();
ArrayList<? extends Number> list2 = new ArrayList<Integer>();

不需要任何显式转换。

通配符类型比具体类型更“一般”/“不太具体”,上界通配符类型(如? extends Number)比无界通配符类型?)更具体。

有关通配符类型之间关系的更多信息,请参阅Oracle Wildcards and Subtyping教程

规定这一点的JLS相关部分是4.10.2。类和接口类型之间的子类型关系

给定通用类型声明C(n>0),参数化类型C的直接超类型,其中Ti(1≤i≤n)是一种类型,都是以下所有类型:
  • D,其中D是一个通用类型,它是通用类型C的直接超类型,θ是替换[F1:=T1,...,Fn:=Tn]。
  • C,其中Si包含Ti(1≤i≤n)(§4.5.1)。

[...]

这是指4.5.1. Parameterized Types的类型参数
如果根据以下规则的自反和传递闭包,T2表示的类型集合在T1表示的类型集合中是可证明的子集,则称类型参数T1包含另一个类型参数T2,写作T2 <= T1(其中<:表示子类型关系(§4.10)):

  • ? extends T <= ? extends S,如果T <: S

  • ? extends T <= ?

因此,按照这个定义,ArrayList<? extends Number>ArrayList<Integer>的超类型,ArrayList<?>是任何ArrayList<>(原始类型除外)的超类型。

对于超类型的变量赋值不需要进行强制类型转换。


如果您想分配不同类型的内容,则需要进行转换:

Object list = new ArrayList<Integer>();
//...somewhere else - the compiler does not know that list is always an ArrayList, but we tell it that we know what we are doing
List<? extends Number> numbers = (List<? extends Number>) list; //unchecked cast warning, but works

这样转换后,您可以从列表中获取元素并将其视为数字。如果您将不是List<? extends Number>子类型的内容分配给list引用,则转换会在运行时失败。

    Object list = new HashSet<Integer>();
    List<? extends Number> numbers = (List<? extends Number>) list; //unchecked cast warning

在运行时抛出异常

java.lang.ClassCastException: java.util.HashSet无法转换为java.util.List


当泛型类型不匹配时,问题就开始出现:
List<String> stringList = new ArrayList<String>();
stringList.add("hi");
Object list = stringList;
List<? extends Number> numbers = (List<? extends Number>) list; //unchecked cast warning, but (sadly) works

Number n = numbers.get(0); //fails: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

这是因为在运行时,列表类型的擦除匹配。请参见有关擦除的教程

强制类型转换是不必要的。- 如我所说可能不是最好的例子。我发现jls很难读,但您的解释真的很有帮助,谢谢。其他信息可以在此处找到https://dev59.com/THE85IYBdhLWcg3wbTD2 - Petr Beneš

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接