Java 何时需要显式类型参数?

2022-09-04 22:20:14

鉴于:

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));
    }
}

语句涉及和编译正常,但失败:map1map3map2

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>

  1. Java 8 何时需要显式类型参数?也就是说,是否有一种已知的模式可以打破类型推断?
  2. 是否可以更改以避免显式类型参数,而不会丢失使用生成器来配置收集器?toImmutableMap()MapCollectorBuilder

更新

  1. 为什么涉及的声明有效?它与涉及的声明有何不同?map3map2

答案 1

为了回答你的问题“意思是,有没有一个已知的模式可以打破类型推断?”:当然,有一个模式,而且Java编程语言的整个行为都有一个巨大的规范

但是,有关类型推断和方法调用类型的章节非常详尽且难以理解。在发生意外行为的情况下,通常会根据规范对预期行为进行大量讨论,这一事实可以最好地说明这一点。

但是对于程序员来说,有一些要点是可以解释和记忆的。

有两种方法可以推断类型参数,一种是通过传递给组成表达式的方法或部分的参数,或者通过表达式的目标类型,即调用参数的预期类型,赋值的变量或返回语句情况下的方法的返回类型。

目标类型可以通过嵌套方法调用进行传播,例如

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(…)ofStream<String>collectmap1Stream<String>ImmutableMap<String, Integer>

  • Testcase.toImmutableMap(keyMapper, valueMapper).build()这是一个链式调用,因此目标类型已知但不适用于 。但是,参数是具有已知精确类型的局部变量,因此类型推断可以使用它们来推断结果类型并检查它是否符合build()toImmutableMaptoImmutableMaptoImmutableMap.build()

  • Testcase.toImmutableMap(i -> i, Integer::valueOf).build()这又是一个链式调用,但现在参数具有不完整的类型,并且缺少目标类型。在不了解目标类型的情况下猜测 类型失败。i - > ii -> i

  • Testcase.toImmutableMap2(i -> i, Integer::valueOf)这不是一个链式调用,因此目标类型可用于调用(相对于调用,它是嵌套调用)。因此,目标类型 允许推断参数的目标类型,从而推断 lambda 表达式的目标类型。使用正确的目标类型,可以推断出正确的功能签名。toImmutableMap2collecttoImmutableMap2i -> i


答案 2

lambda 表达式的目标类型完全由上下文确定,如 Java 教程中所述。因此,lambdas不参与类型参数推断;相反,他们依靠它。方法引用“紧凑,易于阅读的lambda表达式,用于已经具有名称的方法”(Oracle Java教程;着重号是后加的),因此没有区别,当涉及它们时,颜色类型分析会有所不同。

将 lambda / 方法引用分配给变量时,该变量的类型会提供用于推断类型参数的上下文。但是,当您将它们直接传递给泛型方法时,您需要一些其他机制来推断它们的类型。在某些情况下,该方法的其他参数可能用于该目的。在您的特定情况下,看起来您可能需要显式类型参数:

ImmutableMap<String, Integer> map2 = Stream.of("1", "2", "3").collect(
        Testcase.<String, String, Integer>toImmutableMap(i -> i, Integer::valueOf).build());

更新

关于更新的问题,看起来Java可以在这种情况下正确推断类型,部分原因是调用该方法不会使确定变得复杂。如果没有,类型 提供上下文来确定 的第一个类型参数,该参数同时给出 和 。类型参数可以从 (推断的) 类型中推断出来。map3MapCollectorBuilder.build()build()map3Stream.collect()KVTStream

然而,对于涉及,我认为Java正在将推断泛型方法的类型参数的问题与调用其返回值的返回值的类型问题分开。换句话说,它希望在考虑通过对该值调用方法获得的值的类型之前,确定返回的对象的类型。build()toImmutableMap()build()toImmutableMap()


推荐