Issue with Java varargs and generics in abstract classes
generics, java, variadic-functions
Solution
Varargs parameters are first and foremost arrays. So without the syntactic sugar, your code would look like the following:
OUT fn2(IN in1, IN in2) {
return fnN(new IN[] {in1, in2});
}
abstract OUT fnN(IN[] ins);
Except `new IN[]` would not be legal because arrays of type parameters cannot be instantiated, due to type erasure. An array needs to know its component type, but `IN` has been erased to its upper bound, `Object`, at runtime.
The varargs invocation hides this issue unfortunately, and at runtime you have the equivalent of `fnN(new Object[] {in1, in2})`, whereas `fnN` has been overriden to take a `Number[]`.
However, for a non-abstract FooGen, it works fine
This is because by instantiating `FooGen` directly, you haven't overridden `fnN`. Thus it accepts an `Object[]` at runtime and no `ClassCastException` occurs.
For example, this will fail even if `FooGen` isn't `abstract`:
FooGen<Number, Number> foogen = new FooGen<Number, Number>() {
@Override
Number fnN(Number... gs) {
return super.fnN(gs);
}
};
System.out.println(foogen.fn2(1.2, 3.4));
So you can see that it really isn't related to the abstractness of `FooGen`, but to whether `fnN` gets overridden with a narrowed argument type.
SOLUTION
There are no easy workarounds. One idea is to have `fnN` take a `List<? extends IN>` instead:
OUT fn2(IN in1, IN in2) {
//safe because the array won't be exposed outside the list
@SuppressWarnings("unchecked")
final List<IN> ins = Arrays.asList(in1, in2);
return fnN(ins);
}
abstract OUT fnN(List<? extends IN> ins);
If you wanted to keep the varargs support, you could treat this method as an implementation detail and delegate to it:
abstract OUT fnNImpl(List<? extends IN> ins);
public final OUT fnN(IN... ins) {
return fnNImpl(Arrays.asList(ins));
}
Problem
I'm playing with some functional like programming. And having issues with some pretty deeply nested generics. Here's my SCCE that fails, with an abstract class involved: ``` public abstract class FooGen<IN, OUT> { OUT fn2(IN in1, IN in2) { // clever? try at a lazy way, just call the varargs version return fnN(in1, in2); } abstract OUT fnN(IN...ins); // subclasses implement this public static void main(String[] args) { FooGen<Number, Number> foogen = new FooGen<Number, Number>() { @Override Number fnN(Number... numbers) { return numbers[0]; } }; System.out.println(foogen.fn2(1.2, 3.4)); } } ``` This dies with a ``` java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Number; ``` However, for a non-abstract FooGen, it works fine: ``` public class FooGen<IN, OUT> { OUT fn2(IN g1, IN g2) { return fnN(g1, g2); } OUT fnN(IN...gs) { return (OUT)gs[0]; } public static void main(String[] args) { FooGen<Number,Number> foogen = new FooGen<Number,Number>(); System.out.println(foogen.fn2(1.2, 3.4)); } } ``` This prints 1.2. Ideas? It seems like somewhere Java has lost track of the generics. This is pushing the limits of my generics knowledge. :-) (Added in response to answers) First, thanks for the upvotes, and to Paul and Daemon for their helpful answers. Still wondering why it works as Numbers in the 2nd version, I had an insight. As a Thought Experiment, let's add a `.doubleValue()` somewhere. You can't. In the code itself the variables are INs, not Numbers. And in the `main()` it's merely declaring the type, `FooGen<Number,Number>` but there's no place there to add code. In Version #2, it really isn't "working" as Numbers. Internally, with erasure, everything is Objects, as explained by Paul and Daemon, and, looking back sheepishly, well understood by myself. Basically, in this complex example, I got overexcited and mislead by the `<Number>` declaration. Don't think I'll bother with a workaround. The whole idea was to be lazy. :-) For efficiency I created parallel interfaces and code that take primitive doubles (and ints), and there this trick works just fine.