1

I have an interface like so

public interface A {}

and an interface which extends from the previous like so

public interface B extends A {}

The constructor of one of my classes takes as parameter a Function<A, String> object. Within the constructor, several objects which implement A are passed into said function to generate a string.

I would like to be able to pass this constructor a Function<B, String> object, as all classes that implement B implement A by extension. However, when attempting to do this, I am told "Incompatible types: A is not convertible to B". Which I don't understand as I am trying to convert B to A not A to B

I have tried changing the parameter type from a Function<A, String> to a

Function<? extends A, String>

but alas this doesn't work either.

Is there some way I can achieve passing either a Function<A, String> or a Function<B, String> into the same argument?

I am creating a game using the Swing library, and my A is Displayable and B is Purchasable. I have a class DisplayablePanel which extends JPanel and creates a visual representation of any displayable object in my game. All Purchasables are Displayable so I can display them using my DisplayPanel however, I want Purchasables to be able to display Purchasable specific information too. Here my constructor uses the Function<A, String> to decide what text should be displayed on a button that sits on the DisplayPanel And for my Purchasables this information is specific to the Purchasable interface

Harry
  • 193
  • 1
  • 12

1 Answers1

2

s there some way I can achieve passing either a Function<A, String> or a Function<B, String> into the same argument?

Sure:

public void heyNow(Function<? super B, String> func) {
  B b = createAnInstanceOfSomethingImplementingB();
  String answer = func.apply(b);
}

That works fine.

Generics are invariant - in plain jane java, Number n = new Integer(10); is fine. In generics, it is not; ArrayList<Number> x = new ArrayList<String>(); doesn't compile because it shouldn't: After all, you can do this:

void foo(List<Number> list) {
  Number n = 5; // n is an integer.
  list.add(n); // this has to work, right?
}

So now we just added an integer. Imagine we invoked foo, passing in a new ArrayList<Double>(). We just.... added an integer.... to a list of doubles.

Oh dear.

This is why generics are invariant. Because the concept inherently is.

This is what <? extends> and <? super> are all about: You add some handcuffs to the method (<? extends> means: Can't invoke add, <? super> means: when invoking get(), just get Object back, that's all, i.e. not very useful, i.e. can't usefully invoke get), in trade for allowing different things in the <> to be passed along.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • Changing the parameter to type`Function super B, String> func` makes the output of `func.apply(a)` `Object` But how does this make sense as the return type of the function referenced by `func` is specified as `String` by the generic type argument? – Harry May 20 '23 at 05:08
  • It compiles fine for me as having a `String` result: `void use(Function super B, String> f) { String s = f.apply(null); }` – yshavit May 20 '23 at 05:33
  • @Harry no, it doesn't - `the `apply()` function of a `Function super B, String>` requires that you pass in a B (so, `new B()`, or `new SomeSubTypeOfB()`), and it returns a `String`. – rzwitserloot May 20 '23 at 05:40
  • @rzwitserloot yeah sorry didn't realize that I'd bumped a button in IntelliJ that changed the return type for some reason :/ – Harry May 20 '23 at 05:46