Array of Generic List - java

I am playing with Generic and arrays, it seems the following code compiles fine,
ArrayList<Key> a = new ArrayList<Key>();
But the compiler complains about this one,
ArrayList<Key>[] a = new ArrayList<Key>[10];
By reading post in stackoverflow, I sort of understand that this is due to Type Erasure and I can fix it by using,
ArrayList<Key>[] a = (ArrayList<Key> []) new ArrayList[10];
or list of list
ArrayList<ArrayList<Key>> b = new ArrayList<ArrayList<Key>>();
But I can't figure out the reason behind the scene. Especially, why the second one is illegal given the first one is perfectly OK. And why the compiler does not complain about the list of list.

You can't have an array, because an array requires a raw type. You typecast it in the second instance, which makes it fit the defined type, and is therefore legal (however, this is impossible for it to infer). The list of list is legal as ArrayList isn't an array.
Read chapter 7.3 (page 15) in the official tutorial for more details on this.
The component type of an array object may not be a type variable or a
parameterized type, unless it is an (unbounded) wildcard type.You can
declare array types whose element type is a type variable or a
parameterized type, but not array objects.
This is annoying, to be sure. This restriction is necessary to avoid situations like:
List<String>[] lsa = new List<String>[10]; // not really allowed
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // unsound, but passes run time store check
String s = lsa[1].get(0); // run-time error - ClassCastException
If arrays of parameterized type were allowed, the example above would
compile without any unchecked warnings, and yet fail at run-time.
The tutorial then goes on to say the following:
Since type variables don’t exist at run time, there is no way to determine what the
actual array type would be.
The way to work around these kinds of limitations is to use class literals as run time
type tokens

Array was poor man's generics; with real generics, one should avoid arrays, though not always possible.
Arrays are covariant, generics are invariant; combined with erasure, things just don't fit very well, as illustrated by the example in Chris's answer.
However I think it is possible to relax the spec to allow generic array creation - there's really no problem there. The danger comes when up casting the array; a compiler warning at that point is enough.
Actually Java does create generic arrays for vararg methods, so it's a little hypocritical.
Here are utility methods taking advantage of that fact
#SafeVarargs
static <E> E[] arrayLiteral(E... array)
{
return array;
}
#SafeVarargs
static <E> E[] newArray(int length, E... array)
{
return Arrays.copyOf(array, length);
}
// usage
List<String>[] array1 = arrayLiteral(list, list);
List<String>[] array2 = newArray(10);

I had a similar question myself - FWIW, I didn't find the answers persuasive. The pertinent section from the most detailed answer (referring to the pdf reference) is this:
The component type of an array object may not be a type variable or a
parameterized type, unless it is an (unbounded) wildcard type.You can
declare array types whose element type is a type variable or a
parameterized type, but not array objects. This is annoying, to be
sure. This restriction is necessary to avoid situations like
List<String>[] lsa = new List<String>[10]; // not really allowed
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // unsound, but passes run time store check
String s = lsa[1].get(0); // run-time error - ClassCastException
So because I can cat the List[] to Object[], then shove something incorrect into the Object[], then refer to incorrectly from the List reference, through the casted ref, this is bad/disallowed? But only with new?
It's still more than a bit obscure to me how declaring this with new is any more or less of a problem than the usage, still crossing my eyes staring at it in the hope that it will start to make sense, or at least resolve into a nice 3d image.

Creating generic arrays isn't type-safe (see "Item 25: Prefer lists to arrays" of "Effective Java - second edition" by Joshua Bloch).
Use:
List<List<Key>> b = new ArrayList<List<Key>>(10);
Or with Java SE 7:
List<List<Key>> b = new ArrayList<>(10);

