Anders
Anders

Reputation: 8577

Passing an argument that could be of multiple types to a method in Java?

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

Answers (6)

LuCio
LuCio

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

Andrey Tyukin
Andrey Tyukin

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:

  • if 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
  • if you later want to make it work with TypeD, TypeE, ..., TypeZ, you don't have to dig into code of bar, you can simply supply yet another typeclass implementation
  • more importantly: if someone else wanted to use it with TypeD, 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

Ben P.
Ben P.

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

Dave Costa
Dave Costa

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

Rishab Ghanti
Rishab Ghanti

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

elbraulio
elbraulio

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

Related Questions