用于Java中看起来很奇怪的显式类型参数声明语法

我最近发现了一种奇怪的语法,用于在调用Java方法时显式声明generics类型。 例如:

Collections.emptyList(); 

返回一个空的List 。 然而,这似乎很愚蠢,因为 emptyList()的实现只是未经检查的类型转换(List) EMPTY_LIST ,这样所有结果都具有相同类型的擦除(并且是相同的对象。)此外,这种类型通常不需要显式类型声明,因为编译器通常可以推断出类型:

 List empty = Collections.emptyList(); 

在做了一些挖掘之后,我发现了另外两次你想要使用这种语法,并且它们都是由于使用了Guava库并且显然试图在一行上放置太多语句

  1. 装饰集合,例如使用同步包装器,并且编译器无法推断类型。 如果取出类型声明,则以下内容不起作用: cannot convert from Set to Set

     Set set = Collections.synchronizedSet(Sets.newHashSet()); 
  2. 当编译器尝试制作过于具体的类型参数时,获取较少的特定类型参数。 例如,如果没有类型声明,则以下语句也会抱怨: cannot convert from Map to Map

     Map toJson = ImmutableMap.of("foo", "bar"); 

我觉得具有讽刺意味的是,在第一种情况下,推断类型参数过于笼统,在第二种情况下它们过于具体,但我认为这只是Java中generics系统的一个工件。

然而,除了在番石榴团队发明的这些奇怪的用例之外,这种语言结构本身似乎是可以避免的。 此外,我很清楚, 在上面的两个例子中编译器都有一种推断类型参数的方法,开发人员只是选择不这样做。 是否有在Java编程中使用此构造所必需或​​有用的示例,还是仅仅为了使编译器更简单/ JDK开发人员的生活更容易存在?

如何“关闭编译器”不是“必要或有用的”? 我发现编译代码既有必要又有用。

有些时候无法推断出正确的类型,正如您已经发现的那样。 在这种情况下,有必要明确指定类型参数 。 编译器的一些例子不够智能:

  • 为什么javac不能推断用作参数的函数的generics类型参数?
  • generics类型推断失败了吗?

如果您真的想深入了解类型推断的复杂性,它将以Java语言规范开始和结束。 您将要关注JLS§15.12.2.7。 根据实际参数和§15.12.2.8推断 类型参数 。 推断未解决的类型参数 。

我发现至少有一种情况,编译器正确地推断出类型,并且仍然需要:当你想将结果用作更通用的类型时。 采用这种方法,它基本上从零个或多个T对象创建List

 public static  List listOf(T... items) { ArrayList list = new ArrayList(); for (T item : items) list.add(item); return list; } 

这个想法是你可以像这样使用它:

 List numbers = ListUtils.listOf(1, 2, 3); 

现在,假设您有一个可以接收List

 public static void a(List objs) { ... } 

并且您想要提供通过listOf()方法构建的列表:

 a(ListUtils.listOf(1, 2, 3)); 

这不会编译,因为方法参数类型是List ,并且提供的参数是List 。 在这种情况下,我们可以将调用更改为:

 a(ListUtils.listOf(1, 2, 3)); 

按预期编译。

Java类型推断非常弱。 只有在方法的结果定义变量时, emptyList()必要在诸如emptyList()类的generics方法中包含显式类型。 如果你试图传递一个空列表作为另一个方法的参数(例1),这是我每天出现的情况(我还没有使用Guava),编译器就完全放弃了类型推断。 我没有看到如何将空列表声明为本地的一次性变量是“在一行上放置太多语句”,就像你所说的那样; 空列表是一个非常简单的子表达式,除了Java的悲惨类型推断使它变得复杂。 与Scala比较,Scala将在3种不同情况下进行推理。