Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Java generics, how to work around type erasure?
This code doesn't compile:
package tryNcry;
public class ExampleClass<S extends SomeClass> {
S[] createArray(int n) {
return new S[n]; // obvious compile time ERROR here:
// Cannot create a generic array of S
}
}
The reason for this is obvious: due to type erasure, at runtime there is no way for the JRE to know what S would have been, and so no way to create an array for type S.
Everything would be fine (if tedious) if there was any instance of S, using reflection.
How can I solve this?
1 answer
I'm afraid there's no straighforward way to do that. Due to type erasure, we don't have enough information to create the array directly. Therefore, this information must be provided somehow.
Alternative 1: Class object informed in the constructor
You could force all instances of ExampleClass to be created with a Class<S> object:
import java.lang.reflect.Array;
public class ExampleClass<S extends SomeClass> {
private Class<S> arrayClass;
public ExampleClass(Class<S> arrayClass) {
this.arrayClass = arrayClass;
}
public S[] createArray(int n) {
return (S[]) Array.newInstance(arrayClass, n);
}
}
With a Class instance we can use java.lang.reflect.Array to create an array of the desired type and length.
I admit it might be annoying to provide a Class object for every new instance of ExampleClass. But now it works:
ExampleClass<SomeSubClass> creator = new ExampleClass<>(SomeSubClass.class);
SomeSubClass[] array = creator.createArray(10);
Assuming, of course, that SomeSubClass is a subclass of SomeClass.
But what if SomeSubClass is also generic?
Let's say SomeSubClass also has a generic type:
// parent class, not generic
class SomeClass {
}
// subclass, generic
class SomeSubClass<T> extends SomeClass {
}
And I want to create an array of SomeSubClass<String>. Unfortunately, passing just SomeSubClass.class doesn't compile. Trying something like SomeSubClass<String>.class doesn't compile either. And a cast such as (Class<SomeSubClass<String>>) SomeSubClass.class also doesn't compile. So you need to do this trick:
ExampleClass<SomeSubClass<String>> creator =
new ExampleClass<>((Class<SomeSubClass<String>>) (Class<?>) SomeSubClass.class);
Yes, casting to Class<?> and then casting it again to Class<SomeSubClass<String>> is ugly, but it works: now createArray will return an array of SomeSubClass<String>.
If you think it's too confusing, you could create a helper method to do the cast:
public class ClassUtils {
public static <T> Class<T> cast(Class<?> c) {
return (Class<T>) c;
}
}
And then use it like this:
ExampleClass<SomeSubClass<String>> creator =
new ExampleClass<>(ClassUtils.<SomeSubClass<String>>cast(SomeSubClass.class));
Not sure which one is uglier, but anyway.
Alternative 2: varargs
Another way to solve it is to change the method to receive a varargs argument of the desired type:
import java.util.Arrays;
public class ExampleClass<S extends SomeClass> {
public S[] createArray(int length, S... elements) {
return Arrays.copyOf(elements, length);
}
}
This solution provides information about the type in the varargs elements, so that Arrays.copyOf can create an array of the correct type.
With this, you don't need to provide a Class object anymore, which makes it less cumbersome, specially if the type is also generic (no need to do the ugly cast trick):
// No need to provide a Class object
// It works even if SomeSubClass is a generic type
ExampleClass<SomeSubClass<String>> ec = new ExampleClass<>();
SomeSubClass<String>[] array = ec.createArray(5);
Note that the elements argument is optional, so passing just the size will set all elements to null, just like it happens when you create a new array with new Type[size].
As a bonus, you can also initialize some of (or all) the elements of the array:
// first and seconds elements will be set to instance1 and instance2, respectively
// the other elements will be null
SomeSubClass<String>[] array = ec.createArray(5, instance1, instance2);

0 comment threads