方法链

时间:2017-05-09 19:07:41

标签: java generics methods java-8 method-chaining

阅读this question后,我开始考虑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>。然后,使用参数ImmutableMap.Builder.put"a"调用"b"方法,最后调用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();

所以我的问题不是如何解决/解决这个问题,而是为什么我需要在链接泛型方法时显式提供泛型类型参数。或者,换句话说,为什么编译器可以在方法链中推断泛型类型参数,尤其是当方法链位于赋值的右侧时?如果有可能,这会破坏其他东西(我的意思是,与泛型类型系统有关)?

修改

有一个question asking the same,但它唯一的答案并没有清楚地解释为什么编译器没有推断出方法链中的泛型类型参数。它只是对JSR-000335 Lambda Expressions for the JavaTM Programming Language Final Release for Evaluation(规范D部分)中的一个小段的引用:

  

有一些兴趣允许在a()。b()中推断&#34; chain&#34;:,将类型信息从b的调用传递给a的调用。这为推理算法的复杂性增加了另一个维度,因为部分信息必须在两个方向上传递;只有当a()的返回类型的擦除对于所有实例化(例如List)都是固定的时,它才有效。此特征不适合多聚表达模型,因为目标类型不能轻易导出;但也许还有其他增强功能,可以在将来添加。

所以我想我可以将我原来的问题改写如下:

  • 这些额外的改进是什么?
  • 如何在两个方向上传递部分信息会使推理算法更复杂?
  • 为什么只有当a()的返回类型的擦除对所有实例化(例如List)都是固定的时才能使用?实际上,对于所有实例化来说,删除方法的返回类型是什么意思?
  • 为什么这个功能不能很好地适应多聚表达模型?实际上,什么是多元表达模型?为什么在这种情况下不能轻易导出目标类型?

1 个答案:

答案 0 :(得分:3)

如果这应该是一个评论,请告诉我,我会在单独的评论中将其分解(它可能不适合单一评论)。

首先poly expression是在不同的上下文中可以有不同类型的contextA。您在typeA中声明了contextB; typeB中的一个,ImmutableMap.of("a", "b")

您通过ImmutableMap.of(1,2) A generic class instance creation is a poly expression创建地图的方式就是这样。更准确地说Chapter 15.2 in the JLS说这实际上是Class Instance Creation Expression多义表达式。

到目前为止,我们确定了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

注意两者是如何声明相同的泛型类型let's say 10 methods。这可能很容易从一种方法传递到另一种方法。但是考虑一下你的方法(<K,V> of() .... <M extends T, R extends S> build() )每个都声明泛型类型的不同界限的情况,例如:

left to right

等等。你可以链接他们。有关类型的信息必须从right to left{% set residences = [] %} {% set demandesCount = {} %} {% for key in demandes|keys %} {% set name = demandes[key].residence.name %} {% set demandesCount = demandesCount|merge({ (name) : demandesCount[name]|default(0) + 1 }) %} {% if name not in residences %} Résidence : <em style="color:#b94a83;">{{ name }}</em> <br/> {% set residences = residences|merge([(name)]) %} {% endif %} {% endfor %} 传递给推理到工作,据我所知,这将是非常困难的(除了非常复杂)。

现在它的工作方式是每个poly表达式从我看到的一次编译一个(并且你的例子似乎证明了这一点)。