public class Main {
public static void main(String[] args) {
ArrayList<Integer> ar = new ArrayList<Integer>();
List l = new ArrayList();
l.add("a");
l.add("b");
ar.addAll(l);
System.out.println(ar);
}
}
Output: [a,b]
You can't directly add String
to ArrayList<Integer> ar
, but by using addAll()
it is possible.
How can we add String
to ArrayList
whose type has already been specified as Integer
? Can anyone highlight clear implementation details and the reason behind this?
Yeah, you can create an ArrayList<Object> but having said that, my advice for you is this: don't. Don't create Lists with mixed types since this suggests that your program design is broken and needs to be improved so that this sort of monster isn't needed.
The Java collection classes, including ArrayList, have one major constraint: they can only store pointers to objects, not primitives. So an ArrayList can store pointers to String objects or Color objects, but an ArrayList cannot store a collection of primitives like int or double.
Note: The ArrayList class does not have its own toString() method. Rather it overrides the method from the Object class.
For int data type, the wrapper class is called Integer . So, to create an ArrayList of ints, we need to use the Integer wrapper class as its type. We can now add integers to the ArrayList by using the class's add() method. ArrayList , just like normal arrays, follow zero-based indexing.
But how can we add strings to arraylist whose type has already been specified as Integer?
Because of the way Java generics was designed for backwards compatibility, with type erasure and raw types, basically.
At execution time, there's no such things as an ArrayList<Integer>
- there's just an ArrayList
. You're using the raw type List
, so the compiler isn't doing any of its normal checks, either at compile-time or adding execution-time casts.
The compiler does warn you that you're doing unsafe things though:
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
... and when you recompile with the relevant flag, it will warn about everything, including probably the most surprising line:
ar.addAll(l);
That's the one that surprises me somewhat, in terms of compiling - I believe it's effectively trusting that the List
is a Collection<? extends Integer>
really, when we know it's not.
If you avoid using raw types, this sort of mess goes away.
This is more about mixing of raw and generic types in Java's type system than it is about type erasure. Let me augment the code fragment from the question:
ArrayList<Integer> ar = new ArrayList<Integer>();
List l = new ArrayList(); // (1)
l.add("a");
l.add("b");
ar.addAll(l); // (2)
System.out.println(ar);
Integer i = ar.get(0); // (3)
With today's erased generics, line (3) throws ClassCastException
. If Java's generics were reified, it is easy to assume that runtime type checking would cause an exception to be thrown at line (2). That would be one possible design of reified generics, but other designs might not do that checking. Why not? Mainly for the same reason we have erased generics today: migration compatibility.
Neal Gafter observed in his article Reified Generics for Java that there are a lot of unsafe uses of generics, with improper casts, and so forth. Today, even more than ten years after generics were introduced, I still see a lot of usage of raw types. (Including, unfortunately, here on Stack Overflow.) Unconditionally performing reified generic type checking would break a huge amount of code, which of course would be a big blow to compatibility.
Any realistic generic reification proposal would have to provide reification on an opt-in basis, such as via subtyping (as in Gafter's proposal), or via annotations (Gerakios, Biboudis, Smaragdakis. Reified Type Parameters Using Java Annotations. [PDF] GPSE 2013.), and it would have to decide how to deal with raw types. It seems wholly impractical to disallow raw types entirely. In turn, allowing raw types effectively means that there is a way to circumvent the generic type system.
(This sort of decision is not undertaken lightly. I've witnessed shouting matches between type theorists, one of whom was complaining that Java's type system is unsound. For a type theorist, this is the most grievous of insults.)
Essentially, that's what this code does: it bypasses the generic type checking goodness by using raw types. Even if Java's generics were reified, checking might not be done at line (2). Under some of the reified generics designs, the code might behave exactly the same as it does today: throwing an exception at line (3).
In Jon Skeet's answer, he admits to being somewhat surprised that at line (2) the compiler trusts that list l
contains elements of the right type. It's not really about trust -- after all, the compiler does issue a warning here. It's more the compiler saying, "Ok, you're using raw types, you're on your own. If you get a ClassCastException
later, it's not my fault." Again, though, this is about allowing raw types for compatibility purposes, not erasure.
You are using a raw type. If you use List<String> l = new ArrayList<>()
you will find that your code will not compile anymore. Raw types exist only for backwards compatibility and should not be used in new code.
When it was born, Java did not have generics (that is, classes that are parameterized by another class). When generics were added, to maintain compatibility, it was decided not to change the Java bytecode and class file format. So, generic classes are transformed by the compiler into non-generics ones. This means that an ArrayList is actually storing instances of class Object, and so it can also accept instances of String (that is a subclass of Object). The compiler cannot always detect misuses.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With