Catching ArrayStoreException at Compile-time - java

Consider the following test of Java's ArrayList#toArray method. Note that I borrowed the code from this helpful answer.
public class GenericTest {
public static void main(String [] args) {
ArrayList<Integer> foo = new ArrayList<Integer>();
foo.add(1);
foo.add(2);
foo.add(3);
foo.add(4);
foo.add(5);
Integer[] bar = foo.toArray(new Integer[10]);
System.out.println("bar.length: " + bar.length);
for(Integer b : bar) { System.out.println(b); }
String[] baz = foo.toArray(new String[10]); // ArrayStoreException
System.out.println("baz.length: " + baz.length);
}
}
But, notice that there will be a ArrayStoreException when trying to put an Integer into a String[].
output:
$>javac GenericTest.java && java -cp . GenericTest
bar.length: 10
1
2
3
4
5
null
null
null
null
null
Exception in thread "main" java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at java.util.ArrayList.toArray(Unknown Source)
at GenericTest.main(GenericTest.java:16)
Can this error be prevented through Java generics at compile-time?

ArrayStoreException exists precisely because Java's type system cannot handle this situation properly (IIRC, by the time Generics came along, it was too late to retrofit arrays in the same manner as the collections framework).
So you can't prevent this problem in general at compile time.
You can of course create internal APIs to wrap such operations, to reduce the likelihood of accidentally getting the types wrong.
See also:
Dealing with an ArrayStoreException
Why are arrays covariant but generics are invariant?

List#toArray(T[]) is a generic method declared as
<T> T[] toArray(T[] a);
So the type argument is either inferred from the type of the given array or with the <Type> notation prefixing the method invocation.
So you could do
String[] baz = foo.<Integer>toArray(new String[10]); // doesn't compile
But I think that's the best you could do.
But in that sense, you can clearly see that Integer doesn't match String (or vice-versa).
Note that this is a documented exception
ArrayStoreException - if the runtime type of the specified array is
not a supertype of the runtime type of every element in this list
So I don't think you should be trying to find it at compile time.

The method Collection.toArray cannot be changed for compatibility reasons.
However for your own code you can create a (more) type-safe helper method which protects you from the ArrayStoreException if you use your method consequently:
public static <T> T[] toArray(List<? extends T> list, T[] t) {
return list.toArray(t);
}
This method will reject String[] s=toArray(new ArrayList<Integer>(), new String[0]); which matches the example case of your question, but beware of the array subtyping rule: it will not reject
Object[] s=toArray(new ArrayList<Integer>(), new String[0]);
because of the pre-Generics “String[] is a subclass of Object[]” rule. This can’t be solved with the existing Java language.

The ArrayStoreException is runtime exception not compile time and thrown at run time and it indicates that different type of object is being stored in the array.
Object x[] = new String[3];
x[0] = new Integer(0);
The only way you can find it at compile time is by using <Integer> type as below
foo.<Integer>toArray(new String[10]);
The above will throw compile time error as
The parameterized method <Integer>toArray(Integer[]) of type List<Integer> is not applicable for the arguments (String[]).

Related

java generics wildcards casting issue

I have method as below
void meth(List<?> list) {
List<Integer> integers = (List<Integer>)list; //how do I make sure am casting correctly to List < Integer >? what if list passed as List< String > , then this will throw some RunTime Exceptions, how to avoid this?
}
In above snippet, for meth(), am not sure which type of Arraylist will be passed, it could be List or List etc, based on type of list type, I have to assign it to another list correctly, how can I achieve this?
Basically ... you can't. Since you could call meth (as you have written it) with a List<String> parameter, there can always be runtime exceptions.
Solutions:
Declare meth as public void meth(List<Integer> list) so that you can't call it like this meth(someStringList). That avoids the unchecked type cast and eliminates the possibility of a class cast exception.
Use list like this in meth.
void meth(List<?> list) {
for (Object o: list) {
Integer i = (Integer) o;
// ...
}
}
We can still get the class cast exception, but at least we get rid of the compiler error about unchecked type casts.
Use a #SuppressWarning annotation to suppress the compiler warning about the unchecked type cast. Once again, you could get the exceptions.
Unfortunately, given Java's generic type parameter erasure, there is no way that the body of meth (as written) can find out what kind of list it has been called with at runtime. And it won't work with a named type parameter either.
I verified that it does not throw exception. With type erasure, all generics are convert to Object. Generics are for compiler to enforce type during compile time.
static List<Integer> meth(List<?> list){
return (List<Integer>) list;
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("world");
strings.add("hello");
List<Integer> integers = meth(strings);
System.out.println(integers);
}
Console:
[world, hello]
You can try the code here: https://onlinegdb.com/z7DmGAJUI

