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

Posted

技术标签:

【中文标题】为啥将 Collections.emptySet() 与泛型一起使用在赋值中而不是作为方法参数?【英文标题】:Why does using Collections.emptySet() with generics work in assignment but not as a method parameter?为什么将 Collections.emptySet() 与泛型一起使用在赋值中而不是作为方法参数? 【发布时间】:2011-03-04 23:02:57 【问题描述】:

所以,我有一个带有这样构造函数的类:

public FilterList(Set<Integer> labels) 
    ...

我想用一个空集构造一个新的FilterList 对象。按照 Joshua Bloch 在他的《Effective Java》一书中的建议,我不想为空集创建一个新对象。我将改用Collections.emptySet()

FilterList emptyList = new FilterList(Collections.emptySet());

这给了我一个错误,抱怨java.util.Set&lt;java.lang.Object&gt; 不是java.util.Set&lt;java.lang.Integer&gt;。好的,这个怎么样:

FilterList emptyList = new FilterList((Set<Integer>)Collections.emptySet());

这也给了我一个错误!好的,这个怎么样:

Set<Integer> empty = Collections.emptySet();
FilterList emptyList = new FilterList(empty);

嘿,它有效!但为什么?毕竟,Java 没有类型推断,这就是为什么如果你使用Set&lt;Integer&gt; foo = new TreeSet() 而不是Set&lt;Integer&gt; foo = new TreeSet&lt;Integer&gt;(),你会得到一个未经检查的转换警告。但是Set&lt;Integer&gt; empty = Collections.emptySet(); 工作时甚至没有警告。这是为什么呢?

【问题讨论】:

以下所有答案都是正确的,但我不明白的是:为什么要使用 emptyList 初始化集合而不是调用不带参数的默认构造函数?我知道的每个集合都有一个带有现有集合的构造函数和一个空的构造函数。 有趣的是,以下代码可以编译:FilterList emptyList = new FilterList((Set&lt;Integer&gt;)(Set&lt;? extends Object&gt;)Collections.emptySet()); 另一个非常有趣的例子:Set&lt;Integer&gt; emptySet = (Set&lt;Integer&gt;)Collections.emptySet(); 无法编译。 【参考方案1】:

你想这样做:

FilterList emptyList = new FilterList(java.util.Collections.<Integer>emptySet());

这告诉emptySet 方法它的泛型参数应该显式地使用Integer 而不是默认的Object。是的,语法是完全时髦且不直观的。 :)

【讨论】:

我不知道那个语法,谢谢!但我仍然想知道为什么在变量赋值中不需要这种语法。 查看 Andrzej Doyle 的回答。我相信这是一个很好的解释。 那里不需要new。事实上,我认为它不会与它一起编译。【参考方案2】:

简短的回答是 - 这是 Java 通用系统中类型推断的限制。它可以根据具体变量推断泛型类型,但不能根据方法参数推断。

怀疑这是因为方法是根据拥有对象的运行时类动态调度的,所以在编译时(当 all 通用信息被解析时)你可以'实际上并不确定方法参数的类是什么,因此无法推断。变量声明很好,而且是不变的,所以你可以。

其他人可能能够提供更多细节和/或一个不错的链接。 :-)

在任何情况下,您始终可以为泛型调用显式指定类型参数,如下所示:

Collections.<Integer>emptySet();

甚至可以同时使用多个参数,例如

Collections.<String, Boolean>emptyMap(); // Returns a Map<String, Boolean>

在无法进行推理的情况下,这通常看起来比强制转换要干净一些。

【讨论】:

+1 很好地解释了原因。我希望可以在 SO 中将两个答案标记为正确 :) 我知道编译器在推断时不会查看方法调用的上下文,但我不确定为什么。至少对于私有或final 方法,应该可以进行推理。 @Hank: 或 static 方法,它们也在编译时解析。仍然 - 它没有,我想这是重点。 "我怀疑这是因为方法是根据拥有对象的运行时类动态调度的,所以在编译时......你实际上无法确定方法参数的类将是,因此无法推断。”在那种情况下,为什么类型转换的版本不起作用? “我怀疑这是因为方法是根据拥有对象的运行时类动态调度的”。我不确定它是否是同一件事,但是在编译时确定要调用哪个版本的重载方法:ampersand.space/blog/2007/1/12/java-overload【参考方案3】:

试试

FilterList emptyList = new FilterList(Collections.<Integer>emptySet());

如果推理不够好,或者允许您使用子类型,您可以为具有它们的方法强制使用类型参数;例如:

// forces use of ArrayList as parameter instead of the infered List
List<String> l = someObject.<ArrayList<String> methodThatTakesTypeParamForReturnType();

【讨论】:

【参考方案4】:

Java 确实有类型推断,只是非常有限。如果您有兴趣确切了解它的工作原理以及它的局限性,那么这是一本非常好的读物:

http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#Type%2BArgument%2BInference

【讨论】:

以上是关于为啥将 Collections.emptySet() 与泛型一起使用在赋值中而不是作为方法参数?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在链表中查找循环时将指针增加 2,为啥不增加 3、4、5?

为啥注册用户未在 Firebase 中进行身份验证?为啥用户不能将产品添加到数据库 Firebase?

为啥我们将字符串数组作为参数传递给 main() 方法,为啥不传递任何集合类型或包装类型或原始类型?

为啥将“viewWithTag”与“dequeueReusableCellWithIdentifier”一起使用?

为啥将 ngStyle 指令声明到 [] 中?

为啥我不能将属性转换为嵌套元素?