I'm attempting to write some code that receives various implementations of the classes in the java.util.function.*
package, but I keep running against a certain compiler error for a specific syntax that I'd really like to get around (explicit type arguments like obj.<Type>foo(/*...*/)
work, but they lack grace).
In short, I'd like to be able to always use method references where I can, and for some reason I don't understand why the compiler can't understand the references explained below.
Suppose the following two classes (implementation is irrelevant):
Some entity class with getter/setter methods:
class Thing {
public List<String> getThings() {
return null;
}
public void setThings(List<String> things) {
// ...
}
}
Some other class that performs actions on instances of that entity class:
class FooBar<A> {
public <B> void foo(Function<A, List<B>> f) {
// ...
}
public <B> void bar(BiConsumer<A, List<B>> f) {
// ...
}
public <B> void wuz(Function<A, List<B>> x, BiConsumer<A, List<B>> y) {
// ...
}
}
When performing calls to these methods the compiler gives me various errors:
// create an instance of the handler class
FooBar<Thing> fb = new FooBar<>();
Ex. 1) Calling the method that expects a function works just fine, no compile errors:
fb.foo(Thing::getThings);
Ex. 2) Yet calling the method that expects a biconsumer gives me this:
fb.bar(Thing::setThings);
// The type Thing does not define setThings(Thing, List<B>) that is applicable here
Ex. 3) As expected, explicitly stating the type works just fine, no compile errors:
fb.<String>bar(Thing::setThings);
Ex. 4) Yet when I write out the lambda longhand, it gives me a different compile error (stating the types in the lambda argument works fine though):
fb.bar((thing, things) -> thing.setThings(things));
// The method setThings(List<String>) in the type Thing is not applicable for the arguments (List<Object>)
Ex. 5) And when calling the method that expects both, I get a different compile error for each argument:
fb.wuz(
Thing::getThings,
// The type of getThings() from the type Thing is List<String>, this is incompatible with the descriptor's return type: List<B>
Thing::setThings
// The type Thing does not define setThings(Thing, List<B>) that is applicable here
);
Ex. 6) Again, as expected, stating the type explicitly works fine again, no compile errors:
fb.<String>wuz(Thing::getThings, Thing::setThings);
Ex. 7) And this is what mystifies me most: for the method that expects both the function and the biconsumer, writing out just the biconsumer (no types) and using a method references for the function, works fine, no compile errors (?!):
fb.wuz(Thing::getThings, (thing, things) -> thing.setThings(things));
What I don't understand is that apparently, the compiler handles explicit lambdas and method references differently when determining runtime type/type erasure in different scenarios, namely:
- Functional interface that gets arguments and returns value
- Functional interface that gets arguments but returns no value
- Method with arguments for both 1. and 2. types
This only seems to occur when the type of the functional interface is a generic type itself (in this example, a List), which leads me to believe this has something to do with type erasure, but I'm at a loss as to the answer.
I would love to be able to write...
fb.wuz(Thing::getThings, Thing::setThings);
...without either the explicit type or a longhand lambda expression.
If there is a way to refactor the methods in FooBar
to support this, I'd really like to know.
Of course, if someone is able to explain these various behaviors of the compiler, I'd really appreciate that too! :-)
EDIT
I'm especially interested in learning why examples #1 and #7 do work, yet why example #4 on its own doesn't work (and then, why example #2 doesn't work either).