public static class MyClass {
public String methodS(UnaryOperator<String> fn) {
return fn.apply("");
}
public Integer methodI(UnaryOperator<Integer> fn) {
return fn.apply(0);
}
public <T> T identity(T t) {
return t;
}
public <T> void test(UnaryOperator<T> fn) {
String s;
s = methodS(UnaryOperator.identity()); // OK
s = methodS(this::identity); // OK
s = methodS(String::trim); // OK
s = methodS(fn::apply); // Fail
Integer i;
i = methodI(UnaryOperator.identity()); // OK
i = methodI(this::identity); // OK
i = methodI(Math::abs); // OK
i = methodI(fn::apply); // Fail
}
public void goal() {
test(o -> {
doSomething(o);
return o;
});
}
}
I wonder why passing class method references OK but passing a method parameter not in the above example? How do you fix it?
The goal is to be able to call like in the goal()
method, where a generic lambda can be written to take on the common base class object (in this case Object
).
Edited: some misunderstood the question, probably because of Object
as the base class. Here might be a better example with some custom common base class:
public static class MyClass {
public static class ExtS extends MyClass {};
public static class ExtI extends MyClass {};
public void common() {}
public ExtS methodS(UnaryOperator<ExtS> fn) {
return fn.apply(new ExtS());
}
public ExtI methodI(UnaryOperator<ExtI> fn) {
return fn.apply(new ExtI());
}
public <X extends MyClass> X identity(X x) {
x.common(); // can call MyClass methods
return x;
}
public <Y extends MyClass> void test(UnaryOperator<Y> fn) {
ExtS s;
s = methodS(UnaryOperator.identity()); // OK
s = methodS(this::identity); // OK
s = methodS(t -> (ExtS) fn.apply((Y) t)); // OK
s = methodS(fn::apply); // fail
ExtI i;
i = methodI(UnaryOperator.identity()); // OK
i = methodI(this::identity); // OK
i = methodI(t -> (ExtI) fn.apply((Y) t)); // OK
i = methodI(fn::apply); // fail
}
public void goal() {
test(y -> {
y.common(); // can call MyClass methods
return y;
});
test(this::identity); // doing the same as above
}
}
You should step back and try doing actual assignments. When you try to do methodI
all you are doing is assigning the argument to UnaryOperator<ExI>
.
static <T> UnaryOperator<T> identity()
That means the T is assigned when you call identity.
methodI(UnaryOperator::identity);
This is good, that is the point of the identity, you call it with an argument and it will take that generic. When you call methodI
it assigns the generic of UnaryOperator.identity as ExtI
.
methodI(this::identity);
Same as the previous example, it takes the type when you call identity so the type is not defined until you assign it in the argument of methodI.
methodI(fn::apply);
This is not good because you have already assigned the type of fn. It is Y
all you know about Y
is that it is a MyClass
and it's parents. That means methodI is trying to assign. UnaryOperatory<ExtI> fn
to UnaryOperator<? extends MyClass>
. They're not the same thing. But they could be. Which is why
methodI(t -> (ExtI) fn.apply((Y) t));
works. Your constraints don't say what you're doing is wrong, they just don't guarantee correctness. If you use explicit casts you can "make it work".
Consider this simple class.
class Example<T>{
T apply( T t ){
return t;
}
<U> U identity( U u){
return u;
}
}
Example<String> ex = new Example();
ex.apply("one");
That works because apply works on String.
ex.apply(2);
Broken because ex is parameterized by a String.
ex.identity(2);
Works because identity takes the parameter type when it is called.