Java方法引用到具有通用参数的方法

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

虽然立即解决问题是理想的,但在这种情况下不可能。

显然,我做错了什么。但是如何使它正常工作呢?

谢谢。


IExecutable<IParameter>能正常工作吗?另外,我觉得你会收到很多有关缺少泛型类型的警告,请尝试修复它们。 - Thilo
3
如果您拥有类型为IExecutable<?>的引用,您将无法使用除null字面值以外的任何参数调用execute。因此,我不明白您想做什么。 - Sotirios Delimanolis
Java7和Java8之间有一个重要的区别。在Java7中,这将起作用。但在Java8中,显然不会。 - Willem Van Onsem
2
@CommuSoft - 我认为Java7不理解this::foo :) - ZhongYu
@bayou.io:显然不是,但对于等效情况,它的工作方式也不同。 - Willem Van Onsem
3个回答

3
在这种情况下,foo没有被编写来处理除Parameter之外的任何IParameter。您可以将foo的引用分配给类型为IExecutable<? extends IParameter>的变量,但是这意味着它是一个处理某���未知类型IParameter(在本例中为Parameter)的可执行文件。由于具体的子类型是未知的,因此在其执行方法中传递任何IParameter的子类型都不是语法上安全的,因为您不知道它可以在此范围内处理哪个子类型!
您需要的是另一个类型变量,而不是使用捕获(?)。这样,您可以指定传入的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);
  }
}

非常感谢,这正是我所需要的(另外需要补充的是方法和方法引用不是静态的)。 - basme

1
您的接口中的类型参数P被限制为子类型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()))。你只能传递 null 参数给 execute

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

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

IExecutable<Parameter> executable = this::foo;

这将编译并允许调用。
executable.execute(new Parameter());

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;

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

2
即使使用各种转换,也无法在IExecutable <?>中执行executable.execute(new Parameter()) - Seelenvirtuose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接