Java method reference to shared parameter method
I am trying to make a method reference to a method that has a common 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)
}
...
}
Specifically, that I don't know of a specific generic executable type here. Using
IExecutable<Parameter> = ...
solves the problem immediately, but this is not possible for case.
It is clear that I am doing something wrong. But how do you make it work?
thank.
source to share
In this case, foo is not written to handle IParameter
anything other than Parameter
. You can assign a reference to foo to a variable of type IExecutable<? extends IParameter>
, however this means that it is an executable file that handles an unknown type IParameter
(in this case Parameter
). Since the specific subtype is unknown, it would be non-synthetically safe to pass any subtype IParameter
to its execution method, since you don't know what it can handle in this area!
You need a different type variable instead of using capture (?). This way you can specify that IParameter
which you are passing is the same type as IParameter
which the executable is taking. You can introduce this with a new method as I do 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);
}
}
source to share
The type parameter P
in your interface IExecutable
is subtyped limited IParameter
. Consider these two subtypes:
class Parameter implements IParameter { ... }
class AnotherParameter implements IParameter { ... }
Now, IExecutable<?>
no more specifically refers to the above limitation. In fact, it ?
states that it is associated with an unknown subtype IParameter
, which could be Parameter
or AnotherParameter
(in my example).
With this variable declaration, you run into the two problems you mentioned.
-
Your method
foo(Parameter)
does not meet the more general constraintIExecutable<?>
. As seen above, such an executable can be linked toAnotherParameter
, which will clearly violate the method signaturefoo
. -
Even if it fits, it cannot be used the way you do. The compiler does not know what type it was mapped to
?
. The only thing he knows is that it must be a subtypeIParameter
, but unknown. This means that the assertion isexecutable.execute(new Parameter())
invalid (as isexecutable.execute(new AnotherParameter())
). The only parameter you can pass toexecute
isnull
.
Conclusion: Point 1 can be solved by declaring a variable executable
with a type IExecutable<? extends Parameter>
. This matches the method signature foo
. But point 2 still doesn't allow calling execute
.
The only thing you can do is declare the variable as
IExecutable<Parameter> executable = this::foo;
This will compile and resolve the call
executable.execute(new Parameter());
source to share
This line fails 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 value foo(p)
. Before java8, the type of an expression builds on the types of subexpressions; here the type p
must be known 1st for permission foo
. But the type is p
not specified, it needs to be inferred from the surrounding context. Here is the context IExecutable<? extends IParameter>
, but it is p
deduced on IParameter
- and the method foo(Iparameter)
does not exist.
In general, type inference faces a dilemma, is it inference from top to bottom or bottom to top? Java8 defines an extremely complex procedure for this, which is humanly impossible to understand :)
Workarounds: specify the type p
IExecutable<?> executable = (Parameter p)->this.foo(p);
or specify a more specific type of goal
IExecutable<?> executable = (IExecutable<Parameter>)p->this.foo(p);
IExecutable<?> executable = (IExecutable<Parameter>)this::foo;
If you ask the language designers, they'll think this is all completely obvious ... but the best way for a programmer is probably to just try different things until it works, than to study the actual language specification.
source to share