有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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

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) 个答案

  1. # 1 楼答案

    这一行暴露了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. # 2 楼答案

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

    您需要的是另一个类型变量,而不是使用捕获(the?)。通过这种方式,您可以指定要传递的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);
      }
    }
    
  3. # 3 楼答案

    接口IExecutable中的类型参数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()))。唯一允许传递给execute的参数是null

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

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

    IExecutable<Parameter> executable = this::foo;
    

    这将编译并允许调用

    executable.execute(new Parameter());