17

ArrayList uses Object array internally:

private transient Object[] elementData;

and in E get(int) method it's cast to E type.

my question is: why ArrayList doesn't use E[] to store objects?

I understand that after compiler run, the type-erasure will transform E[] into Object[], but still need to cast to E in every get() call?

If use it E[] this code below isn't necessary

return (E) elementData[index];

The choice of use Object[] is for performance?

As type-erasure transforms E[] into Object[], java makes a cast internally to return correct type in generic methods?

EDITED

Let me to explain better what is the point of my doubt:

If ArrayList use E[] instead Object[], in method get(int) the cast isn't necessary. This will increase performance (apparently).

But, there is no magic, I think that use E[] JVM will cast object anyway, because type-erasure will transformed in Object. Correct?

ps: sorry for my bad english.

Johnny Willer
  • 3,236
  • 3
  • 22
  • 48
  • You seem to think that "E" is real. It's Fig Newton of your imagination. The class of the array is fixed at the time that the ArrayList class is compiled, back in the Java foundry. – Hot Licks Sep 05 '14 at 22:59
  • @HotLicks "I understand that after compiler run, the type-erasure will transform E[] into Object[]" .. "If use it E[] this code below [it isn't] necessary [to use casts]" – user2864740 Sep 05 '14 at 23:00
  • The source code could be probably improved declaring E[], but the final compiled result would be the same. – Simone Gianni Sep 05 '14 at 23:00
  • What code isn't necessary? The `(E)` cast generates no actual code. – Hot Licks Sep 05 '14 at 23:00
  • ArrayList.toArray(); return also Object[].. – barwnikk Sep 05 '14 at 23:01
  • what if you skip to pass Type parameter – jmj Sep 05 '14 at 23:01
  • @barwnikk But that is an *external* API that also creates a new array; the OP is asking why the internal backing is Object[] and not E[]. – user2864740 Sep 05 '14 at 23:01
  • @user2864740 - Because `E[]` is a total fiction. There is no `E` class to make an array with. – Hot Licks Sep 05 '14 at 23:08
  • @SimoneGianni, *The source code could be probably improved declaring*, actually then there won't be possible to allocate the backing array and it'd need cast too. No improvement. – bestsss Sep 05 '14 at 23:15
  • @barwnikk, it's free to return String[] if it can contain the elements. For example Arrays.asList("a", "b").toArray(); is actually a String[]. – bestsss Sep 05 '14 at 23:17
  • @bestsss and? ArrayList s = new ArrayList(); s.toArray() return Object[]; – barwnikk Sep 05 '14 at 23:18
  • ArrayList is from Java 1.2. Generics are from Java 1.5. (Although, perhaps they could have changed it in 1.5.) – David Conrad Sep 05 '14 at 23:33
  • Possible duplicate: http://stackoverflow.com/a/13782651/1393766 – Pshemo Sep 05 '14 at 23:39
  • @HotLicks [I still don't get the problem](http://ideone.com/p0yXT7), and there isn't a debate that "E doesn't really exist" - even the OP acknowledges it. So why stick with Object[] as the *type* of the member variable? (The *explicit* cast here is creating array, which presumably happens far less, not on access.) – user2864740 Sep 05 '14 at 23:39
  • Because the compiler (and the class verifier in the JVM) expect a REAL object class there. If you think you can do it better, take the source and try to create (and compile and run) your own version. – Hot Licks Sep 05 '14 at 23:42
  • @HotLicks I've provide a working example, now the OP's questions from the last two lines, paraphrased: "*Why* isn't a generic E[] used?", "Is it using Object[] for performance?", and "Does the compiler insert implicit casts *anyway* if E[] were to be used?" All this other stuff about E not really existing is simply a distraction, IMOHO. – user2864740 Sep 05 '14 at 23:43
  • Run *javap* on your "working example" and the real code and tell me what's different. – Hot Licks Sep 05 '14 at 23:45
  • @HotLicks Write the results of such as an answer; so many side-tracking comments. – user2864740 Sep 05 '14 at 23:46
  • 1
    Basically, generics simply hide cast operations. The casts don't go away, and, in fact, it's probably the case that more cast operations occur because of the awkwardness of making generics "mesh". – Hot Licks Sep 07 '14 at 03:33

1 Answers1

14

Update: this answer got way more attention and upvotes than I think it deserved for basically copy-pasting JDK source code, so I'm going to try to turn it into something worthy.


Java generics are designed to look and feel like true, reified, multi-instantiated, C++- or C#-style generics. That means that for a type like ArrayList<E>, we expect ArrayList<String> to behave like every occurrence of E has been replaced with String. In other words, this:

private Object[] elementData = new Object[size];

public E get(int i) {
    return (E) elementData[i];
}

String str = list.get(0);

ought to become this:

private Object[] elementData = new Object[size];

public String get(int i) {
    return (String) elementData[i];
}

String str = list.get(0);

Now, as you probably know, that's not actually how they work. For backwards-compatibilty reasons that are now (mostly) long behind us, Java generics are implemented via type erasure, where E is actually replaced with Object everywhere, and casts to String are inserted into the calling code where necessary. That means the code actually becomes something like this:

private Object[] elementData = new Object[size];

public Object get(int i) {
    return elementData[i];
}

String str = (String) list.get(0);

The cast to (E) has disappeared, and reappeared at the call site. If the call site had ignored the result, the cast would have vanished completely! This is why it gave an "unchecked" warning.


Now imagine if elementData had type E[] instead, as you suggest. That is, the code looked like this:

private E[] elementData = (E[]) new Object[size];

public E get(int i) {
    return elementData[i];
}

String str = list.get(0);

We know it gets transformed into the same thing as above, due to erasure. But if we had reified generics like we wish we did, it would look like this:

private String[] elementData = (String[]) new Object[size];
// ClassCastException: Object[] is not a String[]

Essentially we've written some code that should crash at runtime, and the only reason it works at all is that Java's generics implementation pretends to be better than it is. We've lied to the compiler to convince it to accept brittle code.

And it is brittle! We happen to avoid runtime crashes because the array never escapes the class. But if it did, it would cause ClassCastExceptions in hard-to-predict places. What if Java 9 introduced reified generics? The first implementation would keep working, but this one would break.

That's why most reasonable Java coding conventions require unchecked casts to be type-correct. (E) elementData[i] is type-correct because ArrayList makes sure only Es can be stored in elementData. (E[]) new Object[size] is never type-correct unless E is Object.


There are other benefits too. In Java 8, the elementData field can take on special sentinel values:

/**
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access
Tavian Barnes
  • 11,679
  • 3
  • 41
  • 107
  • I am not sure if existence of these special sentinel values is *reason for*, or *result of* declaring `elementData;` as `Object[]`. – Pshemo Sep 05 '14 at 23:17
  • 2
    @Pshemo `static E[]` is illegal – Tavian Barnes Sep 05 '14 at 23:18
  • 2
    They can still leave the `static` fields as `Object[]` and have the instance field of type `E[]`, casting at assignment. – Sotirios Delimanolis Sep 05 '14 at 23:21
  • I understand this (generic types are meant to be used with instances, and since there can be many instances `E` could represent many types at the same time so it is impossible to use it with static members). I updated my comment a little. – Pshemo Sep 05 '14 at 23:21
  • Generics were introduced in Java 5 .. there wasn't any shortage of time between Java 5 and 8. – user2864740 Sep 05 '14 at 23:49
  • @user2864740 It's been ten years since Java 5 was delivered, but we are *still* generifying the JDK code! For example, http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/6b2404e27d07 – Stuart Marks Sep 06 '14 at 04:40
  • Well, I observed this too, but putting E[] array = new (E[])Object[SIZE]; what is the problem with this? – Johnny Willer Sep 08 '14 at 11:19
  • 1
    @JohnnyWiller I think that would also work but it's a more egregious lie. The cast in `return (E) array[i]` is okay because the runtime type of `array[i]` must be `E`. The cast in `(E[]) new Object[size]` is never type-correct because `Object[]` and `E[]` are fundamentally different types. – Tavian Barnes Sep 08 '14 at 18:45
  • 1
    @JohnnyWiller In other words, if you took the current implementation and replaced `E` with `String` everywhere, you'd have a working `List` implementation. But if it worked how you suggested, the cast would become `(String[]) new Object[size]` which *would fail* at runtime. – Tavian Barnes Sep 08 '14 at 18:47
  • Ok, to finalize it: using `Object[] elementData = new Object[SIZE];` works internally basically equals to `E[] elementData = (E[])new Object[SIZE];` In terms of performance, is pratically same too. But the second option is 'ugly' and 'lier'. – Johnny Willer Sep 09 '14 at 00:31
  • @JohnnyWiller I've expanded on those points in a (very large) edit to my answer. – Tavian Barnes Sep 09 '14 at 02:21
  • @TavianBarnes thank you :) – Johnny Willer Sep 09 '14 at 11:15