32
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?

Radiodef
  • 37,180
  • 14
  • 90
  • 125
Santanu
  • 893
  • 3
  • 10
  • 24
  • must say that there is a deep difference between JAVA and C# in that matter... for a short summary please reffer to this link: http://stackoverflow.com/questions/355060/c-sharp-vs-java-generics – ymz Mar 24 '15 at 23:44
  • possible duplicate of [What is the difference between ArrayList>, ArrayList, ArrayList?](http://stackoverflow.com/questions/18513308/what-is-the-difference-between-arraylist-arraylist-arraylistobject) – SpaceTrucker Mar 25 '15 at 13:48
  • possible duplicate of [What is a raw type and why shouldn't we use it?](http://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it) – Radiodef Apr 09 '15 at 07:23

6 Answers6

50

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • To add on to this, when spectating the code of `java.util.ArrayList#addAll`, you can simply see that it is just creating an object array of the collection and uses System.arraycopy to copy this array into another array of objects. – SomeJavaGuy Mar 24 '15 at 13:13
  • 8
    This isn't because of type erasure, it's because generic collections were designed to be backwards usable with non-generic ones. It's easy for the compiler to not accept an untyped List in addAll but it was not very pragmatic to expect users to convert all their code at once to generics. The warning is a settlement. – Benjamin Gruenbaum Mar 24 '15 at 19:45
  • 1
    Also, at runtime you will get a ClassCastException if your code expects that array to only contain Integer. for (final Integer i : ar) { System.out.println(i); } – Rick Ryker Mar 24 '15 at 22:55
  • 1
    @Benjamin: without type erasure though, the error could be detected at execution time, surely... So it's not like type erasure is irrelevant, unless I've missed something about your comment. – Jon Skeet Mar 24 '15 at 22:59
  • It's more about raw types than erasure vs reification. I was going to comment here, but it turned into an answer. http://stackoverflow.com/a/29247830/1441122 – Stuart Marks Mar 25 '15 at 05:03
  • 1
    @StuartMarks: I think it would be fair to say that it *compiles* because of raw types, but it *executes* because of erasure. They're all pretty related topics, of course - I'll edit my answer accordingly. – Jon Skeet Mar 25 '15 at 06:23
  • @JonSkeet thanks for fixing your answer and clarifying, I feel that while types that are not erased would allow for easier implementation of run-time type checking here the crux of the issue is indeed the design of Java generics for backwards compatibility. – Benjamin Gruenbaum Mar 25 '15 at 08:42
  • @BenjaminGruenbaum: As I said to Stuart, I think it depends on whether you're considering the compile-time or execution-time aspect of the question. Without type erasure, I would *expect* execution-time checking, at which point this code wouldn't have executed without an exception even if it had compiled. (e.g. the compiler could have added a cast which *actually* checked that it's a `Collection extends Integer>`). There are two aspects to backward compatibility, too - source code, and bytecode. – Jon Skeet Mar 25 '15 at 08:45
  • @JonSkeet I agree with the distinction but in (mostly) statically typed languages typically when type safety is discussed the context is usually compile time type safety. While I agree that run time generics would have made checking type safety in run time easier here - it's quite possible to design (ugly) interfaces that enforce run time type safety in this case even with type erasure using a variety of tricks I'm sure you're aware of. My initial point was that forfeiting type safety here was a deliberate design decision and not a language limitation of sorts - which blaming erasure implies. – Benjamin Gruenbaum Mar 25 '15 at 08:52
  • @BenjaminGruenbaum: Whereas I'd say it *is* a language limitation - a deliberate one for the sake of backward compatibility, but a limitation nonetheless. – Jon Skeet Mar 25 '15 at 09:03
8

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.

Community
  • 1
  • 1
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
7

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.

Hoopje
  • 12,677
  • 8
  • 34
  • 50
6

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.

lodo
  • 2,314
  • 19
  • 31
3

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.

Kai
  • 38,985
  • 14
  • 88
  • 103
0

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.

Buhake Sindi
  • 87,898
  • 29
  • 167
  • 228
Vani
  • 11