Java 中外观奇怪的显式类型参数声明语法的用法

2022-09-04 04:23:22

我最近遇到了在调用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开发人员的生活更轻松?


答案 1

“关闭编译器”为什么不是“必要或有用的”?我发现编译我的代码既必要有用。

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

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


答案 2

我发现至少有一种编译器正确推断类型的情况,并且仍然需要它:当您想要将结果用作更通用的类型时。采用此方法,它基本上从零个或多个对象创建一个: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));

它确实编译,如预期的那样。