Java method reference to a method with generic parameter

我们两清 提交于 2019-12-10 10:00:42

问题


I'm trying to make a method reference to a method which have a generic parameter specified in a class declaration. So I have:

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)
    }
    ...
}

It's specific that I don't know the concrete generic type of the executable here. Using

IExecutable<Parameter> = ...

solves the problem immediately, but it's impossible for the case.

Clearly, I'm doing something wrong. But how to make it work?

Thx.


回答1:


In this case, foo is not written to handle any IParameter other than Parameter. You could assign a reference to foo to a variable of type IExecutable<? extends IParameter>, however this means that it is an executable that handles some unknown type of IParameter (in this case, Parameter). Since the specific subtype is unknown, it would not be syntactically safe to pass any subtype of IParameter in to its execute method, since you don't know which it can handle within this scope!

What you need is another type variable instead of using a capture (the ?). This way you can specify that the IParameter you're passing in is the same type as the IParameter the executable accepts. You could introduce this with a new method, like I'm doing below:

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);
  }
}



回答2:


The type parameter P in your interface IExecutable is constrained to being a subtype of IParameter. Consider these two subtypes:

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

Now, an IExecutable<?> is not more specific regarding the above mentioned constraint. In fact, the ? states that it is bound to an unknown subtype of IParameter, which could be Parameter or AnotherParameter (in my example).

With such a variable declaration, you face the two problems you mentioned.

  1. Your method foo(Parameter) does not match the more general constraint of an IExecutable<?>. As seen above, such an executable could be bound to AnotherParameter which clearly would violate the method signature of foo.

  2. Even if it matched, it cannot be used like you did. The compiler does not know to which type the ? actually was mapped. The only thing it knows: It must be a subtype of IParameter, but which one is not known. That means, the statement executable.execute(new Parameter()) is not allowed (as also executable.execute(new AnotherParameter())). The only parameter you are allowed to pass to execute is null.

Conclusion: Point 1 could be solved by declaring the variable executable with type IExecutable<? extends Parameter>. This matches the method signature of foo. But point 2 still does not allow the call to execute.

The only thing you can do is to declare the variable as

IExecutable<Parameter> executable = this::foo;

This will compile and allow the call to

executable.execute(new Parameter());



回答3:


This line exposes failure in java type inference

IExecutable<?> executable = this::foo;

Let's look at it this way

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

To compile it, java needs to know the meaning of foo(p). Before java8, the type of an expression is built on the types of sub-expressions; here, the type of p needs to be known 1st to resolve foo. But the type of p is not specified, it needs to be inferred from surrounding context. Here the context is IExecutable<? extends IParameter>, and p is inferred to IParameter - and method foo(Iparameter) does not exist.

In general, type inference faces a dilemma, does it infer from top down, or bottom up? Java8 defines an extremely complicated procedure for that, which is humanly impossible to understand:)

Workarounds: specify the type of p

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

or specify a more specific target type

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

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

If you ask the language designers, they'd consider all of this is quite obvious ... but a programmer's best action is probably just try different things till it works, than to study the actual language spec.



来源:https://stackoverflow.com/questions/30471015/java-method-reference-to-a-method-with-generic-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!