方法链中的通用类型参数推断
Generic type parameters inference in 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");
我知道从 Java8 开始,编译器推理机制得到了改进,即它从上下文中推断方法的泛型类型,在本例中是一个赋值。
上下文也可以是方法调用:
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()
方法,其中 return 是 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();
所以我的问题不是如何 solve/workaround 这个,而是为什么我在链接泛型方法时需要显式提供泛型类型参数。或者,换句话说,为什么编译器不能推断方法链中的泛型类型参数,尤其是当方法链位于赋值的右侧时?如果可能的话,这会破坏其他东西吗(我的意思是,与泛型类型系统有关)?
编辑:
有一个 question asking the same, however the only answer it has doesn't clearly explain why the compiler doesn't infer the generic type parameters in a method chain. All it has is a reference to a small paragraph in the JSR-000335 Lambda Expressions for the JavaTM Programming Language Final Release for Evaluation(规范 D 部分):
There has been some interest in allowing inference to "chain": in a().b(), passing type information from the invocation of b to the invocation of a. This adds another dimension to the complexity of the inference algorithm, as partial information has to pass in both directions; it only works when the erasure of the return type of a() is fixed for all instantiations (e.g. List). This feature would not fit very well into the poly expression model, since the target type cannot be easily derived; but perhaps with additional enhancements it could be added in the future.
所以我想我可以将我原来的问题改写如下:
- 这些额外的增强功能是什么?
- 为什么双向传递部分信息会使推理算法更复杂?
- 为什么这只有在对所有实例化(例如 List)修复了 return 类型的 a() 的擦除时才有效?其实,一个方法的return类型的擦除对于所有实例化都是固定的是什么意思呢?
- 为什么此功能不能很好地适合多边形表达式模型?其实,什么是多表达模型呢?为什么在这种情况下不能轻松导出目标类型?
如果这确实是一条评论,请告诉我,我会在单独的评论中将其分解(它可能无法放在一个单独的评论中)。
第一个 poly expression
是在不同上下文中可以有不同类型的类型。你在 contextA
中声明了一些东西,它有 typeA
; contextB
中的一个,它有 typeB
.
您通过 ImmutableMap.of("a", "b")
或 ImmutableMap.of(1,2)
创建地图就是这样的事情。更准确地说 Chapter 15.2 in the JLS says that this is actually a Class Instance Creation Expression 多边形表达式。
到目前为止我们已经确定 A generic class instance creation is a poly expression
。因此,实例创建 可以有 不同的类型,具体取决于使用它的上下文(这显然会发生)。
现在在你的例子中 ImmutableMap.builder
事情并不难推断(如果你可以链接 builder
和 of
例如)Builder 声明如下:
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
传递才能进行推理,据我所知这很难做到(除了非常复杂之外)。
现在的工作方式是,根据我所见(你的例子似乎证明了这一点),每个多边形表达式一次编译一个。
阅读 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");
我知道从 Java8 开始,编译器推理机制得到了改进,即它从上下文中推断方法的泛型类型,在本例中是一个赋值。
上下文也可以是方法调用:
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()
方法,其中 return 是 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();
所以我的问题不是如何 solve/workaround 这个,而是为什么我在链接泛型方法时需要显式提供泛型类型参数。或者,换句话说,为什么编译器不能推断方法链中的泛型类型参数,尤其是当方法链位于赋值的右侧时?如果可能的话,这会破坏其他东西吗(我的意思是,与泛型类型系统有关)?
编辑:
有一个 question asking the same, however the only answer it has doesn't clearly explain why the compiler doesn't infer the generic type parameters in a method chain. All it has is a reference to a small paragraph in the JSR-000335 Lambda Expressions for the JavaTM Programming Language Final Release for Evaluation(规范 D 部分):
There has been some interest in allowing inference to "chain": in a().b(), passing type information from the invocation of b to the invocation of a. This adds another dimension to the complexity of the inference algorithm, as partial information has to pass in both directions; it only works when the erasure of the return type of a() is fixed for all instantiations (e.g. List). This feature would not fit very well into the poly expression model, since the target type cannot be easily derived; but perhaps with additional enhancements it could be added in the future.
所以我想我可以将我原来的问题改写如下:
- 这些额外的增强功能是什么?
- 为什么双向传递部分信息会使推理算法更复杂?
- 为什么这只有在对所有实例化(例如 List)修复了 return 类型的 a() 的擦除时才有效?其实,一个方法的return类型的擦除对于所有实例化都是固定的是什么意思呢?
- 为什么此功能不能很好地适合多边形表达式模型?其实,什么是多表达模型呢?为什么在这种情况下不能轻松导出目标类型?
如果这确实是一条评论,请告诉我,我会在单独的评论中将其分解(它可能无法放在一个单独的评论中)。
第一个 poly expression
是在不同上下文中可以有不同类型的类型。你在 contextA
中声明了一些东西,它有 typeA
; contextB
中的一个,它有 typeB
.
您通过 ImmutableMap.of("a", "b")
或 ImmutableMap.of(1,2)
创建地图就是这样的事情。更准确地说 Chapter 15.2 in the JLS says that this is actually a Class Instance Creation Expression 多边形表达式。
到目前为止我们已经确定 A generic class instance creation is a poly expression
。因此,实例创建 可以有 不同的类型,具体取决于使用它的上下文(这显然会发生)。
现在在你的例子中 ImmutableMap.builder
事情并不难推断(如果你可以链接 builder
和 of
例如)Builder 声明如下:
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
传递才能进行推理,据我所知这很难做到(除了非常复杂之外)。
现在的工作方式是,根据我所见(你的例子似乎证明了这一点),每个多边形表达式一次编译一个。