The arrays allow to escape type checks (as illustrated in the Chris's answer). So, you could have a code which passes all compiler checks (no "unchecked" warnings from compiler), but fail at run time with ClassCastException.
Forbidding this construction raises the problem for a developer, so warnings do appear.

Related

If Java doesn't support parameterized type arrays, how does Arrays.asList() work with them?

Supposedly, the method Arrays.asList looks like this:
public static <T> List<T> asList(T... a)
T... is practically equivalent to T[], but aren't arrays of generic types disallowed in Java? For example:
// Example 1
ArrayList<Integer>[] list = new ArrayList<Integer>[10]; // This is not allowed
// Example 2
List<ArrayList<Integer> > listOfList = Arrays.asList(new ArrayList<Integer>(), new ArrayList<Integer>()); // Allowed?
In example 2, the two parameters I passed would imply that Arrays.asList is taking ArrayList<Integer>[] as parameters which contradicts example 1. Why does example 2 work when example 1 doesn't?
You can declare ageneric type array in Java which is perfectly legal. So this should work.
private E[] array;
But you can't instantiate a generic array like so, because arrays are reified and all the generic types are implemented as erasure. So this fictitious E is not available at runtime and you are out of luck !
array = new E[10]; // Illegal and gives compiler error.
But you have a workaround here. What you can do it just a cast.
array = (E[]) new Object[10];
This way you can create and instantiate a generic array in java. You may use #SuppressWarnings("unchecked") at the lowest possible level, may be at the site of declaration to get rid of any unchecked warnings made by the compiler.
Here, the T[] is in a parameter declaration, and as #Ravindra Ranwala answered, declaring a variable or parameter of type T[] is perfectly fine. It's creating an array of a type parameter type, i.e. new T[] that is not allowed.
In this case, by passing to the varargs argument, the compiler is actually implicitly creating an array of type ArrayList<Integer>[] to pass to the method. As you know, creating an array of a parameterized type, i.e. new ArrayList<Integer>[], is also not allowed. So the compiler creates new ArrayList[], and, normally, you would get a warning in a call such as this. However, you don't get a warning in this call because Arrays.asList() has the #SafeVarargs annotation, which indicates that they do not use the runtime component type of the array object, so it is safe.

How to create List<List<T>> in java? [duplicate]

From Effective Java by Joshua Bloch,
Arrays differ from generic type in two important ways. First arrays are covariant. Generics are invariant.
Covariant simply means if X is subtype of Y then X[] will also be sub type of Y[]. Arrays are covariant As string is subtype of Object So
String[] is subtype of Object[]
Invariant simply means irrespective of X being subtype of Y or not ,
List<X> will not be subType of List<Y>.
My question is why the decision to make arrays covariant in Java? There are other SO posts such as Why are Arrays invariant, but Lists covariant?, but they seem to be focussed on Scala and I am not able to follow.
Via wikipedia:
Early versions of Java and C# did not include generics (a.k.a. parametric polymorphism).
In such a setting, making arrays invariant rules out useful polymorphic programs.
For example, consider writing a function to shuffle an array, or a function that tests two arrays for equality using the Object.equals method on the elements. The implementation does not depend on the exact type of element stored in the array, so it should be possible to write a single function that works on all types of arrays. It is easy to implement functions of type
boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);
However, if array types were treated as invariant, it would only be possible to call these functions on an array of exactly the type Object[]. One could not, for example, shuffle an array of strings.
Therefore, both Java and C# treat array types covariantly. For instance, in C# string[] is a subtype of object[], and in Java String[] is a subtype of Object[].
This answers the question "Why are arrays covariant?", or more accurately, "Why were arrays made covariant at the time?"
When generics were introduced, they were purposefully not made covariant for reasons pointed out in this answer by Jon Skeet:
No, a List<Dog> is not a List<Animal>. Consider what you can do with a List<Animal> - you can add any animal to it... including a cat. Now, can you logically add a cat to a litter of puppies? Absolutely not.
// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?
Suddenly you have a very confused cat.
The original motivation for making arrays covariant described in the wikipedia article didn't apply to generics because wildcards made the expression of covariance (and contravariance) possible, for example:
boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
The reason is that every array knows its element type during runtime, while generic collection doesn't because of type erasure.
For example:
String[] strings = new String[2];
Object[] objects = strings; // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime
If this was allowed with generic collections:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings; // let's say it is valid
objects.add(12); // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this
But this would cause problems later when someone would try to access the list:
String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
May be this help:-
Generics are not covariant
Arrays in the Java language are covariant -- which means that if Integer extends Number (which it does), then not only is an Integer also a Number, but an Integer[] is also a Number[], and you are free to pass or assign an Integer[] where a Number[] is called for. (More formally, if Number is a supertype of Integer, then Number[] is a supertype of Integer[].) You might think the same is true of generic types as well -- that List<Number> is a supertype of List<Integer>, and that you can pass a List<Integer> where a List<Number> is expected. Unfortunately, it doesn't work that way.
It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer> to a List<Number>.
Then the following code would allow you to put something that wasn't an Integer into a List<Integer>:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
Because ln is a List<Number>, adding a Float to it seems perfectly legal. But if ln were aliased with li, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.
An important feature of parametric types is the ability to write polymorphic algorithms, i.e. algorithms that operate on a data structure regardless of its parameter value, such as Arrays.sort().
With generics, that's done with wildcard types:
<E extends Comparable<E>> void sort(E[]);
To be truly useful, wildcard types require wildcard capture, and that requires the notion of a type parameter. None of that was available at the time arrays were added to Java, and makings arrays of reference type covariant permitted a far simpler way to permit polymorphic algorithms:
void sort(Comparable[]);
However, that simplicity opened a loophole in the static type system:
String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException
requiring a runtime check of every write access to an array of reference type.
In a nutshell, the newer approach embodied by generics makes the type system more complex, but also more statically type safe, while the older approach was simpler, and less statically type safe. The designers of the language opted for the simpler approach, having more important things to do than closing a small loophole in the type system that rarely causes problems. Later, when Java was established, and the pressing needs taken care of, they had the resources to do it right for generics (but changing it for arrays would have broken existing Java programs).
Arrays are covariant for at least two reasons:
It is useful for collections that hold information which will never change to be covariant. For a collection of T to be covariant, its backing store must also be covariant. While one could design an immutable T collection which did not use a T[] as its backing store (e.g. using a tree or linked list), such a collection would be unlikely to perform as well as one backed by an array. One might argue that a better way to provide for covariant immutable collections would have been to define a "covariant immutable array" type they could use a backing store, but simply allowing array covariance was probably easier.
Arrays will frequently be mutated by code which doesn't know what type of thing is going to be in them, but won't put into the array anything which wasn't read out of that same array. A prime example of this is sorting code. Conceptually it might have been possible for array types to include methods to swap or permute elements (such methods could be equally applicable to any array type), or define an "array manipulator" object which hold a reference to an array and one or more things that had been read from it, and could include methods to store previously-read items into the array from which they had come. If arrays were not covariant, user code would not be able to define such a type, but the runtime could have included some specialized methods.
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
I think they made a wrong decision at the first place that made array covariant. It breaks the type safety as it described here and they got stuck with that because of backward compatibility and after that they tried to not make the same mistake for generic.
And that's one of the reasons that Joshua Bloch prefers lists to arra ys in Item 25 of book "Effective Java(second edition)"
Generics are invariant: from JSL 4.10:
...Subtyping does not extend through generic types: T <: U does not
imply that C<T> <: C<U> ...
and a few lines further, JLS also explains that Arrays are covariant (first bullet):
4.10.3 Subtyping among Array Types
My take: When code is expecting an array A[] and you give it B[] where B is a subclass of A, there's only two things to worry about: what happens when you read an array element, and what happens if you write it. So it's not hard to write language rules to ensure that type safety is preserved in all cases (the main rule being that an ArrayStoreException could be thrown if you try to stick an A into a B[]). For a generic, though, when you declare a class SomeClass<T>, there can be any number of ways T is used in the body of the class, and I'm guessing it's just way too complicated to work out all the possible combinations to write rules about when things are allowed and when they aren't.