Why do I get a ClassCastException using generics in this case?

This is the code: A simple ceneric class and trying to assign an integer to aa[0].
public class GenericTest<T> {
T [] aa = (T[]) new Object[2];
T bb;
public GenericTest(T x, T y) {
aa[0] = x; aa[1] = y;
System.out.println(aa[0] + " " + aa[1]); //OK
}
static public void main(String[] args) {
GenericTest<Integer> ll = new GenericTest<>(1,2);
ll.bb = 1; // OK
ll.aa[0] = 6; // ClassCastException from Object to Integer
}
}
In fact, the exception message is this:
java.lang.ClassCastException:
[Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
It is saying that it can't cast an Object[] to an Integer[].
The root cause of is the initializer in:
T [] aa = (T[]) new Object[2];
That typecast is an unsafe typecast. And indeed the compiler tells you that something is wrong:
$ javac GenericTest.java
Note: GenericTest.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Anyhow ... what is happening is that when you then do this:
ll.aa[0] = 6;
the JVM is trying to cast the ll.aa to an Integer[] ... because that is what the static typing says that it should be. But it isn't an Integer[]. It is an Object[]. Since Object[] is not assignment compatible with an Integer[] that gives you a class cast exception.
(Why is it doing a hidden type cast? Well this is how the JVM ensures runtime type safety in the face of possible unsafe casts and the like!)
How to fix it?
Avoid using T[]. Use List<T> instead.
Unfortunately, if you have to use T[] there is no easy fix. Basically arrays of a generic type parameter are difficult to create. You end up having to pass the Class object for the parameter's actual class as an extra parameter. Something like this:
import java.lang.reflect.Array;
public class GenericTest<T> {
T [] aa;
T bb;
public GenericTest(Class<T> cls, T x, T y) {sy
aa = (T[]) Array.newInstance(cls, 2);
aa[0] = x; aa[1] = y;
System.out.println(aa[0] + " " + aa[1]); //OK
}
static public void main(String[] args) {
GenericTest<Integer> ll = new GenericTest<>(Integer.class, 1, 2);
ll.bb = 1; // OK
ll.aa[0] = 6; // ClassCastException from Object to Integer
}
}
There is still a warning about an unsafe typecast ... but in this case it is safe to suppress the warning.
For Java 8 onwards, there is another solution which involves passing a reference to the array constructor for Integer[]; see Andy Turner's answer. This is cleaner than using reflection and calling Array.newInstance, but you still have to pass an extra parameter to the constructor.
This is what happens when you use generics. Because generics are erased at runtime, compiler still needs to somehow be safe (after erasure) that things work correctly. Let's simplify this:
GenericTest<Integer> ll = new GenericTest<>(1,2);
ll.bb = 1; // OK
System.out.println(ll.aa.getClass());
The last line is going to be translated to:
28: getfield #7 // Field aa:[Ljava/lang/Object;
31: checkcast #42 // class "[Ljava/lang/Integer;"
notice the checkcast. Since your T was resolved as Integer, means that the array must be Integer[] too; when in reality it is Object []. Compiler is trying to warn you btw when you do :
T [] aa = (T []) new Object[2];
because this is unsafe. In general, generic arrays are a major headache in java, imo.
This line:
ll.aa[0] = 6;
fails because the compiler has inserted some casts. Because it knows that ll is a GenericTest<Integer>, it expected aa to be an Integer[].
The compiler inserts a cast to this type before you can do anything with it: it is evaluated identically to:
((Integer[]) ll.aa)[0] = Integer.valueOf(6);
The problem is that ll.a isn't an Integer[], it's an Object[], so this cast fails.
These casts are inserted even when you're doing something that could be applied to "any array", or even "any object": for example, ll.a.toString() would have casts inserted, even though toString is available on all subclasses of Object, i.e. all objects.
T [] aa = (T[]) new Object[2];
As already pointed out by StephenC, this is an unchecked cast. Checked casts are instructions inserted into the bytecode, which statically check that the object is of a particular type. But, because T is a type variable, there is no "particular type" that the compiler can choose to insert a checkcast instruction on that assignment.
The correct way to deal with this is either:
Use a List<T> instead of a T[] as the field type. Generics and arrays don't play nicely together anyway, so you're better off sticking with generics.
You can use a fixed-length array, e.g. Arrays.asList(null, null), to mimick an array, insofar as you can only set the elements, not clear/add/remove etc.
Inject a T[] (or a Supplier<T[]>, or an IntFunction<T[]> etc) into the constructor of the GenericTest, in order that you push the burden of ensuring the correct type onto the caller:
public GenericTest(T x, T y, IntFunction<T[]> arrayFn) {
// ...
aa = arrayFn.apply(2); // Creates an array of the right length.
}
// Then...
GenericTest<Integer> ll = new GenericTest(1, 2, Integer[]::new);
ll.aa[0] = 6;
This will then be type-correct, because aa was created using Integer[]::new, i.e. it's an Integer[]. The casts are still inserted, they just pass the type check.
T [] aa = (T[]) new Object[2];
Because that line is broken. Your compiler warned you when you tried to compile it. As a general rule, if you get a compiler warning you do not understand, then do something else - java compiler warnings are best treated as: "Your code is completely broken and this is not going to work, but for the benefit of e.g. trying to start this application up because you want to test a completely different part of it, we'll just slap this broken stuff in a class file so you can move on, for now. Do not under any circumstances publish this to production until you fully understand this warning first."
In java, generics is a 100% compile-time show: Generics are compiler-checked documentation. At runtime, generics are mostly erased, and the few places where they remain, they are 'comments' as far as the runtime is concerned. The verifier and the runtime system never does any checks, ever. If javac allows it, the generics are now done being useful.
This in sharp contrast to arrays: Arrays are their own object and they DO know their 'component type'. Also, the compiler doesn't do all that much checking on them, but the runtime DOES. If you use some classfile hackery to put an Integer in a List<String>, the runtime will let you. However, you cannot use any amount of hackery to put an Integer in a String[]. It just won't work - you end up with an ArrayStoreException instead.
Because arrays actually know their type, at runtime, Integer[] and Object[] are different types (whereas at runtime, a List is just a List - the runtime doesn't know what generics are).
T is Integer, and that variable is of type T[], therefore, it is Integer[], therefore, any interactions with aa get you an implicit cast to Integer[]. This is 100% analogous to this code:
List<String> list = new ArrayList<String>();
List raw = list; // legal, but gets you a warning.
raw.add(5);
System.out.println(list.get(0));
You can compile it. If you run it, the last line throws ClassCastException which is weird, there is no cast there at all! Ah, but there is. Because the compiler rewrote that for you, into this code (and remember, the runtime does not know generics, which is why it is rewritten like this):
List list = new ArrayList();
List raw = list;
raw.add(Integer.valueOf(5));
String $v = (String) list.get(0);
System.out.println($v);
You'd think: Wait, why cast it? There is a System.out.println(Object) variant? But, that's just the rules. Java will immediately cast that because it can, after all, list is a List<String> which means its get(int idx) method can be assumed to always return strings, so java will cast it as soon as possible even if not needed.
The exact same thing happens in your code: It sees T[], where T is bound to Integer, so, aa is cast to Integer[] even if it wasn't needed. This cast fails at runtime because an object created as new Object[2] is clearly not an Integer[]. After all, I can invoke:
Integer[] x = ...;
System.out.println(x.getClass().getComponentType());
and this code is guaranteed to print java.lang.Integer. If I can write code so that java.lang.Object is printed, that would be broken, and yet that is exactly what would happen.
So how do I fix it?
Do not, ever, write T[]. It's as simple as that.
Arrays are low-level constructs you shouldn't be using, especially if the component type is non-primitive. If you MUST use them, then they should be relegated to internal implementation details, hidden behind a nicer API.
Do the casting in those wrappers.
This is precisely how ArrayList works. ArrayList has an array inside that holds your list elements (hence the name ArrayList). That is defined as Object[] storage = new Object[...];. Not T[] storage = (T[]) new Object[..]. Then, the get method of ArrayList does the casting:
public T get(int idx) {
// check if idx is within 0-size()...
return (T) storage[idx];
}
This cast does nothing (javac emits a warning that it has no idea what T might be and the runtime doesn't either, so nobody can check this), but this will all work at runtime (obviously: ArrayList works fine).
You need to do the same thing, and hide that array, if you must use it. Or better yet, don't do any of this stuff and use List<T> instead. Arrays of non-primitive types are a blight: If you use them, you better have an excellent reason for it, and hide it as much as possible.
This code works (printing the class inside the constructor....) and I don't know why
public class GenericTest<AnyType> {
public AnyType [] aa = (AnyType []) new Object[2];
public AnyType bb;
public GenericTest(AnyType x, AnyType y) {
aa[0]=x; aa[1]=y; System.out.println(aa.getClass());
System.out.println( aa[0]+" "+aa[1]); //OK
}
static public void main(String[] args) {
GenericTest<Integer> ll = new GenericTest<>(1,2);
ll.aa[0]= 6; // ClassCastException from Object to Integer
}
}

Why is the declaration of the array of generic type correct?

We cannot create the array of generic type, it's a well-known fact, so I'm not going to provide formal references to JLS here. But we can declare such arrays as follows:
static <E> void reduce() {
List<Integer>[] arr; //compiles fine
E[] avv; //compiles fine
avv = new E[10]; //doesn't compile
arr = new List<Integer>[10]; //doesn't compile
}
Anyone know the reason for such declarations?
First of all, I assume you meant
avv = new E[2]; //doesn't compile
arr = new List<Integer>[2]; //doesn't compile
in the last two lines of your method. You have to specify the size of the array when to create a new array. The code still doesn't compile, though.
Arrays predate generics. Arrays are present since the first version of Java, while generics were only added in version 1.5. To break no old code, the Java designers decided to erase generic types at runtime: at runtime, a type parameters are replaced by their upper bound. In your case, at runtime, E is the same as Object. It is not known which type E is really.
This is a problem, because, the array element type is not erased at runtime. Integer[] and String[] are different types, even at runtime. If you write new E[2], the Java runtime doesn't know what kind of array it must create. It could be String[] or Integer[], or any other array type. Therefore, you cannot create new arrays with generic elements.
Arrays with generic elements are still allowed as types, mainly in order to use them in method parameters:
<E> E doSomething(E[] param) { ... }
The actual array is created in another part of the program, where its type is known. You could call this method with
String result = doSomething(new String[2]);
for example.
It allows you to pass arrays of generic types as method parameters.
For example:
public class Foo<T>
{
public void bar (T[] arr) {}
}
...
Foo<String> foo = new Foo<String>();
String[] arr = {"aa","bb");
foo.bar (arr);
If T[] wasn't allowed, the method signature would have to be public void bar (Object[] arr), and the compiler would let you pass any type of array to that method.

Quick java generics question

I don't think I really understand Java generics. What's the difference between these two methods? And why does the second not compile, with the error shown below.
Thanks
static List<Integer> add2 (List<Integer> lst) throws Exception {
List<Integer> res = lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
.
static <T extends List<Integer>> T add2 (T lst) throws Exception {
T res = lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - incompatible types
required: T
found: capture#1 of ? extends java.util.List
For the second method to compile, you have to cast the result of newInstace() to T:
static <T extends List<Integer>> T add2 (T lst) throws Exception {
T res = (T) lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
Regarding the difference between the two methods, let's forget about the implementation, and consider only the signature.
After the code is compiled, both methods will have exactly the same signature (so the compiler would give an error if the have the same name). This happens because of what is called type erasure.
In Java, all the type parameters disappear after compilation. They are replaced by the most generic possible raw type. In this case, both methods will be compiled as List add2(List).
Now, this will show the difference between the two methods:
class Main {
static <T extends List<Integer>> T add1(T lst) { ... }
static List<Integer> add2(List<Integer> lst) { ... }
public static void main(String[] args) {
ArrayList<Integer> l = new ArrayList<Integer>();
ArrayList<Integer> l1 = add1(l);
ArrayList<Integer> l2 = add2(l); // ERROR!
}
}
The line marked as // ERROR! won't compile.
In the first method, add1, the compiler knows that it can assign the result to a variable of type ArrayList<Integer>, because the signature states that the return type of the method is exactly the same as that of the parameter. Since the parameter is of type ArrayList<Integer>, the compiler will infer T to be ArrayList<Integer>, which will allow you to assign the result to an ArrayList<Integer>.
In the second method, all the compiler knows is that it will return an instance of List<Integer>. It cannot be sure that it will be an ArrayList<Integer>, so you have to make an explicit cast, ArrayList<Integer> l2 = (ArrayList<Integer>) add2(l);. Note that this won't solve the problem: you are simply telling the compiler to stop whining and compile the code. You will still get an warning (unchecked cast), which can be silenced by annotating the method with #SuppressWarnings("unchecked"). Now the compiler will be quiet, but you might still get a ClassCastException at runtime!
The first one is specified to accept a List<Integer> and return a List<Integer>. List being an interface, the implication is that an instance of some concrete class that implements List is being passed as a parameter and an instance of some other concrete class that implements List is returned as a result, without any further relationship between these two classes other than that they both implement List.
The second one tightens that up: it is specified to accept some class that implements List<Integer> as a parameter, and return an instance of exactly that same class or a descendant class as the result.
So for example you could call the second one like so:
ArrayList list; // initialization etc not shown
ArrayList result = x.add2(list);
but not the first, unless you added a typecast.
What use that is is another question. ;-)
#Bruno Reis has explained the compile error.
And why does the second not compile, with the error shown below.
The error shown is actually reporting that you have tried to run code that failed to compile. It is a better idea to configure your IDE to not run code with compilation errors. Or if you insist on letting that happen, at least report the actual compilation error together with the line number, etc.
"I don't think I really understand Java generics."
Nobody does...
The issue is related to the interesting return type of getClass(). See its javadoc. And this recent thread.
In both of your examples, lst.getClass() returns Class<? extends List>, consequently, newInstance() returns ? extends List - or more formally, a new type parameter W introduced by javac where W extends List
In your first example, we need to assign W to List<Integer>. This is allowed by assignment conversion. First, W can be converted to List because List is a super type of W. Then since List is raw type, the optional unchecked conversion is allowed, which converts List to List<Integer>, with a mandatory compiler warning.
In the 2nd example, we need to assign W to T. We are out of luck here, there's no path to convert from W to T. It makes sense because as far as javac knows at this point, W and T could be two unrelated subclass of List.
Of course, we know W is T, the assignment would have been safe if allowed. The root problem here, is that getClass() loses type information. If x.getClass() returns Class<? extends X> without erasure, both of your examples will compile without even warning. They indeed are type safe.
Generics are a way to guarantee type safety.
Eg:
int[] arr = new int[4];
arr[0] = 4; //ok
arr[1] = 5; //ok
arr[2] = 9; //ok
arr[3] = "Hello world"; // you will get an exception saying incompatible
types.
By default arrays in Java are typeSafe. An integer array is only meant to
contain integer and nothing else.
Now:
ArrayList arr2 =new ArrayList();
arr2.add(4); //ok
arr2.add(5); //ok
arr2.(9); //ok
int a = arr2.get(0);
int b = arr2.get(1);
int c = arr3.get(2);
You willa gain get an exception like what it is not possible to cast Object
instance to integer.
The reason is that ArrayList stores object and not primitive like the
above array.
The correct way would be to explicitly cast to an integer.You have to do this
because type safety is not yet guaranteed.
eg:
int a = (int)arr2.get(0);
To employ type safety for collections, you simply specify the type of objects that your collection contains.
eg:
ArrayList<Integer> a = new ArrayList<Integer>();
After insertion into the data structure, you can simply retrieve it like you
would do with an array.
eg:
int a = arr2.get(0);

What's the reason I can't create generic array types in Java?

What's the reason why Java doesn't allow us to do
private T[] elements = new T[initialCapacity];
I could understand .NET didn't allow us to do that, as in .NET you have value types that at run-time can have different sizes, but in Java all kinds of T will be object references, thus having the same size (correct me if I'm wrong).
What is the reason?
It's because Java's arrays (unlike generics) contain, at runtime, information about its component type. So you must know the component type when you create the array. Since you don't know what T is at runtime, you can't create the array.
Quote:
Arrays of generic types are not
allowed because they're not sound. The
problem is due to the interaction of
Java arrays, which are not statically
sound but are dynamically checked,
with generics, which are statically
sound and not dynamically checked.
Here is how you could exploit the
loophole:
class Box<T> {
final T x;
Box(T x) {
this.x = x;
}
}
class Loophole {
public static void main(String[] args) {
Box<String>[] bsa = new Box<String>[3];
Object[] oa = bsa;
oa[0] = new Box<Integer>(3); // error not caught by array store check
String s = bsa[0].x; // BOOM!
}
}
We had proposed to resolve this
problem using statically safe arrays
(aka Variance) bute that was rejected
for Tiger.
-- gafter
(I believe it is Neal Gafter, but am not sure)
See it in context here: http://forums.sun.com/thread.jspa?threadID=457033&forumID=316
By failing to provide a decent solution, you just end up with something worse IMHO.
The common work around is as follows.
T[] ts = new T[n];
is replaced with (assuming T extends Object and not another class)
T[] ts = (T[]) new Object[n];
I prefer the first example, however more academic types seem to prefer the second, or just prefer not to think about it.
Most of the examples of why you can't just use an Object[] equally apply to List or Collection (which are supported), so I see them as very poor arguments.
Note: this is one of the reasons the Collections library itself doesn't compile without warnings. If this use-case cannot be supported without warnings, something is fundamentally broken with the generics model IMHO.
The reason this is impossible is that Java implements its Generics purely on the compiler level, and there is only one class file generated for each class.
This is called Type Erasure.
At runtime, the compiled class needs to handle all of its uses with the same bytecode. So, new T[capacity] would have absolutely no idea what type needs to be instantiated.
The answer was already given but if you already have an Instance of T then you can do this:
T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);
Hope, I could Help,
Ferdi265
The main reason is due to the fact that arrays in Java are covariant.
There's a good overview here.
I like the answer indirectly given
by Gafter. However, I propose it is wrong. I changed Gafter's code a little. It compiles and it runs for a while then it bombs where Gafter predicted it would
class Box<T> {
final T x;
Box(T x) {
this.x = x;
}
}
class Loophole {
public static <T> T[] array(final T... values) {
return (values);
}
public static void main(String[] args) {
Box<String> a = new Box("Hello");
Box<String> b = new Box("World");
Box<String> c = new Box("!!!!!!!!!!!");
Box<String>[] bsa = array(a, b, c);
System.out.println("I created an array of generics.");
Object[] oa = bsa;
oa[0] = new Box<Integer>(3);
System.out.println("error not caught by array store check");
try {
String s = bsa[0].x;
} catch (ClassCastException cause) {
System.out.println("BOOM!");
cause.printStackTrace();
}
}
}
The output is
I created an array of generics.
error not caught by array store check
BOOM!
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at Loophole.main(Box.java:26)
So it appears to me you can create generic array types in java. Did I misunderstand the question?
From Oracle tutorial:
You cannot create arrays of parameterized types. For example, the following code does not compile:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
The following code illustrates what happens when different types are inserted into an array:
Object[] strings = new String[2];
strings[0] = "hi"; // OK
strings[1] = 100; // An ArrayStoreException is thrown.
If you try the same thing with a generic list, there would be a problem:
Object[] stringLists = new List<String>[]; // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>(); // OK
stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown,
// but the runtime can't detect it.
If arrays of parameterized lists were allowed, the previous code would fail to throw the desired ArrayStoreException.
To me, it sounds very weak. I think that anybody with a sufficient understanding of generics, would be perfectly fine, and even expect, that the ArrayStoredException is not thrown in such case.
In my case, I simply wanted an array of stacks, something like this:
Stack<SomeType>[] stacks = new Stack<SomeType>[2];
Since this was not possible, I used the following as a workaround:
Created a non-generic wrapper class around Stack (say MyStack)
MyStack[] stacks = new MyStack[2] worked perfectly well
Ugly, but Java is happy.
Note: as mentioned by BrainSlugs83 in the comment to the question, it is totally possible to have arrays of generics in .NET
class can declare an array of type T[], but it cannot directly instantiate such an array. Instead, a common approach is to instantiate an array of type Object[], and then make a narrowing cast to type T[], as shown in the following:
public class Portfolio<T> {
T[] data;
public Portfolio(int capacity) {
data = new T[capacity]; // illegal; compiler error
data = (T[]) new Object[capacity]; // legal, but compiler warning
}
public T get(int index) { return data[index]; }
public void set(int index, T element) { data[index] = element; }
}
It is because generics were added on to java after they made it, so its kinda clunky because the original makers of java thought that when making an array the type would be specified in the making of it. So that does not work with generics so you have to do
E[] array=(E[]) new Object[15];
This compiles but it gives a warning.
There surely must be a good way around it (maybe using reflection), because it seems to me that that's exactly what ArrayList.toArray(T[] a) does. I quote:
public <T> T[] toArray(T[] a)
Returns an array containing all of the
elements in this list in the correct order; the runtime type of the
returned array is that of the specified array. If the list fits in the
specified array, it is returned therein. Otherwise, a new array is
allocated with the runtime type of the specified array and the size of
this list.
So one way around it would be to use this function i.e. create an ArrayList of the objects you want in the array, then use toArray(T[] a) to create the actual array. It wouldn't be speedy, but you didn't mention your requirements.
So does anyone know how toArray(T[] a) is implemented?
If we cannot instantiate generic arrays, why does the language have generic array types? What's the point of having a type without objects?
The only reason I can think of, is varargs - foo(T...). Otherwise they could have completely scrubbed generic array types. (Well, they didn't really have to use array for varargs, since varargs didn't exist before 1.5. That's probably another mistake.)
So it is a lie, you can instantiate generic arrays, through varargs!
Of course, the problems with generic arrays are still real, e.g.
static <T> T[] foo(T... args){
return args;
}
static <T> T[] foo2(T a1, T a2){
return foo(a1, a2);
}
public static void main(String[] args){
String[] x2 = foo2("a", "b"); // heap pollution!
}
We can use this example to actually demonstrate the danger of generic array.
On the other hand, we've been using generic varargs for a decade, and the sky is not falling yet. So we can argue that the problems are being exaggerated; it is not a big deal. If explicit generic array creation is allowed, we'll have bugs here and there; but we've been used to the problems of erasure, and we can live with it.
And we can point to foo2 to refute the claim that the spec keeps us from the problems that they claim to keep us from. If Sun had more time and resources for 1.5, I believe they could have reached a more satisfying resolution.
As others already mentioned, you can of course create via some tricks.
But it's not recommended.
Because the type erasure and more importantly the covariance in array which just allows a subtype array can be assigned to a supertype array, which forces you to use explicit type cast when trying to get the value back causing run-time ClassCastException which is one of the main objectives that generics try to eliminate: Stronger type checks at compile time.
Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException
A more direct example can found in Effective Java: Item 25.
covariance: an array of type S[] is a subtype of T[] if S is a subtype of T
T vals[]; // OK
But, you cannot instantiate an array of T
// vals = new T[10]; // can't create an array of T
The reason you can’t create an array of T is that there is no way for the
compiler to know what type of array to actually create.
Try this:
List<?>[] arrayOfLists = new List<?>[4];

Categories