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.
A method with a varargs annotation produces a forwarder method with the same signature (args: Array[String])Unit as an existing method. And of course putting the annotation only on the bar in Baz means we can't use the forwarder from a Bar instance.
Varargs are useful for any method that needs to deal with an indeterminate number of objects. One good example is String. format . The format string can accept any number of parameters, so you need a mechanism to pass in any number of objects.
Rules to follow while using varargs in JavaWe can have only one variable argument per method. If you try to use more than one variable arguments a compile time error is generated.
Core Java bootcamp program with Hands on practiceYes, we can define a parameterized constructor in an abstract class.
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));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With