14

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

Collections.<String>emptyList();

返回一个空List<String>。然而,这似乎很愚蠢,因为它的实现<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 编程中使用此构造的必要或有用的示例,或者它的存在仅仅是为了使编译器更简单/JDK 开发人员的生活更轻松?

4

3 回答 3

5

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

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

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

于 2013-03-26T04:55:39.830 回答
1

我发现至少一种编译器正确推断类型的情况,并且仍然需要它:当您想将结果用作更通用的类型时。采用这种方法,它基本上List<T>从零个或多个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<Object>并且提供的参数是List<Integer>. 在这种情况下,我们可以将调用更改为:

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

正如预期的那样编译。

于 2014-08-27T19:38:08.203 回答
0

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

于 2013-03-26T05:01:30.243 回答