Java方法引用具有泛型参数的方法

时间:2015-05-27 00:03:08

标签: java generics java-8 method-reference

我试图对一个方法进行方法引用,该方法在类声明中指定了泛型参数。 所以我有:

public interface IExecutable<P extends IParameter> {

    void execute(P parameter);

}

public class Parameter implements IParameter {

    public void childSpecific() {
    ...
    }
}

public class TestClass {
    ...
    //somewhere in the code
    public void foo(Parameter parameter) {
        parameter.childSpecific();
    }

    public void test() {
        IExecutable<?> executable = this::foo; //compilation error
        // The type TestClass does not define inner(IParameter) that is applicable here
        executable.execute(new Parameter()); //compilation error as well
        // The method execute(capture#4-of ?) in the type IExecutable<capture#4-of ?> is not applicable for the arguments (Parameter)
    }
    ...
}

具体而言,我不知道可执行文件的具体泛型类型。使用

IExecutable<Parameter> = ...

立即解决问题,但案件不可能。

显然,我做错了什么。但是如何让它发挥作用?

THX。

3 个答案:

答案 0 :(得分:3)

在这种情况下,不会编写foo来处理IParameter以外的任何Parameter。您可以将foo的引用分配给类型为IExecutable<? extends IParameter>的变量,但这意味着它是一个可执行文件,可处理某些未知类型的IParameter(在本例中为Parameter)。由于特定的子类型是未知的,因此将IParameter的任何子类型传递给其execute方法在语法上是不安全的,因为您不知道它可以在此范围内处理它!

您需要的是另一种类型变量,而不是使用捕获(?)。这样,您可以指定传入的IParameter与可执行文件接受的IParameter类型相同。你可以用一种新的方法来介绍它,就像我在下面做的那样:

public class TestClass {
  public static void foo(Parameter parameter) {
    parameter.childSpecific();
  }

  public static void main(String args) {
    execute(TestClass::foo, new Parameter());
  }

  public static <P extends IParameter> void execute(
        IExecutable<P> executable, P param) {
    executable.execute(param);
  }
}

答案 1 :(得分:1)

接口P中的类型参数IExecutable被限制为IParameter的子类型。考虑这两个子类型:

class Parameter implements IParameter { ... }
class AnotherParameter implements IParameter { ... }

现在,IExecutable<?>对于上述约束并不具体。实际上,?表示它绑定到IParameter未知子类型,可以是ParameterAnotherParameter(在我的示例中) )。

使用这样的变量声明,您将面临您提到的两个问题。

  1. 您的方法foo(Parameter)IExecutable<?>的更一般约束不匹配。如上所示,此类可执行文件可能绑定到AnotherParameter,这显然会违反foo的方法签名。

  2. 即使匹配,也不能像你一样使用。编译器不知道?实际映射到哪种类型。它唯一知道的事情是:它必须是IParameter的子类型,但哪一个是未知的。这意味着,不允许声明executable.execute(new Parameter())(同样也executable.execute(new AnotherParameter()))。您可以传递给execute的唯一参数是null

  3. 结论:可以通过声明类型为executable的变量IExecutable<? extends Parameter>来解决第1点。这与foo的方法签名匹配。但是第2点仍然不允许调用execute

    您唯一能做的就是将变量声明为

    IExecutable<Parameter> executable = this::foo;
    

    这将编译并允许调用

    executable.execute(new Parameter());
    

答案 2 :(得分:0)

此行暴露了java类型推断中的失败

IExecutable<?> executable = this::foo;

让我们以这种方式看待它

IExecutable<?> executable = p->this.foo(p);

要编译它,java需要知道foo(p)的含义。在java8之前,表达式的类型建立在子表达式的类型上;在这里,需要知道p的类型才能解决foo。但是没有指定p的类型,需要从周围的上下文推断出它。此处的上下文为IExecutable<? extends IParameter>p推断为IParameter - 方法foo(Iparameter)不存在。

一般来说,类型推理面临两难选择,它是从上到下推断还是自下而上? Java8为此定义了一个非常复杂的过程,这在人类是不可能理解的。:)

解决方法:指定p

的类型
IExecutable<?> executable = (Parameter p)->this.foo(p);

或指定更具体的目标类型

IExecutable<?> executable = (IExecutable<Parameter>)p->this.foo(p);

IExecutable<?> executable = (IExecutable<Parameter>)this::foo;

如果你问语言设计师,他们会认为所有这一切都很明显......但程序员的最佳动作可能只是尝试不同的东西,直到它起作用,而不是研究实际的语言规范。