【问题标题】:Why does the following cast lead to a compilation error?为什么以下强制转换会导致编译错误?
【发布时间】:2012-02-14 06:13:49
【问题描述】:

我有以下代码:

public static<F, S> void xyz() {
  class Pair<X, Y> {}    
  class Role<F> {}

  Map<?, List<Pair<?, ?>>> map = null;
  Role<F> role = null;

  List<Pair<F, S>> result = (List<Pair<F, S>>) map.get(role);
}

不幸的是,Java 抱怨最后一行的强制转换。为什么会这样?

在我看来,这是违反直觉的。在每个可能存在正确类型对应的地方都应该允许进行强制转换。

【问题讨论】:

  • 预期的返回类型是什么?
  • 你也应该提供方法签名——也许还应该提供类签名(你在哪里指定了 F 和 S?)。
  • 我添加了更完整的示例。

标签: java generics types casting


【解决方案1】:

演员阵容没有任何意义。这不是扩大演员阵容的反面。

类似的可能转换,我们有一个通配符参数:

List<String> ls;
List<?> cast = ls;

在这种情况下,我无法随后将 Integer 添加到 ls/cast 对象。

cast.add(Integer.valueOf(0)); // Not going to work.

如果泛型参数本身是带有通配符的泛型,情况就大不相同了。

List<List<String>> strses;
List<List<?>> cast = strses; // Suppose this actually worked.
List<Integer> ints;
List<?> wildInts = ints;
cast.add(0, wildInts); // Good.
List<String> wasInts = strses.get(0); // Oops!!

【讨论】:

  • 您的示例不包含任何强制转换表达式。我理解为什么这些示例不起作用,请在此处查看另一个问题:stackoverflow.com/questions/9152302/… 但是,我们能够从 Object 显式转换为 String,这在某些情况下可能有一定的意义。 IMO,从 List> 到 List> 的转换也是如此。我们现在可能从程序的其他部分得知这个列表实际上是一个 Pair
  • 我重新提出了我的问题:stackoverflow.com/questions/9274547/…
【解决方案2】:

当一个对象被创建时,它有一个确切的类型。这种类型可能是ArrayList&lt;Pair&lt;F, S&gt;&gt;,它当然是List&lt;Pair&lt;F, S&gt;&gt; 的子类型。然而,正如 Tom 所指出的,这不是 List&lt;Pair&lt;?,?&gt;&gt; 的子类型。所以你的演员表是正确的唯一方法是如果在其他地方有一个不健全的演员表。如果泛型在某个时候被具体化,那么你的演员或不健全的演员都会崩溃。这就是为什么该演员被拒绝的理由。

