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



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

Collections.<String>emptyList();

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

List<String> empty = Collections.emptyList();

经过进一步的挖掘,我发现还有两次你想使用这种语法,它们都是由于使用了Guava库和显然试图在一行上放太多语句

  1. 装饰集合,例如使用同步包装器,而编译器无法推断类型。如果去掉类型声明,以下内容将不起作用:cannot convert from Set<Object> to Set<String>:

    Set<String> set = Collections.synchronizedSet(Sets.<String>newHashSet());
    
  2. 当编译器试图生成过于特定的类型参数时,会得到不太特定的类型。例如,如果没有类型声明,以下语句也会抱怨:cannot convert from Map<String, String> to Map<String, Object>:

    Map<String, Object> toJson = ImmutableMap.<String, Object>of("foo", "bar");
    

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

然而,这种语言结构本身似乎是可以避免的,除非是在Guava团队发明的这些奇怪的用例中。此外,在我看来,是编译器在上述两个例子中推断类型参数的一种方式,而开发人员只是选择不这样做。在Java编程中使用这种构造是否有必要或有用的例子,或者它的存在只是为了让编译器更简单/JDKK开发人员的生活更轻松?

"关闭编译器"怎么不是"必要的或有用的?"我发现它对编译我的代码来说既是必要的也是有用的

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

  • 为什么可以';javac是否为用作参数的函数推断泛型类型参数
  • 泛型类型推理失败

如果真的想深入研究类型推理的复杂性,那么它从Java语言规范开始和结束。您需要关注JLS§15.12.2.7.基于实际参数和§15.12.2.8.推断未解决的类型参数。

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

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

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

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

现在,假设您有一个可以接收List<Object>:的方法

public static void a(List<Object> objs) {
    ...
}

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

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

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

a(ListUtils.<Object>listOf(1, 2, 3));

它确实像预期的那样编译。

Java类型推理非常弱。唯一一次不需要在像emptyList()这样的泛型方法中包含显式类型是在该方法的结果定义变量时。如果你试图传递一个空列表作为另一个方法的参数(示例1(,这种情况每天都会发生(我还没有使用Guava(,编译器就会完全放弃类型推理。我看不出将空列表声明为本地一次性变量是如何"在一行上放太多语句"的;空列表是一个非常简单的子表达式,只是Java糟糕的类型推断使它变得复杂。与Scala进行比较,后者将在3种不同的情况下进行推理。

最新更新