12

I was reading this article on Java Generics and there it is mentioned that the constructor for an ArrayList looks somewhat like this:

class ArrayList<V> {
  private V[] backingArray;
  public ArrayList() {
    backingArray = (V[]) new Object[DEFAULT_SIZE]; 
  }
}

I was not able to understand how type erasure and type checking by the compiler happens as explained there. One point I got was that the type parameter is transformed to Object type.

I would imagine it as (replacing all V with Object), but this definitely wrong.

class ArrayList<Object> {
      private Object[] backingArray;
      public ArrayList() {
        backingArray = (Object[]) new Object[DEFAULT_SIZE]; 
      }
}

How exactly is it transformed to Object type but still retaining the type safety for V? when I have ArrayList<String> and ArrayList<Integer> are there two different classes for each? If not, where is the type information of String and Integer is stored?

ugo
  • 2,653
  • 2
  • 28
  • 34
brain storm
  • 25,842
  • 54
  • 187
  • 349
  • As an aside, the collections classes don't actually do that. Maybe they did when the article was written but not anymore. – Radiodef Mar 07 '14 at 20:46

3 Answers3

8

Your type erased version is not correct. The type parameter declaration is not erased to Object but only it's usage is erased. More specifically:

  • Erasure of a generic type is its corresponding raw type. So, for ArrayList<V>, it would be just ArrayList.
  • Erasure of a type parameter is its left-most bound.
  • And all the type arguments are just removed. The type arguments are the one you use while instantiating the generic class. So, ArrayList<Integer> will be replaced with ArrayList.

So, the correct erased version would be:

class ArrayList {
    private Object[] backingArray;
    public ArrayList() {
      backingArray = (Object[]) new Object[DEFAULT_SIZE]; 
    }
}

when I have ArrayList and ArrayList are there two different classes for each?

No, this is never the case. The compiler generates only one byte code representation of a generic type or method and maps all the instantiations of the generic type or method to the unique representation.

if not where the type information of String and Integer is stored?

When the compiler performs type-erasure, it removes all the type information, based on some pre-defined rules, occasionally adding what is called as bridge method, and adds all the necessary type casting required.

So, for example, the following usage of ArrayList<Integer> and ArrayList<String>:

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
int value = list.get(0);

ArrayList<String> list2 = new ArrayList<String>();
list.add("A");
String value2 = list.get(0);

will be converted to somewhat like this:

ArrayList list = new ArrayList();
list.add(1);
int value = (Integer) list.get(0);

ArrayList list2 = new ArrayList();
list.add("A");
String value2 = (String) list.get(0);

Further Reading:

Rohit Jain
  • 195,192
  • 43
  • 369
  • 489
  • if the `necessary castings` are added by `compiler`, for `ArrayList`, I have to cast to `Integer` and for `ArrayList` I have to cast to `String`, meaning there will be two different classes? – brain storm Mar 07 '14 at 20:33
  • 1
    @brainstorm No, there aren't two different classes. Why do you think casting would require two different classes? – Rohit Jain Mar 07 '14 at 20:34
  • Casts will be added where they are used..Not in the class definition itself.If I am using a main program,casts will be added where I am creating ArrayList instances,not in the ArrayList bytecode itself... – Kumar Abhinav Mar 07 '14 at 20:36
  • regarding the correct erased version you have above, how is a String[] backingArray created, you are casting `(Object[]) new Object[DEFAULT_SIZE]; `, which I am unable to understand – brain storm Mar 07 '14 at 20:37
  • 2
    @brainstorm That is the whole point. `String[]` is not at all created. There is no information abotu the type argument inside the generic class itself. The backing array is still an `Object[]`. And the list will still give you back an `Object` reference. That is why there is a need of casting it `list.get(0)` to `String`. – Rohit Jain Mar 07 '14 at 20:39
  • I see, then why is such a cast necessary `backingArray = (V[]) new Object[DEFAULT_SIZE]`; instead declare it as `backingArray = new Object[DEFAULT_SIZE];` – brain storm Mar 07 '14 at 20:42
  • @brainstorm Because to compiler, `Object[]` is not a `V[]`. You need a cast there to make it compile. Remember an important point - *Generics is all about compile-time type safety, nothing more than that.* – Rohit Jain Mar 07 '14 at 20:45
  • it is tricky to understand. because at the end it is like fooling the compiler, just to make it compile. At the end, if this is what I have `(Object[]) new Object[DEFAULT_SIZE];`, I could have declared it as `new Object[DEFAULT_SIZE]`. because casting Object[] to Object[] array (ofcourse at runtime) makes no sense – brain storm Mar 07 '14 at 20:48
  • @brainstorm Hmmm. Yes in fact this is how the `java.util.ArrayList` is implemented. Take a look at the source code. But there you'll be casting `Object` to `Object`, but that is how generics work. It is not about fooling compiler. You're not doing that. Compiler will just help you in case you're doing something wrong, by stopping the code from compiling. – Rohit Jain Mar 07 '14 at 20:49
  • yes, I know that is how it is implemented. ok one final clarification, After compilation of generic code, the type information is lost due to type erasure, but appropriate casting would be inserted in byte code correct? – brain storm Mar 07 '14 at 20:51
  • along with bridge methods – Kumar Abhinav Mar 07 '14 at 20:55
3

Your second example is incorrect. Type erasure does not imply globally casting everything to Object. As you surmised, this hardly makes any sense. Instead, what type erasure does is (literally) the following (borrowed from Jon Skeet):

List<String> list = new ArrayList<String>();
list.add("Hi");
String x = list.get(0);

This block of code is translated to:

List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);

Notice the cast to String, not merely a vanilla Object. Type erasure "erases" the types of generics and casts all objects therein to T. This is a clever way to add some compile-time user-friendliness without incurring a runtime cost. However, as the article claims, this is not without compromises.

Consider the following example:

ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();

It may not seem intuitive (or correct), but li.getClass() == lf.getClass() will evaluate to true.

Community
  • 1
  • 1
David Titarenco
  • 30,718
  • 13
  • 54
  • 108
0

Good Question.The type checking is done first.If everything compiles(that is,after it has provided compile type safety) does type erasure occur.

Again,lot of things happen as part of type erasure which includes:-

1)Adding casts 
2) creating bridge methods

But type checking is done first,everything happens later

Kumar Abhinav
  • 6,274
  • 2
  • 19
  • 32