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

2024-01-09

看完之后这个问题 https://stackoverflow.com/questions/43869399/initialize-an-immutablemap-with-explicit-types,我开始考虑泛型方法在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, ie. 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" and "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();

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

EDIT:

有一个问同样的问题 https://stackoverflow.com/q/24794924/1876620,但是它的唯一答案并没有清楚地解释为什么编译器不推断方法链中的泛型类型参数。它所包含的只是对其中一小段的引用JSR-000335 JavaTM 编程语言最终版本的 Lambda 表达式用于评估 https://jcp.org/aboutJava/communityprocess/final/jsr335/index.html(规范 D 部分):

人们对允许“链”推理感兴趣:在 a().b() 中,将类型信息从 b 的调用传递到 a 的调用。这又增加了推理算法的复杂性,因为部分信息必须在两个方向上传递;仅当所有实例化(例如 List)的 a() 返回类型的擦除都是固定的时,它才起作用。此功能不太适合多表达式模型,因为目标类型无法轻松导出;但也许通过其他增强功能,将来可以添加它。

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

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

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

First poly expression是一种在不同的上下文中可以有不同类型的类型。你声明了一些东西contextA it has typeA;一进contextB它有typeB.

您正在通过以下方式创建地图ImmutableMap.of("a", "b") or ImmutableMap.of(1,2)就是这样的事情。更准确地说JLS 第 15.2 章 https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.2说这实际上是一个类实例创建表达式 https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.9多表达。

到目前为止我们已经确定A generic class instance creation is a poly expression。这样实例创建可以有根据使用环境的不同类型(这显然会发生)。

现在在你的例子中ImmutableMap.builder事情并不那么难以推断(如果你可以链接builder and of例如)Builder 的声明如下:

   public static <K, V> Builder<K, V> builder() {
      return new Builder<K, V>();
   }

and 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为了使推理有效,据我所知,这将非常困难(除了非常复杂之外)。

现在,它的工作方式是,根据我所看到的,每个多表达式一次编译一个(并且您的示例似乎证明了这一点)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

方法链中的泛型类型参数推断 的相关文章

随机推荐