Generics in HashMap dont seem to be used consistently

Firstly new to Generics. Now question - In the HashMap.java I see the following -
transient Entry[] table;
which is initiated in constructor as
table = new Entry[capacity];
Why was this not declared with type parameters ?
Or
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
Why was Entry in the for loop declared with type parameters?
Is there a deep concept or just an affordable inconsistency ?
That's because creating an array of concrete parameterized type is not type safe, and that is why that's not allowed at all.
If you try code something like this, you would get a compiler error:
List<String>[] arr = new ArrayList<String>[10]; // Compiler error: Generic Array creation
The issue is that generic types are non-reifiable - their type information is not available at runtime. Meanwhile, arrays use type information that is available at runtime to do an ArrayStoreCheck to see if an element inserted into an array is compatible with the array's type. So, if you mix up arrays and generics, then you might end up having surprising behaviour at runtime.
For example, consider the following code:
List<String>[] arr = new ArrayList<String>[10]; // Suppose this was valid
Object[] objArr = arr; // This is valid assignment. A `List[]` is an `Object[]`
objArr[0] = new ArrayList<Integer>(); // There you go. A disaster waiting at runtime.
String str = arr[0].get(0); // Assigned an `Integer` to a `String`. ClassCastException
So, had the 1st assignment compiled, the 4th assignment, which looks fine to the compiler, would have throw a ClassCastException at runtime.
However, you can create an array of raw types - ArrayList, or unbounded wildcard parameterized type - ArrayList<?>, as both of them are fully reifiable types. So the following array creations are valid:
List[] arr = new ArrayList[10];
List<?>[] arr2 = new ArrayList<?>[10];
Since there is no type information associated with raw types or unbounded wildcard types, there is nothing to lose at runtime. And hence those types are reifiable, they are an eligible component type of an array. That is why Entry[] is used instead of Entry<K, V>[].
See also:
How to create a generic array?

