Reputation: 893
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?
Upvotes: 32
Views: 2337
Reputation: 1500695
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.
Upvotes: 50
Reputation: 132390
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.
Upvotes: 8
Reputation: 11
Type safety we can achieve during compilation only. During run time type eraser will erase all these things and it will be a normal byte code, i.e it is same as if we do without generics.
Upvotes: 0
Reputation: 39641
You left out the type of your second list. Leaving out the type of the first list you can also do this:
ArrayList ar = new ArrayList();
ar.add(Integer.valueOf(42));
ar.add("Hello");
The type is only considered on compile time. That's why you might get a warning in eclipse. In byte code the type is not considered and your application runs without an exception.
Upvotes: 3
Reputation: 2383
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.
Upvotes: 6
Reputation: 12932
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.
Upvotes: 7