问题
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.
Your method
foo(Parameter)
does not match the more general constraint of anIExecutable<?>
. As seen above, such an executable could be bound toAnotherParameter
which clearly would violate the method signature offoo
.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 ofIParameter
, but which one is not known. That means, the statementexecutable.execute(new Parameter())
is not allowed (as alsoexecutable.execute(new AnotherParameter())
). The only parameter you are allowed to pass toexecute
isnull
.
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