Creating an array to store generic types in Java [duplicate]

This question already has answers here:
Generic arrays in Java
(5 answers)
Closed 9 years ago.
Suppose I have to create an array which stores ArrayList's of Integers and the array size is 10.
The below code will do it:
ArrayList<Integer>[] pl2 = new ArrayList[10];
Question 1:
In my opinion the more appropriate code would be
ArrayList<Integer>[] pl2 = new ArrayList<Integer>[10];
Why does this not work?
Question 2:
Both of the below compile
ArrayList<Integer>[] pl2 = new ArrayList[10];
ArrayList[] pl3 = new ArrayList[10];
What is the difference as far as the reference declaration of pl2 and pl3 is concerned?
The generic info only matters in compile time, it tells the compiler which type could be put into an array, in runtime, all the generic info will be erased, so what matters is how you declare the generic type.
Quoted from Think in Java:
it’s not precisely correct to say that you cannot create arrays of
generic types. True, the compiler won’t let you instantiate an array
of a generic type. However, it will let you create a reference to
such an array. For example:
List<String>[] ls;
This passes through the compiler without complaint. And although you
cannot create an actual array object that holds generics, you can
create an array of the non-generified type and cast it:
//: arrays/ArrayOfGenerics.java
// It is possible to create arrays of generics.
import java.util.*;
public class ArrayOfGenerics {
#SuppressWarnings("unchecked")
public static void main(String[] args) {
List<String>[] ls;
List[] la = new List[10];
ls = (List<String>[])la; // "Unchecked" warning
ls[0] = new ArrayList<String>();
// Compile-time checking produces an error:
//! ls[1] = new ArrayList<Integer>();
// The problem: List<String> is a subtype of Object
Object[] objects = ls; // So assignment is OK
// Compiles and runs without complaint:
objects[1] = new ArrayList<Integer>();
// However, if your needs are straightforward it is
// possible to create an array of generics, albeit
// with an "unchecked" warning:
List<BerylliumSphere>[] spheres =
(List<BerylliumSphere>[])new List[10];
for(int i = 0; i < spheres.length; i++)
spheres[i] = new ArrayList<BerylliumSphere>();
}
}
Once you have a reference to a List[], you can see that you
get some compile-time checking. The problem is that arrays are
covariant, so a List[] is also an Object[], and you can use
this to assign an ArrayList into your array, with no error at
either compile time or run time.
If you know you’re not going to
upcast and your needs are relatively simple, however, it is possible
to create an array of generics, which will provide basic compile-time
type checking. However, a generic container will virtually always be a
better choice than an array of generics.
Question 1:
Basically, this is forbidden by Java language. This is covered in Java Language Specification for generics.
When you use
ArrayList<Integer>[] pl2 = new ArrayList[10]; // warning
you get the compiler warning, because the following example will compile (generating warning for every line of code):
ArrayList wrongRawArrayList = new ArrayList(); // warning
wrongRawArrayList.add("string1"); // warning
wrongRawArrayList.add("string2"); // warning
pl2[0] = wrongRawArrayList; // warning
but now you array, that supposed to contain ArrayList of Integer, contains totally wrong ArrayList of String objects.
Question 2:
As it was already answered, declaration of p12 provides you with compile time checking and frees you from using casting when getting items from your ArrayList.
Slightly modified previous example:
ArrayList<Integer>[] pl2 = new ArrayList[10]; // warning
ArrayList<String> wrongArrayList = new ArrayList<String>(); // OK!
wrongArrayList.add("string1"); // OK!
wrongArrayList.add("string2"); // OK!
pl2[0] = wrongArrayList; // ERROR
Now, since you are using generics, this won't compile.
But if you use
ArrayList[] pl2 = new ArrayList[10];
you will get the same result as in the first example.
Arrays are covariant. That means they retain the type of their elements at runtime. Java's generics are not. They use type erasure to basically mask the implicit casting that is going on. It's important to understand that.
You need to use Array.newInstance()
In addition, arrays carry runtime type information about their
component type, that is, about the type of the elements contained.
The runtime type information regarding the component type is used when
elements are stored in an array in order to ensure that no "alien"
elements can be inserted.
For more details look here
This does not work because generic classes does not belong to Reifiable Types.
The JLS about Array creation expression states :
It is a compile-time error if the [class type] does not denote a reifiable type (§4.7). Otherwise, the [class type] may name any named reference type, even an abstract class type (§8.1.1.1) or an interface type (§9).
The rules above imply that the element type in an array creation expression cannot be a parameterized type, other than an unbounded wildcard.
The definition of Reifiable Types is :
Because some type information is erased during compilation, not all types are available at run time. Types that are completely available at run time are known as reifiable types.
A type is reifiable if and only if one of the following holds:
It refers to a non-generic class or interface type declaration.
It is a parameterized type in which all type arguments are unbounded wildcards (§4.5.1).
It is a raw type (§4.8).
It is a primitive type (§4.2).
It is an array type (§10.1) whose element type is reifiable.
It is a nested type where, for each type T separated by a ".", T itself is reifiable.
For example, if a generic class X<T> has a generic member class Y<U>, then the type X<?>.Y<?> is reifiable because X<?> is reifiable and Y<?> is reifiable. The type X<?>.Y<Object> is not reifiable because Y<Object> is not reifiable.
Let's start with question 2 first and then get back to question 1:
Question 2:
>
ArrayList[] pl2 = new ArrayList[10];
ArrayList[] pl3 = new ArrayList[10];
What is the difference as far as the reference declaration of p12 and
p13 is concerned?
In pl2 ensures better type safety than p13.
If I write for pl2:
pl2[0]=new ArrayList<String>();
it will give me a compiler error stating "cannot convert from ArrayList<String> to ArrayList<Integer>"
Thus it ensures compile time safety.
However if I write for p13
pl3[0]=new ArrayList<String>();
pl3[1]=new ArrayList<Integer>();
it will not throw any error and the onus will be on the developer to code and check properly while extracting data from p13, to avoid any unsafe type conversion during runtime.
Question 1:
That's just probably the way generics work. During the main array initialization, ArrayList<Integer>[] pl2 = new ArrayList[10], the left hand side, ArrayList<Integer>[] pl2, will ensure type safety only when you initialize the ArrayList object in the index position:
pl2[0]=new ArrayList<Integer>();
The right hand side main array declaration = new ArrayList[10] just ensures that the index position will hold ArrayList type items. Also have a look at type erasure concepts in Type Erasure for more information.
Question 1.
Well, it's not the correct syntax. Hence that does not work.
Question 2.
ArrayList<Integer>[] pl2 = new ArrayList[10];
ArrayList[] pl3 = new ArrayList[10];
Since pl2 is defined with generic type <Integer> at compile time, the compiler will be know that pl2 is only allowed to have Integers and if you try to assign somthing other than Integers you will be alerted and compilation will fail.
In pl3 since there is no generic type you can assign any type of object to the list.
ArrayList<Integer>[] pl2 = new ArrayList<Integer>[10];
Means you don't need to do casting when you retrive data from the ArrayList
example
in normal case
ArrayList[] pl2 = new ArrayList[10];
pl2.put(new Integer(10));
Integer i = p12.get(0); // this is wrong
Integer i = (Integer)p12.get(0); // this is true with casting
but
ArrayList<Integer>[] pl2 = new ArrayList<Integer>[10];
pl2.put(new Integer(10));
Integer i = p12.get(0); // this is true no need for casting
Problems with generics are by default issued as a warning by the compiler.
After compilation, because of type erasure, they all become ArrayList[] pl2 = new ArrayList[10], but the compiler warns you that this is not good.
Generics have been added to Java, and to be backwards compatible you can use generic with non-generic interchangeably.
Question1
You cannot create arrays of parameterized types
Question 2
ArrayList<Integer>[] pl2 = new ArrayList[10];
It means you are telling to compiler that you are going to create array which will store arraylist of integers. Your arraylist will only contain Integer objects. That's where generics comes in. Generics make your code more safer and reliable. If you are sure your list should only contain integer objects, you should always go ahead with this.
But when you say
ArrayList[] pl3 = new ArrayList[10];
it means arraylist can store any object type like string, integer, custom objects, etc.
It seems like you cannot create an array of arraylists with a generic type, according to an answer to Stack Overflow question Create an array of ArrayList elements.
As far as I know, in Java there are no such things as generics. In terms of types, ArrayList<Integer> and ArrayList are the same things.
Java uses type erasure for generics. It means that all type information about the generic is erased at compile time. So ArrayList<Integer> become ArrayList.
So it's just a compile-time trick. I am guessing, to avoid any confusions or mistakes that the programmer might do, they allowed ArrayList<Integer>[] to be instantiated like this: new ArrayList[10].
So an ArrayList<Integer>[] and a ArrayList[] are the same thing because the information in brackets is erased at compile time.

What could happen when one of the type parameters was not specified during instantiation of a collection?

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.

Categories

Resources