为什么使用带有泛型的Collections.emptySet()可以在赋值中工作但不能用作方法参数?

Kar*_*n L 54 java generics collections type-inference

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

public FilterList(Set<Integer> labels) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

FilterList emptyList = new FilterList(Collections.emptySet());
Run Code Online (Sandbox Code Playgroud)

这给了我一个错误,抱怨java.util.Set<java.lang.Object>不是java.util.Set<java.lang.Integer>.好的,这个怎么样:

FilterList emptyList = new FilterList((Set<Integer>)Collections.emptySet());
Run Code Online (Sandbox Code Playgroud)

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

Set<Integer> empty = Collections.emptySet();
FilterList emptyList = new FilterList(empty);
Run Code Online (Sandbox Code Playgroud)

嘿,它有效!但为什么?毕竟,Java没有类型推断,这就是为什么你,如果你得到一个unchecked转换警告Set<Integer> foo = new TreeSet()代替Set<Integer> foo = new TreeSet<Integer>().但Set<Integer> empty = Collections.emptySet();工作甚至没有警告.这是为什么?

And*_*yle 118

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

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

其他人可能会提供更多细节和/或一个很好的链接.:-)

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

Collections.<Integer>emptySet();
Run Code Online (Sandbox Code Playgroud)

或者甚至一次参数,例如

Collections.<String, Boolean>emptyMap(); // Returns a Map<String, Boolean>
Run Code Online (Sandbox Code Playgroud)

在推理不起作用的情况下,这通常看起来比必须更清洁.

  • 我知道编译器在推断时不会查看上下文的方法调用,但我不确定原因.至少对于私有或"最终"方法,应该可以进行推理. (3认同)
  • +1可以很好地解释原因.我希望有人可以在SO中标记两个正确的答案:) (2认同)
  • @Hank:或`static`方法,它们也在编译时解析.仍然 - 它*不*,我猜这是主要观点. (2认同)

And*_*anu 7

尝试

FilterList emptyList = new FilterList(Collections.<Integer>emptySet());
Run Code Online (Sandbox Code Playgroud)

在推理不够好的情况下,您可以强制使用包含它们的方法的type参数,或者允许您使用子类型; 例如:

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


jdm*_*hal 6

你想这样做:

FilterList emptyList = new FilterList(java.util.Collections.<Integer>emptySet());
Run Code Online (Sandbox Code Playgroud)

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


kas*_*rjj 5

Java确实有一个类型推断,它只是非常有限.如果您有兴趣确切知道它是如何工作的以及它的局限性,那么这是一个非常好的读物:

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