代码之家  ›  专栏  ›  技术社区  ›  matt

为什么在使用泛型参数时可以进行强制转换,而在指定了参数后则不能

  •  5
  • matt  · 技术社区  · 4 月前

    我不明白为什么这段代码会失败。

    List<String> strings = new ArrayList<>();
    List<Object> objs = (List<Object>)strings;
    

    出现错误:

    |  Error:  
    |  incompatible types: java.util.List<java.lang.String> cannot be converted to java.util.List<java.lang.Object>  
    |      List<Object> objs = (List<Object>)strings;  
    |                                        ^-----^
    

    但以下代码可以工作。

    static <T, S extends T> List<T> castIt(List<S> list){
        return (List<T>)list;
    }
    

    我预计这会因为同样的原因而无法编译,但事实并非如此。我甚至可以将它与相同的类一起使用。

    public static void main(String[] args){
        List<String> strings = new ArrayList<>();
        List<Object> objs = castIt(strings);
    }
    
    2 回复  |  直到 4 月前
        1
  •  7
  •   Sweeper    4 月前

    我假设你理解为什么 List<String> cannot be converted to List<Object> .

    列表<字符串> 列表<对象> 可证明不同 编译器可以清楚地看到这些是不同的类型。

    另一方面, List<T> List<S> 没有可证明的区别。编译器不知道确切的类型是什么 T S 基于它们的约束。 T S 很可能是同一类型,在这种情况下,这种转换将在运行时成功。

    这不是 只是 因为 T S 是类型参数。这是的界限 T S 这使得它们无法被证明是不同的。如果界限是,

    <T extends String, S extends Number>
    

    那么转换是无效的,因为 T S 基于这些约束,不可能是同一类型。

    最后,引用Java语言规范中的一些相关内容。 §5.1.6.1 (强调我的):

    从引用类型S到 如果以下所有条件都为真,则引用类型T:

    • [...]

    • 如果存在一个参数化类型X,它是T的超类型,以及一个参数主义类型Y,它是S的超类型 X和Y的擦除是相同的,那么X和Y是不可证明的 与众不同( §4.5 ).

      使用以下类型 java.util 以包为例,不存在从 ArrayList<String> ArrayList<Object> ,反之亦然,因为类型参数 String Object 可证明是不同的。出于同样的原因,没有 缩小参考转换存在于 数组列表<字符串> 列表<对象> 反之亦然。 拒绝可证明的不同 types是一个简单的静态门,用于防止“愚蠢”地缩小引用范围 转换。

    • [...]

    “可证明不同”的定义在 §4.5 .

    如果满足以下任一条件,则两个参数化类型是可证明不同的 以下为真:

    • 它们是不同泛型类型声明的参数化。

    • 他们的任何类型的论点都是可以证明不同的。

    如果满足以下条件之一,则两个类型参数是可证明不同的 true:

    • 两个参数都不是类型变量或通配符,而且这两个参数不是同一类型。

    • 一个类型参数是类型变量或通配符,带有绑定(如果是类型变量)或上限(如果是通配符,使用capture S的转换(§5.1.10)(如有必要);另一种类型的参数 T不是类型变量或通配符;且|S|<|T|nor |T| <:|S |(§4.8、§4.10)。

    • 每个类型参数都是一个类型变量或通配符,其上限(必要时来自捕获转换)为S和T;且|S|< |T|也不是|T|<:|S

    这个 T S 由于第三个要点,你的代码中没有可证明的区别。 T 有一个界限 对象 ,以及 S 已绑定 T .由于 T 是的一个子类型 对象 ,第三点不满足。

        2
  •  1
  •   WJS    4 月前

    此外,您还可以执行以下操作:

    List<? super String> strings = new ArrayList<>();
    strings.add("test");
    @SuppressWarnings("unchecked") // gets rid of warning for cast
    List<Object> objs = (List<Object>) strings;
    

    在这里, List<? super String> 是的一个子类型 List<?> 因此,在这种情况下 ? 相当于 Object 。由于您可以将子类型转换为其父类型,因此现在可以执行上述转换。

    但是,您不能执行以下操作,因为从编译器的角度来看,它可以是任何对象,而不一定是字符串。

    String s = strings.get(0);  // Doesn't work
    

    因此,您必须将其转换回字符串或将其分配给对象。

    String s = (String)strings.get(0);
    Object ob = strings.get(0);
    System.out.println(s);
    System.out.printn(ob);
    

    印刷品

    test
    test
    

    最后,你可以进行编译器没有标记但会导致的强制转换 runtime 类强制转换异常。 下面是一个使用上述列表编译得很好的示例。

    Integer f = (Integer)strings.get(0);