I have this code
ArrayList<Integer>arr = new ArrayList<>();
arr.add(1);
arr.add(2);
List l = arr;
l.add("12");// should't this throw an runtime exception? Point1
l.add("123");
System.out.println(l.size());
ArrayList<String>arr1 =(ArrayList<String>) l;// should't this throw an runtime execptions? Point2
arr1.add("12"); //Point 3
System.out.println(arr1.size());
I was experimenting with generics code, and I am surprised to see some of the results. I have this specific questions.
I have a arraylist of integer. I assign it to a list l, which does not have any generic type. I then add a string to that list. Shouldn't this throw a runtime exception? l list is still a arraylist of integer?
I then cast l to arraylist of string? Shouldn't this also throw a runtime exception? Ain't I effectively casting arraylist of integer to ararylist of string?
And in this case point 3, I am adding a string to arr1, even though it is supposed to be arraylist of string?
I feel like all of the three questioned are related? can anyone explain to me what I am doing wrong?
Generics are enforced at compile time so that the compiler can do type checking. However, through type erasure, the information about the type is actually not used at runtime. Rather, collections all just contain Object. I believe this was originally done to retain bytecode compatibility with previous java versions which didn't have generics support.
You should, however, get some warnings about using raw types.
Generics are erased at runtime, therefore it is possible but highly frowned upon to get any types in a List through casting. This is possible because the compiled code sees it as just a List, no generics. This can cause many problems if you pass the list to any methods, and you will get a runtime error. Generally, you should get an 'unchecked' warning with things like this.
Point 1 : A raw List can contain any Object, if you want to enforce typechecking (at compile time) for the content of the List, you need to specify its content type. When you assign arr to l, you lose the ability to enforce typechecking, no Exception is thus thrown and it's what is expected.
Point 2 : casting a List to an ArrayList "might be possible" (that's why there is no compilation error) as ArrayList implements List. However, as stated in point 1, you lost the ability to enforce typechecking at compile time (assigning directly arr to arr1 won't generate a compile error) that's why you have no ClassCastException. Note that you could even assign a new LinkedList()to l then assign l to arr1and it would have compiled (but it would have raised a class cast exception at runtime as in this case the cast is detected as being impossible at runtime)
Point 3 : it is an ArrayList of Strings, you add a String, it's typesafe.
Related
This question already has answers here:
What is a raw type and why shouldn't we use it?
(16 answers)
Closed 3 years ago.
Java does not allow creating generic arrays directly. I understand that due to erasure generic type is not known at runtime, whereas array require type checking at runtime and hence the two are incompatible.
This piece of code does not compile -
Holder<Integer>[] integers = new Holder<Integer>[5];
Fine, but I am unsure why does this piece of code actually compile (with warning for unsafe type conversions)?
Holder<Integer>[] holders = new Holder[5];
holders[0] = new Holder<Integer>(5);
holders[1] = new Holder<Integer>(5);
holders[2] = new Holder<Integer>(5);
I don't exactly understand what did I actually trick the compiler into by removing diamond brackets. Is this an acceptable to create generic arrays?
Further, when I add this line to the code - holders[3] = new Holder<String>("Hello");
It throws a compile error Holder<String> can not be converted to Holder<Integer>
I find this strange because as far as I understand the whole idea of not allowing generic arrays was because arrays could not differentiate between 2 different generic types because of the type erasure. But in this example the compiler can detect incorrect type conversions.
What am I missing here?
On this page, you can see exactly why creating arrays of generic types is not allowed:
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.
stringLists should only be able to store List<String>, but by using the above code, I can not only trick the compiler, but also the runtime, into allowing me to store a ArrayList<Integer> into stringLists, due to type erasure.
but I am unsure why does this piece of code actually compile
Well, because Holder is a raw type. See What is a raw type and why shouldn't we use it?. It's perfectly fine, as far as the compiler and runtime is concerned, to create an array of a raw type, because here you are not saying that "this array can only store Holder<Integer>", you are just saying "this array can only store Holder (of anything)".
Is this an acceptable to create generic arrays?
Well, your array is technically not generic. I can assign it to a Holder[] and assign a Holder<Foo> to one of its elements, and no exception or compiler errors will occur. As far as the compiler is concerned, this is "acceptable", but because you lose type-safety, I don't recommend you use it. You should use something like ArrayList<Holder<Integer>> instead.
I find this strange because as far as I understand the whole idea of not allowing generic arrays was because arrays could not differentiate between 2 different generic types because of the type erasure. But in this example the compiler can detect incorrect type conversions.
The compiler can detect it not because the array doesn't allow you to put in Holder<String>, but because the variable's compile time type is Holder<Integer>[]. The compiler can still check the type by looking at the compile time types, but as soon as you lose the compile time type (assigning it to a variable of type Object[] or Holder[]), then it can't do it for you. The array itself allows any kind of Holder in the first place anyway, because it is a Holder[].
I'm having trouble figuring out what type parameter is expected at RHS of the following
ArrayList<Pair<ParseNode,ParseNode>>[] nodes = new ArrayList[indexes.length];
Why a copy of <Pair<ParseNode,ParseNode>> is not legitimate?
Arrays of concrete paramaterized types are inherently broken. Remember arrays are covariant and the array type check is a runtime operation. At runtime all the generics have been type erased, so the Array Store check can't tell <Pair<ParseNode, ParseNode>> from <Pair<BigInteger,IOException>>.
The fundamental contract of a generic is "I, the compiler, promise that if you write code that generates no warnings, you will never get a class cast exception at runtime."
Neither can the compiler guarantee to you that it will be able to give you a compile time error if something that is not an ArrayList<Pair<ParseNode,ParseNode>> is put into that array. Nor can the runtime system guarantee you will get an ArrayStoreException (like the Language Specification says it will) if you add the wrong type, rather than a ClassCastException later when you take it back out. (The second part is really why it's actually illegal rather than just a warning, it would result in an array that doesn't obey the language specification.)
So it doesn't let you declare them that way and forces you to acknowledge the 'unsafe' warning. That way it has said "I told you I can't guarantee there will not be any class cast exceptions as a result of using this array, it's on you to make sure you only put the right things in here."
Java not supports generic arrays. Arrays are covariant, generics are not. This means that if class A extends class B then A[] is also B[]. And code
A[] a = new A[10];
B[] b = a;
is legal.
But it is not same for generics. You could not assign Foo<T> to Foo<X> even if T extends X. And so elements of Foo<T>[] can't be guaranteed type safe.
EDIT
Excuse me for just linking out, but I've found Java theory and practice: Generics gotchas article, that explains everything about arrays covariance better than I even would dream.
Don't use an array. Use another ArrayList.
ArrayList<List<Pair<ParseNode,ParseNode>>> listOfLists = new ArrayList<List<Pair<ParseNode,ParseNode>>>();
listOfLists.add(new ArrayList<<Pair<ParseNode,ParseNode>>());
I have the next code:
ArrayList value = new ArrayList<Integer>(); // 1
value.add("Test"); // 2
I'm trying to understand line 2. Although I can see that value.add("Test"); compiles without errors, I can't see the reason it doesn't throw a runtime exception. If value is referencing a generic ArrayList object, why Java allows to add a String to it? Can anyone explain it to me?
The closest explanation I've found about this is described here, but I still don't understand the core reason:
Stack s = new Stack<Integer>()
This is a legal conversion from a parameterized type to a raw type. You will be able to push value of any type. However, any such operation will result in an "unchecked call" warning.
Generic types are erased during compilation. So at runtime, an ArrayList is a raw ArrayList, no matter if you defined it as generic or not.
In your case, the code compiles as your ArrayList declaration is not generic, and it runs fine because of type erasure.
ArrayList value this is your type declaration which is not generic. That is why compiler allows you to add any Object to the list.
I want to create an array of ArrayLists, similar to that in this thread: How to do an array of hashmaps?. However, Java gives the warning
"Cannot create a generic array of ArrayList<String>"
when I try to do the following
ArrayList[] arrayOfLists = new ArrayList[size];
I have sort of understood the problems and the workarounds provided.
I have my own approach which unfortunately does not fix the problem either.
I tried creating a list of ArrayLists and then used toArray().
ArrayList<ArrayList<String>> listOfLists = new ArrayList<ArrayList<String>>();
ArrayList<String>[] arrayOfLists = (ArrayList<String>[])listOfLists.toArray();
Worked fine, but got the warning :
Type safety: Unchecked cast from Object[] to ArrayList<String>[]
When I tried to check for type safety, using
if(listOfLists.toArray() instanceof ArrayList<String>[])
I get the error:
Cannot perform instanceof check against parameterized type ArrayList<String>[]. Use the form ArrayList<?>[] instead since further generic type information will be erased at runtime
Why cant I use this method? Why does toArray() return Object[] instead of ArrayList<String> since the instance was initialised with theArrayList<String>; type?
Any other workarounds/suggestions on how I can get this done? A 2D array will not work since different lists can vary greatly in size.
The currently accepted answer has a major error in describing Java's generics, so I felt I should answer to make sure there aren't any misconceptions.
Generics in Java are an entirely compile-time feature and for the most part don't exist at runtime due to erasure (you can get the runtime to cough up generic type information in some cases, but that's far from the general case). This provides the basis for the answers to your questions.
Why cant I use this method?
Because generics are erased, an ArrayList<String>[] (as well as all other parameterized ArrayList<>[] instances) at runtime is really an ArrayList[]. Thus, it is impossible for the runtime to check if something is instanceof ArrayList<String>[], as the runtime doesn't actually know that String is your type parameter -- it just sees ArrayList[].
Why does toArray() return Object[] instead of ArrayList since the instance was initialised with theArrayList; type?
Again, erasure. The type parameter is erased to Object, so at runtime what you effectively have is an ArrayList<Object>. Because of this erasure, the runtime doesn't have the information necessary to return an array of the proper type; it only knows that the ArrayList holds Objects, so it returns an Object[]. This is why the toArray(T[]) overload exists -- arrays retain their type information, so an array could be used to provide the requisite type information to return an array of the right type.
Any other workarounds/suggestions on how I can get this done?
As you can see, mixing generic stuff and arrays doesn't work too well, so ideally, you wouldn't mix Lists and arrays together. Therefore, if possible, you should use List<List<String>> or something of the sort instead of List<String>[]. If you want to keep a ArrayList<String>[], though, you could do this:
#SuppressWarnings("unchecked")
ArrayList<String>[] array = new ArrayList[size];
You'll still get the unchecked type warning, but you can be reasonably sure that you won't encounter heap pollution as the only reference to the object is through array. You can also use this as the parameter to toArray():
#SuppressWarnings("unchecked")
ArrayList<String>[] temp = new ArrayList[0];
ArrayList<String>[] arrayOfLists = listOfLists.toArray(temp);
or
#SuppressWarnings("unchecked")
ArrayList<String>[] arrayOfLists = listOfLists.toArray((ArrayList<String>[]) new ArrayList[0]);
For more reading on why you can't parameterize an array, see this SO question. In short, such a thing isn't safe because arrays are covariant, while generics are invariant.
The problem is that Generics are created during runtime, but type conversions and array sizes must be checkable at compile time. The compiler cannot tell what class ArrayList<String> will be during compile time (as it will be generated later), it can only tell that it will be at least an Object, because every class in Java is at least an Object. You can do type conversion and suppress the warning and it might even work, but you run into a pitfall to accidentally confuse types somewhere and mess up your code.
Java is a type-safe language by choice to prevent you from doing one of the most recurring mistakes programmers do in their daily work: confusing variable types. So while it is possible to do the type conversion, you - as an upcoming good Java programmer - should not do that. Use the ArrayList<ArrayList<String>> if you need such a construct, and use arrays only when they are necessary.
The main reason to use arrays is speed of execution, as obviously using an object will keep the runtime busy with some overhead. The main reason to not use arrays is the fact that this overhead will allow you more flexibility in coding and reduce the amount of errors you make. So as a general advice: unless you know (as in measured and determined to be a bottleneck) that you need the speed, go with Lists. Java even does some internal optimizations beyond what you would expect to speed up Lists to a point where they come very close to the execution speed of arrays.
The instantiation of a collection in Java is normally as below:
ArrayList<Integer> ali = new ArrayList<Integer>();
It is said that with this convention, certain errors such as
String s = (String)ali(0)
Can lead to compile error instead of run time exceptions.
However, I observed that although
ArrayList ali = new ArrayList<Integer>();
Will cause the situation above to cause run time exceptions,
ArrayList<Integer> ali = new ArrayList();
Will still cause compile time error in the situation above.
Is there something I miss, or could we ignore the type on the right hand side if we do not care for clarity of code?
Thanks!
ArrayList<Integer> ali = new ArrayList();
and
ArrayList ali = new ArrayList<Integer>();
This will generate a compiler warning re: unchecked conversion. You will only get the compile safety of Generics if you do not ignore these warnings, or Supress them with the annotation because you can prove it's safe.
You do a raise an interesting point with:
ArrayList<Integer> ali = new ArrayList();
As you will only be using ali you do have safety with the reference. However you'll have the Compiler warning for the right hand side of the expression, so it's best to add the parameterized type and keep the compiler free of warnings. The reason the compiler is warning you is because someone could come and do this:
ArrayList<String> strings = new ArrayList<String>();
ArrayList<Integer> integers = new ArrayList(strings);
Oh no you've now got Strings in your Integers!
This is where Java 7's type inference comes in i.e.
ArrayList<Integer> ali = new ArrayList<>();
So there will no longer be a need for the parameterized type to be specified, as Integer is inferred. You can do this in Java 5 or 6 by writing a generic method such as makeArrayList() which infers the type (see Joshua Bloch Effective Java book)
You are right in your assessment that your last code snippet is not actually dangerous (though it does generate the compiler warning).
The reason why using raw types is potentially dangerous, is because you lose the type-safety that generics provides. More specifically, you lose the guarantee that you can't treat the " generic parameter as two different types in two different scenarios. (This is what the problem is in your casting-to-String example - the list is considered to contain integers at one point (when being populated) but considered to contain Strings at another point).
In the last example you've provided, the warning is technically spurious since the raw-typed list that's being constructed can only be referenced by the ali reference, which is correctly typed. Therefore, it would be impossible to insert strings into it.
However, the compiler can't guarantee this in general, as it's an implementation detail of how the ArrayList constructor works that makes this safe. (Another list implementation could "publish" a reference to itself externally, which could then be used to insert the wrong type of elements into this list). The compiler just sees that you're assigning something that's of the raw type ArrayList to a variable of type ArrayList<Integer>, and correctly says that "the thing on the right hand side might have been used for things other than Integers in the past, you know - are you sure this is OK?" It's roughly equivalent to
ArrayList al = new ArrayList();
ArrayList<Integer> ali = al;
where in this slightly expanded case, the "temporary" variable al allows one to call al.add("not an int") without compile time errors.
There's no real benefit to doing things this way and "knowing" it's correct, you may as well construct the list with the right generic parameters from the get-go, as in your first example. Unchecked conversion warnings are often not a real problem, but quite often can be - suppressing the warnings runs the risk that you'll migrate from the first situation to the second without noticing. Getting the compiler to check for you means it can tell you if your underlying assumptions become invalidated.
The compiler only checks if a variable that declares a generic type is used correct. If the variables type is a raw type, then it won't complain (with an error). So the following lines compile and run with no error:
ArrayList list = new ArrayList<Integer>();
list.add("hello");
String s = (String) list.get(0);
Note, that the compiler doesn't care that we used the generic constructor and that the runtime won't notice because of type erasure.
Once the variable has a generic type, then the compiler can check, if you use the variable "correctly" (like for collection: the compiler knows that get(0) will return the generic type and can complain on illgal casts)
The last example is safe (in this case). It is critical, if the constructor uses some typed parameters.
The following lines show the problem:
ArrayList<Double> doubles = new ArrayList<Double>();
ArrayList<Integer> integers1 = new ArrayList<Integer>(doubles); // error
ArrayList<Integer> integers2 = new ArrayList(doubles); // no error
With the third line we can legally populate a Integer typed array with Double values, we just have to ignore the warning (and catch all runtime exceptions later ;) )
OT and Trivia
With Java 7 we get the diamond operator:
ArrayList<List<Integer>> multilist = new ArrayList<List<Integer>>(); // Java 1.5+
ArrayList<List<Integer>> multilist = new ArrayList<>(); // Java 7+
The problem is that
ArrayList ali = new ArrayList<Integer>();
is an untyped Collection. The compiler warns your about it with this warning message:
ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized
However, since you have not typed it, the compiler can not know what types are in variable ali.
At runtime, however the type is erased - you in effect have ArrayList<Object>. When you retrieve an element (an Integer) and try to assign it to a String, it explodes, of course with a ClassCastException
If you don't care about code clarity, you could do it - but I don't see why you'd want to. There seems to be absolutely no gain (aside from saving a few characters worth of typing) in doing so, and it just makes your code slightly harder to read if you're not declaring and initialising on the same line.
By themselves, neither of these will produce runtime errors:
ArrayList ali = new ArrayList<Integer>();
or
ArrayList<Integer> ali = new ArrayList();
However, that's only because you've not tried to populate the list. If you do, and you make a mistake, you can get unexpected ClassCastExceptions when using values extracted from the list. For example:
ArrayList ali = new ArrayList();
ali.add("Hi mum");
ArrayList<Integer> oop = ali; // unsafe conversion
Integer first = oop.get(0);
The last line won't give a compilation error or warning, but at runtime it will give a ClassCastException. The CCE is thrown because the compiler does an implicit type cast as part of the assignment.