假设:
import com.google.common.collect.ImmutableMap;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Stream;
public class Testcase
{
public static <T, K, V> MapCollectorBuilder<T, K, V>
toImmutableMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper)
{
return null;
}
public static final class MapCollectorBuilder<T, K, V>
{
public Collector<T, ?, ImmutableMap<K, V>> build()
{
return null;
}
}
public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap2(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper)
{
return null;
}
public void main(String[] args)
{
Function<String, String> keyMapper = i -> i;
Function<String, Integer> valueMapper = Integer::valueOf;
ImmutableMap<String, Integer> map1 = Stream.of("1", "2", "3")
.collect(Testcase.toImmutableMap(keyMapper, valueMapper).build());
ImmutableMap<String, Integer> map2 = Stream.of("1", "2", "3")
.collect(Testcase.toImmutableMap(i -> i, Integer::valueOf).build());
ImmutableMap<String, Integer> map3 = Stream.of("1", "2", "3")
.collect(Testcase.toImmutableMap2(i -> i, Integer::valueOf));
}
}
涉及map1
和map3
的语句编译得很好,但map2
失败了:
Testcase.java:[41,57] incompatible types: cannot infer type-variable(s) T,K,V
(argument mismatch; invalid method reference
no suitable method found for valueOf(java.lang.Object)
method java.lang.Integer.valueOf(java.lang.String) is not applicable
(argument mismatch; java.lang.Object cannot be converted to java.lang.String)
method java.lang.Integer.valueOf(int) is not applicable
(argument mismatch; java.lang.Object cannot be converted to int))
可以通过提供显式类型参数<String, String, Integer>
来解决编译器错误。
toImmutableMap()
和MapCollectorBuilder
以避免使用显式类型参数,而不会丢失使用Builder来配置收集器的信息?更新:
map3
的声明有效?它与涉及map2
的声明有何不同?答案 0 :(得分:4)
回答你的问题“意义,是否有一种打破类型推断的已知模式?”很快:当然,有一种模式,而且Java编程语言的整个行为都有huge specification。
但是关于类型推断和方法调用类型的章节实际上是详尽无遗且难以理解的。最好的说明是,在出现意外行为的情况下,通常会根据规范对预期行为进行大量讨论。
但对于程序员来说,有一些问题可以解释并且可以记住。
有两种方法可以推断类型参数,通过传递给方法的参数或构成表达式的部分,或者通过表达式的目标类型,即预期调用的参数类型,赋值的变量或返回语句的方法的返回类型。
目标类型可以通过嵌套方法调用传播,如
TargetType x=foo(bar(/*target type can be used*/));
或有条件的
TargetType x=condition? foo(/*target type can be used*/): foo(/*target type can be used*/);
但不以及
中的链式调用TargetType x=foo(/*target type can NOT be used*/).foo();
现在举例来说:
ImmutableMap<String, Integer> map1 = Stream.of("1", "2", "3").collect( expression );
此处,Stream.of(…)
和.collect(…)
是链式,因此目标类型不能用于确定of
调用的流类型,而是参数提供给该方法足以推断出类型Stream<String>
。 collect
方法提供了分配给map1
的结果,因此,流类型Stream<String>
和目标类型ImmutableMap<String, Integer>
都是已知的,可用于表达式的类型推断。在表达式上:
Testcase.toImmutableMap(keyMapper, valueMapper).build()
这是一个链式调用,因此build()
已知目标类型,toImmutableMap
则不知道。但是,toImmutableMap
的参数是具有已知确切类型的局部变量,因此类型推断可以使用它们来推断toImmutableMap
的结果类型,并检查它是否与{{1}的期望相匹配}
.build()
这又是一个链式调用,但现在参数Testcase.toImmutableMap(i -> i, Integer::valueOf).build()
的类型不完整,并且缺少目标类型。在不知道目标类型的情况下尝试猜测i - > i
的类型的尝试失败。
i -> i
这是不链式调用,因此目标类型可用于Testcase.toImmutableMap2(i -> i, Integer::valueOf)
调用(针对toImmutableMap2
调用,这是一个嵌套的调用)。因此,collect
的目标类型允许推断参数的目标类型,因此可以推导toImmutableMap2
lambda表达式。使用适当的目标类型,可以推断出正确的功能签名。
答案 1 :(得分:3)
lambda表达式的目标类型完全由上下文as discussed in the Java tutorial确定。因此,lambdas不会对类型参数推断做出贡献;相反,他们依赖就可以了。方法引用“ 紧凑,易于阅读的 lambda表达式用于已经具有名称的方法”(Oracle Java Tutorial;强调添加),因此没有区别在涉及时会对颜色类型分析进行不同的着色。
将lambda / method引用赋给变量时,该变量的类型提供了推断类型参数的上下文。但是,当您将它们直接传递给泛型方法时,您需要一些其他机制来推断它们的类型。在某些情况下,该方法的其他参数可能会用于此目的。在您的特定情况下,您可能需要显式类型参数:
ImmutableMap<String, Integer> map2 = Stream.of("1", "2", "3").collect(
Testcase.<String, String, Integer>toImmutableMap(i -> i, Integer::valueOf).build());
<强>更新强>
关于更新的问题,看起来Java可以在map3
案例中正确推断类型,部分原因是调用MapCollectorBuilder.build()
方法并不复杂。如果没有build()
,map3
的类型会提供上下文来确定Stream.collect()
的第一个类型参数,它同时提供K
和V
。类型参数T
可以从Stream
的(推断的)类型推断出来。
但是,如果涉及build()
,我认为Java将推断泛型方法toImmutableMap()
的类型参数的问题与调用build()
的返回值类型的问题分开关于它的回报价值。换句话说,它需要在考虑通过对该值调用方法获得的值的类型之前确定toImmutableMap()
返回的对象的类型。
答案 2 :(得分:1)
还有另一种解决方法。您可以给编译器提示,明确指定标识lambda的参数类型:
ImmutableMap<String, Integer> map2 = Stream.of("1", "2", "3")
.collect(Testcase.toImmutableMap((String i) -> i, Integer::valueOf).build());
在Javac 1.8.0_25和ECJ 3.10.2中编译好。