Reputation: 8577
I am using a library with a method foo
with a signature like this:
TypeR foo(TypeA first, int second)
TypeR foo(TypeB first, int second)
TypeR foo(TypeC first, int second)
I am writing my own method bar
calling foo
, and I want to directly pass the first parameter along to foo
:
bar(??? first, int baz) {
int second = someCalculation(baz);
return foo(first, second);
}
My problem is that I do not know what type I should give to the parameter first
in bar
. There does not seem to be union typs in Java. How can I solve this without having to write three almost identical versions of bar
?
The types TypeA
to TypeC
do not share a common interface, and I have no control over them or foo
since they are in an external library. All I can control is how I implement bar
.
Upvotes: 4
Views: 4734
Reputation: 5173
We can replace the call of foo
in bar
by a call of a corresponding BiFunction
. Thus for all overloaded foo
methods a BiFunction
has to be defined.
private static final Map<Class<?>, BiFunction<?, Integer, TypeR>> FOO = Map.of(
TypeA.class, (first, second) -> foo((TypeA) first, second),
TypeB.class, (first, second) -> foo((TypeB) first, second),
TypeC.class, (first, second) -> foo((TypeC) first, second));
But for every overloaded foo
mapping one line only has to be written whereas each overloaded bar
method would need four lines if it were written as like in the question.
Now we can make bar
generic. But the following will not work, since the returned BiFunction
has an unbound wildcard as first type parameter.
<T> TypeR bar(T first, int baz) {
int second = someCalculation(baz);
return FOO.get(first.getClass()).apply(first, second);
}
We would need to declare FOO
as Map<Class<T>, BiFunction<T, Integer, TypeR>>
but that's not possible. To work around this we define a method with a type parameter T
which is supposed to establish the missing type equality. But that's not for free. It's at the cost of a warning resulting from casting:
private static <T> BiFunction<T, Integer, TypeR> fooOf(Object o) {
return (BiFunction<T, Integer, TypeR>) FOO.get(o.getClass());
}
Now we can use this method in bar
:
<T> TypeR bar(T first, int baz) {
int second = someCalculation(baz);
return fooOf(first).apply(first, second);
}
This is similar to an instanceof
approach but displaces the type distinction from the method bar
to the Map
FOO
. If there are other methods than bar
which also need to call foo
in a similar way, the type distinction has not be coded again. FOO
and fooOf
can be reused. If the library changes and an additional overloaded TypeR foo(TypeD first, int second)
gets introduced, only FOO
needs to be updated by adding one additional line.
This approach requires first
to be not null
.
Upvotes: 1
Reputation: 44908
In theory, nothing prevents you from using typeclasses for ad-hoc polymorphism in Java:
interface Fooable<T> {
int foo(T t, int second);
}
public int bar<T>(T t, int baz, Fooable<T> fooable) {
int second = someCalculation(baz);
return fooable.foo(t, second);
}
The implementations would look roughly as follows:
public class TypeAFooable implements Fooable<TypeA> {
public int foo(TypeA t, int second) { return yourLibrary.foo(t, second); }
}
And invocations would look like
bar(myTypeAThing, 1234, new TypeAFooable());
If bar
were a little bit longer, this would bring several advantages:
bar
makes lots of calls to foo
in lots of different places, then you could define each instance of the typeclass once, instead of having multiple if-instanceof
-branches inside of bar
TypeD
, TypeE
, ..., TypeZ
, you don't have to dig into code of bar
, you can simply supply yet another typeclass implementationTypeD
, TypeE
, etc., then they could supply yet another typeclass implementation, without having to fork your bar
implementation.In practice, the problem is that the typeclass instances won't be supplied automatically by the compiler, so it's usually not worth the hassle. Unless bar
is somehow gigantic and super-complex, just overload it, or use instanceof
.
Upvotes: 3
Reputation: 54194
I would attempt to avoid this problem by decomposing your bar()
function into the piece that deals with computing int second
and the piece that deals with TypeA
/B
/C first
. That way you could write something like
int second = decomposedPortion(baz);
TypeR result = foo(first, second);
Upvotes: 1
Reputation: 48111
The closest I can think of is something like this (removing the stuff relating to second
since it is irrelevant to the point).
TypeR bar(Object first) {
TypeR retvalue;
if (first instanceof TypeA)
retvalue = foo( (TypeA)first );
else if (first instanceof TypeB)
retvalue = foo( (TypeB)first );
else if (first instanceof TypeC)
retvalue = foo( (TypeC)first );
return retvalue;
}
If there is a more specific known supertype of TypeA etc., you could obviously use that instead of Object
as the parameter.
This is only possible if all three versions of foo
have the same return type.
Sadly, this "solution" is not really better, and maybe worse, than writing three overloaded versions of bar
.
Upvotes: 5
Reputation: 46
You can define first as a generic type type in bar as below.
bar(Object first, int baz) {
int second = someCalculation(baz);
if(first instanceOf TypeA) return foo((TypeA) first, second);
else if(first instanceOf TypeB) return foo((TypeB) first, second);
else if(first instanceOf TypeC) return foo((TypeC) first, second);
else throw new CustomException("Object pass in is not of correct type");
}
Upvotes: 1
Reputation: 994
Use interfaces like this:
interface Type { //...}
class TypeA implements Type { //...}
class TypeB implements Type { //...}
class TypeC implements Type { //...}
then you will only need one foo function
foo(Type first, int second)
and one bar function
bar(Type first, int baz)
Upvotes: -1