方法链中的泛型类型参数推断

12
阅读完这个问题之后,我开始思考Java 8中的通用方法。具体而言,当方法被链接时,泛型类型参数会发生什么。
对于这个问题,我将使用Guava的ImmutableMap中的一些通用方法,但我的问题更普遍,适用于所有链接的通用方法。
考虑ImmutableMap.of通用方法,它具有以下签名:
public static <K, V> ImmutableMap<K, V> of(K k1, V v1)

如果我们使用这种通用方法声明一个Map,编译器会正确推断出通用类型:
Map<String, String> map = ImmutableMap.of("a", "b");

我知道从Java 8开始,编译器的推断机制已经得到了改进,即它可以从上下文中推断方法的泛型类型,这种情况下通常是一个赋值操作。
上下文也可以是一个方法调用:
void someMethod(Map<String, String> map) { 
    // do something with map
}

someMethod(ImmutableMap.of("a", "b"));

在这种情况下,ImmutableMap.of的泛型类型是从someMethod的参数的泛型类型中推断出来的,即Map<String, String> map
但是当我尝试使用ImmutableMap.builder()并链接方法来构建我的映射时,我会得到一个编译错误:
Map<String, String> map = ImmutableMap.builder()
    .put("a", "b")
    .build(); // error here: does not compile

错误是:
Error:(...) java: incompatible types: 
ImmutableMap<Object, Object> cannot be converted to Map<String, String>

为了清晰起见,我已经从错误消息中删除了包名。

我理解这个错误以及它发生的原因。链中的第一个方法是ImmutableMap.builder(),编译器没有上下文来推断类型参数,因此它回退到<Object,Object>。然后,使用参数"a""b"调用ImmutableMap.Builder.put方法,最后调用ImmutableMap.Builder.build()方法,返回一个ImmutableMap<Object,Object>。这就是为什么我收到不兼容类型的错误:当我尝试将这个ImmutableMap<Object,Object>实例分配给我的Map<String,String> map变量时,编译器会抱怨。

我甚至知道如何解决这个错误:我可以将该方法链拆成两行,这样编译器就可以推断出泛型类型参数:

ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
Map<String, String> map = builder.put("a", "b").build();

或者我可以明确提供泛型类型参数:

Map<String, String> map = ImmutableMap.<String, String>builder()
    .put("a", "b")
    .build();

所以我的问题不是如何解决/绕过这个问题,而是为什么在链接泛型方法时需要显式提供通用类型参数。换句话说,为什么编译器不能推断出方法链中的通用类型参数,特别是当方法链位于赋值的右侧时?如果可能的话,这会破坏其他东西吗(我指与通用类型系统相关的东西)?
编辑:
有一个问同样的问题,然而它唯一的答案没有清楚地解释为什么编译器不推断方法链中的通用类型参数。它只有一个对JavaTM编程语言最终版的Lambda表达式进行评估的JSR-000335 Lambda Expressions for the JavaTM Programming Language Final Release for Evaluation(规范部分D)的小段引用。
有人对允许推理“链式”(inference to "chain")表示了兴趣:在a().b()中,将b的调用中的类型信息传递给a的调用。这将增加推理算法的复杂度,因为部分信息必须双向传递;仅当a()的返回类型的擦除对于所有实例化都固定时才有效(例如List)。这个特性不太适合于poly表达式模型,因为目标类型不能很容易地推导出来;但也许在未来可以通过额外的增强来添加它。
所以我认为我可以重新表述我的原始问题如下:
  • 这些额外的增强会是什么?
  • 为什么在双向传递部分信息会使推理算法更加复杂?
  • 为什么只有在a()的返回类型的擦除对于所有实例化都固定时才有效(例如List)?事实上,方法的返回类型的擦除对于所有实例化都固定意味着什么?
  • 为什么这个特性不太适合于poly表达式模型?实际上,什么是poly表达式模型?为什么在这种情况下目标类型不能很容易地推导出来?

2
目前看起来这个功能不可用。请参考:https://dev59.com/qmAf5IYBdhLWcg3wUBNc?rq=1 - Ishnark
2
@Ishnark 在多个问题中搜索,但没有找到那一个,谢谢! - fps
3
这里是JLS类型推断章节,如果您想弄清楚编译器为什么不按照您的意愿执行,以及可能需要修复什么,请参考该章节。 - Jeffrey Bosboom
1
@JeffreyBosboom 谢谢,我想是吧…… 我不想修复编译器,只是好奇而已。 - fps
1
不相关话题:非常感谢! - ΦXocę 웃 Пepeúpa ツ
显示剩余3条评论
1个回答

3
如果这应该是一个注释,请告诉我,我会将其分成单独的注释(它可能无法适应一个注释)。
首先,多态表达式是一种在不同上下文中可以具有不同类型的表达式。在 contextA 中声明的内容具有 typeA;在 contextB 中声明的内容具有 typeB
您通过 ImmutableMap.of("a", "b")ImmutableMap.of(1,2) 创建映射的操作就属于这种情况。更准确地说,JLS第15.2章 表示这实际上是一个 类实例创建表达式 多态表达式。
到目前为止,我们已经确定“泛型类实例创建是一个多态表达式”。因此,该实例创建“可以”根据使用它的上下文具有不同的类型(显然会发生这种情况)。
现在,在您的示例中,使用“ImmutableMap.builder”并不难推断出事情(例如,如果您可以链接“builder”和“of”)。构建器声明如下:
   public static <K, V> Builder<K, V> builder() {
      return new Builder<K, V>();
   }

ImmutableMap.of 就像这样:

   public static <K, V> ImmutableMap<K, V> of() {
      return ImmutableBiMap.of();
   }

注意两者都声明了相同的泛型类型 K,V。这可能很容易从一个方法传递到另一个方法。但考虑一下当您的方法(假设有10个方法)每个方法声明不同的泛型类型边界时,会发生什么情况,例如:
<K,V> of()
....
<M extends T, R extends S> build()

等等,你可以将它们链接起来。类型信息必须从左到右和从右到左传递才能进行推理,并且据我所知,这很难做到(除了非常复杂之外)。

现在,从我看到的情况来看,每个多项式表达式都是一个接一个地编译的(而您的示例似乎证明了这一点)。


1
谢谢,尤金。实际上,我提出了很多问题,不能指望一次性得到所有答案。你的回答很好,帮助我理解了我所问的问题。 - fps

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接