3

我正在尝试对具有在类声明中指定的泛型参数的方法进行方法引用。所以我有:

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> = ...

立即解决问题,但对于这种情况是不可能的。

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

谢谢。

4

3 回答 3

3

在这种情况下,不会编写 foo 来处理IParameterParameter. 您可以将 foo 的引用分配给类型为 的变量IExecutable<? extends IParameter>,但这意味着它是一个可执行文件,可以处理某些未知类型的IParameter(在本例中为Parameter)。由于特定的子类型是未知的,因此将IParameterin 的任何子类型传递给它的 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);
  }
}
于 2015-05-27T01:03:45.880 回答
1

P接口中的类型参数IExecutable被限制为IParameter. 考虑这两种亚型:

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

现在,IExecutable<?>关于上述约束,an 不再具体。事实上,?声明它绑定到 的未知子类型IParameter,它可能是ParameterAnotherParameter(在我的示例中)。

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

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

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

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

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

IExecutable<Parameter> executable = this::foo;

这将编译并允许调用

executable.execute(new Parameter());
于 2015-05-27T07:08:48.250 回答
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;

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

于 2015-05-27T01:24:41.803 回答