Reputation: 23332
I have some code with this general structure:
interface Func<A> {
double apply(Optional<A> a);
}
class Foo {
public double compute(Func<Double> f) {
// Sometimes amend the function to do something slightly different
Func<Double> g = f;
if (someCondition())
g = oa -> Math.max(0, f.apply(oa));
return g.apply(Optional.of(3.14)) + g.apply(Optional.empty());
}
}
This, in itself, works well enough. Now I want to be more liberal such that if someone has, say, a Func<Number>
or Func<Object>
instead of a Func<Double>
they can still pass that into compute
. A priori this ought to be safe enough. Very well, so I change it to
public double compute(Func<? super Double> f) {
Func<? super Double> g = f;
if (someCondition())
g = oa -> Math.max(0, f.apply(oa));
...
Unfortunately now the lambda doesn't typecheck (says Eclipse) because the type of oa
cannot be passed to f.apply
.
The assignment of f
itself to g
doesn't seem to worry the compiler, and the problem does not arise if the argument to apply
is A
rather than Optional<A>
.
Unfortunately there doesn't seem to be a way to name the ? super Double
argument type so I can use it again as the type for g
and/or replace the lambda with an old-fashioned inner class.
For example, this is not even syntactically allowed:
public <T super Double> double compute(Func<T> f) {
Func<T> g = f;
...
Is there any reasonably elegant way to make this work?
The best I've come up with so far is
public double compute(Func<? super Double> f) {
if (someCondition())
return computeInner(oa -> Math.max(0, f.apply(oa)));
else
return computeInner(f);
}
private double computeInner(Func<? super Double> g) {
return g.apply(Optional.of(3.14)) + g.apply(Optional.empty());
}
The compiler accepts this, but the indirect call for no other reason than to make the type checker happy is not really what I'd call elegant.
Upvotes: 5
Views: 81
Reputation: 4681
A solution is change your interface signature:
double apply(Optional<? extends A> a);
Consider if your interface is simply:
double apply(A a);
Then never error will happen.
It's because Double
is assignable to Object
. Compiler will auto adapt the type. That means the interface in fact is:
double apply(? extends A a);
So what you need do is let your interface has this adapt ability.
func(Number)
can accept Double
as parameter.
func(Optional<Number>)
should accept Optional<Double>
as well.
So you should add the ? extends
on your inteface.
Upvotes: 0
Reputation: 23624
In general I find type inference for super bounds in Java to be a nightmare.. there are many compiler bugs and it's generally hard to reason around why the compiler will reject a certain syntax and not others.
That said, you can get around this in a hackish way by casting f
to a Func<Double>
type. This is not exactly a safe cast; if f
had any state, we might assume that the lower bound on f
is Double
when in reality it could be Number
or Object
. With the cast, you don't incur the performance penalty of the solution provided by @AndyTurner, but unchecked casts are also not your friend.
public double compute(Func<? super Double> f) {
// Sometimes amend the function to do something slightly different
Func<? super Double> g = f;
if (someCondition())
g = oa -> Math.max(0, (((Func<Double>) f)).apply(oa));
return g.apply(Optional.of(3.14)) + g.apply(Optional.empty());
}
All in all, I would consider the solution you presented in the question to be the best solution to the problem. It is a little more verbose, but the compiler should be able to optimize the code. You don't lose compile time safety with unchecked casts, and you don't introduce run time degradation due to the creation of additional Optional
instances.
Upvotes: 1
Reputation: 140319
Try this one weird trick:
g = oa -> Math.max(0, f.apply(oa.map(a -> a)));
// ^----------^
Mapping the type of the optional like this allows the compiler to "cast" the type of the optional to a consistent type.
This does have the downside of creating a new Optional
instance.
But, of course, I asked this question which ponders whether this is actually something that is intended to be allowed by the spec, or a bug.
Personally, I don't find your "best so far" particularly egregious. Of course, it depends on how the real code looks with it.
Upvotes: 2