【问题标题】:Returning a lambda function with parameter types constrained by generic type parameters返回具有受泛型类型参数约束的参数类型的 lambda 函数
【发布时间】:2017-01-12 22:46:51
【问题描述】:

假设我想编写一个方法buildBiFxnWithSameTypeArgs,它产生一个两个参数BiFunction,其输入参数的类型相同。事先不知道(即何时调用buildBiFxnWithSameTypeArgs)具体是什么类型——我只是希望编译器强制执行,每当调用buildBiFxnWithSameTypeArgs返回的函数时,提供给它的参数的类型必须匹配。本质上,最终效果应该是 lambda 等价物,对于方法定义中的两个参数的类型使用相同的泛型类型参数。

我最初的尝试如下所示:

public interface ConstrainedBiFunction<I, O> extends BiFunction<I, I, O> {}

public static ConstrainedBiFunction<?, String> buildBiFxnWithSameTypeArgs() {
    return (inputOne, inputTwo) -> String.valueOf(inputOne) + String.valueOf(inputTwo);
}

public void test() {
    buildBiFxnWithSameTypeArgs().apply(Integer.valueOf(1), Integer.valueOf(2));
}

然而,通配符类型参数? 似乎不能以这种方式使用; apply 步骤编译失败,出现以下错误:

apply (capture<?>, capture<?> in BiFunction cannot be applied to (java.lang.Integer, java.lang.Integer)

是否有可能返回一个参数类型被推断出来的 lambda 函数,然后以这种方式相互约束?

编辑:为这个不太好的问题道歉;我有一个更复杂的问题,我试图抓住它的本质,以便在这里提出最简单的问题,但看起来我把问题简化得太多了。当然,我知道所有类都继承自Object,所以在这个问题的过度简化再现中,@shmosel 和@JB Nizet 提出的解决方案有效。一旦我更好地提炼了原始问题,我将发布一个新问题。

【问题讨论】:

  • 您认为ConstrainedBiFunction&lt;?, String&gt;.apply(Integer.valueOf(1), Integer.valueOf(2)); 有何关系?
  • 所有类最终都继承自 Object,因此即使您传递了例如 Banana 和 Truck,编译器也会将其推断为 ConstrainedBuFunction,除非您明确指定类型。

标签: java generics lambda types type-inference


【解决方案1】:

通配符表示“特定但未知的类型”。您不能传递满足? 类型参数的任何 参数,因为它可能属于错误类型。将返回类型更改为ConstrainedBiFunction&lt;Object, String&gt;,它将能够接受任何输入类型,因为每个类都隐式扩展Object

public static ConstrainedBiFunction<Object, String> buildBiFxnWithSameTypeArgs() {
    return (inputOne, inputTwo) -> String.valueOf(inputOne) + String.valueOf(inputTwo);
}

请注意,这仍然可以用于对输入类型有限制的方法,使用PECS 原则。例如:

// input parameter must be of type Integer or any supertype,
// so that we can safely pass in an Integer
String combine(ConstrainedBiFunction<? super Integer, String> function) {
    return function.apply(1, 2);
}

void test() {
    ConstrainedBiFunction<Object, String> concat = buildBiFxnWithSameTypeArgs();
    ConstrainedBiFunction<Integer, String> sum = (a, b) -> String.valueOf(a + b);
    System.out.println(combine(concat)); // "12"
    System.out.println(combine(sum));    // "3"
}

【讨论】:

    【解决方案2】:

    虽然shmosel’s answer 正确指出使用Object 作为输入类型对于遵循PECS 规则的所有API 来说应该足够了,但可以修复您最初的尝试。

    你必须添加一个类型参数,而不是通配符,它​​允许调用者选择一个类型:

    public interface ConstrainedBiFunction<I, O> extends BiFunction<I, I, O> {}
    
    public static <T> ConstrainedBiFunction<T, String> buildBiFxnWithSameTypeArgs() {
        return (inputOne, inputTwo) -> String.valueOf(inputOne) + String.valueOf(inputTwo);
    }
    
    public void test() {
        ConstrainedBiFunction<Integer, String> f1 = buildBiFxnWithSameTypeArgs();
        String s1 = f1.apply(1, 1);
        ConstrainedBiFunction<String, String>  f2 = buildBiFxnWithSameTypeArgs();
        String s2 = f2.apply("hello ", "world");
    }
    

    请注意,当将其用作buildBiFxnWithSameTypeArgs().apply(1, 1); 时,编译器无论如何都会将Object 推断为T。这允许传递Integer 参数,但当然也可以传递其他任何东西,这使得两个参数具有相同类型的想法没有实际意义,即buildBiFxnWithSameTypeArgs().apply(1.5, "foo"); 也可以工作,因为两个参数都可以分配给相同的类型@ 987654332@.


    此类构造处理无法通过 PECS 解决的问题有一些现实生活中的示例。

    例如Function.identity() 返回具有相同输入和输出类型的任意类型函数。当将此函数传递给toMap collector时,您可以获得与流元素具有相同键或值类型的映射。

    一个更复杂的例子是Comparator.naturalOrder()。在这里,类型参数有一个界限,确保要比较的元素确实是Comparable。由于这些类型没有实现Comparable&lt;?&gt;,而是一个引用自身的类型,所以naturalOrder() 在没有类型参数的情况下将无法工作。

    所以你不能做Comparator.naturalOrder().compare(42, "foo")。但是你可以做

    Comparator<Integer> c1 = Comparator.naturalOrder();
    c1.compare(10, 42);
    Comparator<String> c2  = Comparator.naturalOrder();
    c2.compare("foo", "bar");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多