【讨论】:

    【解决方案3】:

    泛型不是一个全新的类型系统。在内部,这一切都基于强制转换(因为字节码(向后)兼容性)。

    扩展汤姆的答案,让我们做最简单的例子:

    List<? extends Object> genericList; // List<?> is short for List<? extends Object>
    List<String> concreteList = new LinkedList<String>();
    
    genericList = concreteList;
    concreteList = (List<String>) genericList; //(caution: uncheck cast)
    

    所以,这有效,因为 String 扩展了 Object。让我们更进一步,列出列表:

    List<List<?>> listOfGenericLists;
    List<List<String>> listOfStrings = new LinkedList<List<String>>();
    //does not work "only because" String extends Object:
    listOfGenericLists = listOfStrings; //compile error
    

    这与您的示例中的错误相同。直觉是说这应该有效,因为 String 扩展了 Object,因此 List&lt;String&gt; 应该扩展 List&lt;Object&gt;。好吧,事实并非如此。你可能不会对此感到惊讶: 列表> listOfIntegers; listOfIntegers = listOfStrings; //同样编译错误

    让我们看看当我们明确直觉时会发生什么。让我们从列表中实际扩展:

    List<? extends List<?>> genericListOfLists = new LinkedList<List<?>>();
    List<? extends List<String>> genericListOfStringLists = new LinkedList<List<String>>();
    genericListOfLists = listOfStrings;
    genericListOfStringLists = listOfStrings;
    listOfStrings = (List<List<String>>) genericListOfLists; //(caution: unchecked cast)
    listOfStrings = (List<List<String>>) genericListOfStringLists; // works as before (caution: uncheck cast)
    

    啊哈!现在直觉又回到了正轨。对? 其实没有:

    List<? extends List<Integer>> listOfIntegerLists;
    listOfIntegerLists = (List<List<Integer>>) genericListOfLists; //(caution: uncheck cast)
    listOfIntegerLists = (List<List<Integer>>) genericListOfStringLists; //OUCH (no compilation error just an unchecked cast!)
    

    原因是 - 如前所述:从类型的角度来看,所有泛型都是相同的,并且强制转换在类型级别上工作。

    我可以推荐 Maurice Naftalin 和 Philip Wadler (O'Reilly) 的 Java Generics and Collections 了解详情。

    【讨论】:

    【解决方案4】:

    “在每个可能存在正确类型对应的地方都应该允许进行转换。”在这种情况下,不可能有正确的类型对应。

    一个对象永远不能同时是List&lt;A&gt;List&lt;B&gt; 的实例,如果A 和B 是具体的并且不相同,即使A 是B 的子类型或其他什么。例如,您不能拥有一个既是 List&lt;String&gt; 又是 List&lt;Object&gt; 的对象。每个对象都有(概念上)一个特定的类型参数。你必须先了解这部分。

    你的例子是一样的——List&lt;Pair&lt;F, S&gt;&gt;List&lt;Pair&lt;?, ?&gt;&gt;。出于与上面的 String 和 Object 相同的原因,List&lt;Pair&lt;?, ?&gt;&gt; 类型的对象永远不能同时是 List&lt;Pair&lt;F, S&gt;&gt;

    更新:如果有帮助,请提供更多解释。 @Konstantin:如果你写Pair&lt;?, ?&gt;,那么顶层的类型参数?(通配符)是灵活的。 Pair&lt;T1, T2&gt;Pair&lt;?, ?&gt; 兼容。但是,在List&lt;Pair&lt;?, ?&gt;&gt; 中,? 不在顶层。顶层的类型参数是Pair&lt;?, ?&gt;,它不是通配符,因此不灵活。碰巧有? 并不重要。如果你有List&lt;? extends Pair&lt;?, ?&gt;&gt;,那么顶层是一个通配符,并且是灵活的。

    您可能想要的是List&lt;? extends Pair&lt;?, ?&gt;&gt;。也许它会帮助您考虑List&lt;Pair&lt;?, ?&gt;&gt;List&lt;? extends Pair&lt;?, ?&gt;&gt; 之间的区别。 List&lt;Pair&lt;?, ?&gt;&gt; 表示这是一个 List,它的类型参数 完全Pair&lt;?, ?&gt; 类型(没有别的,不是 Pair&lt;T1, T2&gt; 等)。 List&lt;? extends Pair&lt;?, ?&gt;&gt; 表示这是一个 List,它的类型参数是 Pair&lt;?, ?&gt; 的子类型(包括 Pair&lt;T1, T2&gt;)。

    【讨论】:

    • 其实 map.get(role) 编译。这是我为什么代码正确的原因。 ? extends 是一种存在类型。这意味着存在 T1 和 T2,对于 List> 的该实例,它的实际类型是 List>。那么,如果我有信息,我为什么要指定 T1 和 T2 类型呢?
    • 我重新提出了我的问题:stackoverflow.com/questions/9274547/…
    【解决方案5】:

    您想将列表的通用实例转换为列表的另一个通用实例:

    List<Pair<?, ?>> ==>> List<Pair<F, S>>
    

    你不能通过以下方式解决这个问题:

    List<Pair<?, ?>> result = (List<Pair<?, ?>>) map.get(role);
    

    【讨论】:

    • 其实我知道如何解决这个问题。我可以将最后一行更改为 (List>) (List) map.get(role);一切都会好起来的。我想从语言设计者的角度理解为什么下面的代码是不正确的。
    • 这是什么意思:public static void xyz()?因为方法返回类型是 void 所以泛型是什么意思?我不明白。请解释一下。
    • 在 Java 中,可以将泛型参数添加到方法中。泛型没有任何意义,它只是一个例子。
    • 其实我知道在java中可以,但是不明白它的好处?
    • 我重新提出了我的问题:stackoverflow.com/questions/9274547/…
    猜你喜欢
    • 1970-01-01
    • 2011-06-09
    • 1970-01-01
    • 2017-07-30
    • 1970-01-01
    • 2019-08-30
    • 2014-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多