为啥我可以使用有界通配符作为参数而不是方法中的返回类型? [复制]

Posted

技术标签:

【中文标题】为啥我可以使用有界通配符作为参数而不是方法中的返回类型? [复制]【英文标题】:Why can I use bounded wildcards for parameter and not for return type in a method? [duplicate]为什么我可以使用有界通配符作为参数而不是方法中的返回类型? [复制] 【发布时间】:2021-03-04 00:39:02 【问题描述】:

我找不到对我的问题主题更好的描述,所以我会尝试更好地解释我的问题。我注意到如果我使用有界通配符,我可以将其“绑定”用作参数,但不能用作返回值。这可能听起来令人困惑,所以我将立即粘贴我的代码。

public static void main (String[] args) 
    List<?> wildcardList = new ArrayList<> ();
    List<String> stringList = new ArrayList<> ();
    takeList (wildcardList); //compiles
    takeList (stringList); //compiles
    /////////////////////////////////////
    List<?> wildcard = returnList (); //compiles
    List<String> strings = returnList (); //doesn't compile



static void takeList(List<? extends Object> list)
    //some code


static List<? extends Object> returnList()
    return new ArrayList<> ();

我想知道为什么最后一行代码无法编译。当我明确表示returnList()返回类型。显然是 String IS-A 对象。有人可以帮我解决这个问题吗?

【问题讨论】:

你完全可以,只是没有返回你期望的类型。 我明白了,我的目标问题是为什么当我使用“绑定”时该特定代码行无法编译。意思是 String 在 绑定。 因为返回类型是? extends Object,而不是String。很多东西都扩展了Object,比如NumberJButton? extends Object 的列表也可能是这些列表。 但是 String 扩展了 Object 并且我没有做任何可能有风险的 add() 操作。对不起,我还是不明白。 如果列表类型是List&lt;Number&gt;,那怎么是String列表呢?泛型是不变的,继承是协变的。两者是不同的。没什么好说的,只是语言不允许你做你正在尝试的事情。 【参考方案1】:

让我们关注两行:

// f() returns a List<? extends Object>
List<?>      a = f(); // ok: can never do unexpected things
List<String> b = f(); // error: not 100% safe

对于a 的情况,没有错误,因为List&lt;? extends Object&gt; 正是List&lt;?&gt;

对于b 的情况,Java 编译器* 的问题是,如果允许分配通过,是否会生成任何类型的意外错误(类转换)。让我们看一些例子:

f() 实际上返回一个 List (List&lt;String&gt; is-a List&lt;? extends Object&gt;) -- 没问题 f() 实际上返回一个 List (List&lt;Integer&gt; is-a List&lt;? extends Object&gt;) - 可能会导致问题,因为现在 b 被认为是 List&lt;String&gt;,但 b.add("foo") 会失败。

编译器担心您可能不正确地使用它是有道理的。是的,它可以让您继续检查,确实,您没有调用b.add()——但这会使编译器复杂化,但收益甚微。还有许多其他地方可以通过验证实际上不会发生任何不好的事情来避免编译错误,但是拥有简单的规则(“除非明确抛弃,否则没有风险”)使规则更容易遵循,并且默认为“只允许安全的事情”是 Java 语言的重要组成部分。

比较:

// f() returns a float
float a = f(); // ok
int   b = f(); // error: not 100% safe, unwanted rounding may result!

(*) - 我用广义上的“编译器”来指代语言和编译器。

【讨论】:

令我丧命的是,如果我有&lt;T extends Object&gt; 而不是&lt;? extends Object&gt;,它会起作用。我也在谈论返回值。你能知道其中的区别吗? 如果我在parameters 中同时使用了通配符和类型变量,它在这两种情况下都可以工作。当然 add() 方法是行不通的。 您能否展示一个工作代码示例,其中 f() 返回 &lt;T extends Object&gt; 和 T 与 String 不同的 List&lt;String&gt; b = f(); 编译? 当然。我都在这里:pastebin.pl/view/0a4f0483 您的 pastebin 示例涉及到一个不同的问题 - 我建议您提出一个新问题,比较 returns List&lt;? extends Object&gt;returns List&lt;T&gt;, with &lt;T extends Object&gt; 的情况。简短的版本是您不能在第二种情况下放置 return new ArrayList&lt;String&gt;()

以上是关于为啥我可以使用有界通配符作为参数而不是方法中的返回类型? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥将 Collections.emptySet() 与泛型一起使用在赋值中而不是作为方法参数?

为啥 TensorFlow 返回 [[nan nan]] 而不是 CSV 文件中的概率?

java遗珠之泛型通配符

java遗珠之泛型通配符

为啥关键字参数必须作为带有符号键的哈希传递,而不是 Ruby 中的字符串键?

为啥使用通配符将参数数量减半会因“参数列表太长”而失败? [复制]