Arrays of generic types are often hard to create and handle. I’ll show you how to create an array of a class with a generic parameter.
The Generic Type
First we need some class that has a generic type parameter:
public class Generic<T> {
final T foo;
public Generic(T foo) { this.foo = foo; }
@Override
public String toString() {
return String.valueOf(foo);
}
}
Instances of Generic
Then we need some instances that we can put into an Array:
final Generic<String> hello = new Generic<>("Hello");
final Generic<String> world = new Generic<>("World");
Creation of the Arrays
<?> instead of <String>:
That’s easy, but we won’t know the type. You can also remove <?> and just use the raw type.
Generic<?>[] a = { hello, world };
Raw and unchecked
You can cast a raw array, but this is an unchecked cast.
@SuppressWarnings("unchecked")
Generic<String>[] a = new Generic[] { hello, world };
Arrays.copyOf(…)
Creates a copy, length must be specified, unchecked.
@SuppressWarnings("unchecked")
Generic<String>[] a = Arrays.copyOf(new Object[] { hello, world }, 2, Generic[].class);
Array.newInstance(…)
Can’t be initialized by a list, length must be specified, unchecked.
@SuppressWarnings("unchecked")
Generic<String>[] a = (Generic<String>[]) Array.newInstance(Generic.class, 2);
a[0] = hello;
a[1] = world;
Arrays.asList(…).toArray()
Allocates a new array, unchecked. According to the API documentation you can’t be sure that the returned array is of type Generic[]
, it could be an Object[]
.
@SuppressWarnings("unchecked")
Generic<String>[] a = (Generic[]) Arrays.asList(hello, world).toArray();
Arrays.asList(…).toArray(new Generic[#])
You must give the size or it will create another array.
// Uses given array:
Generic<String>[] a = Arrays.asList(hello, world).toArray(new Generic[2]); // Creates an new array of size 2:
Generic<String>[] a = Arrays.asList(hello, world).toArray(new Generic[0]);
asArray(…)
Method must be defined an called.
// This is inside some class called "SomeClass"
/** @see Arrays#asList(Object...) */
@SafeVarargs
static <T> T[] asArray(T... elements) { return elements; }
// Type defined by parameters and by type of variable (implicit):
Generic<String>[] a = SomeClass.asArray(hello, world);
// With explicit type declaration:
Generic<String>[] a2 = SomeClass.<Generic<String>>asArray(hello, world);
Arrays.asList(…)
This is not an Array, but Collection are in many cases much better than Arrays. And it is actually backed by an array, so performance is nearly as good. I just put this here because this really is the best way to do it.
List<Generic<String>> a = Arrays.asList(hello, world);
Why unchecked is a Problem
Due to Type Erasure you will have a hard time debugging your code if anything goes wrong. Note that "Hello"
is a String
but "World"
is a StringBuilder
.
final Generic<CharSequence> hello = new Generic<>("Hello");
final Generic<CharSequence> world = new Generic<>(new StringBuilder("World"));
@SuppressWarnings("unchecked")
Generic<String>[] a = new Generic[] { hello, world };
System.out.println(Arrays.toString(a)); // This is ok.
Generic<String> a1 = a[1]; // Still ok.
System.out.println(a1); // Even this works.
System.out.println(a1.foo.toUpperCase()); // ClassCastException!!
PS: Type safe arrays
In Java an array isn’t really type safe. For some reason FindBugs and CheckStyle both have no check for that (or I just couldn’t find it). I wrote a feature request which explains the problem:
FindBugs – Feature Request: Type Safe Arrays
It seems the newer SpotBugs (successor of FindBugs) has some detectors that might have been inspired by my feature request:
https://spotbugs.readthedocs.io/en/stable/detectors.html#covariantarrayassignment