Question

If I have a generic class Foo<Bar>, I am not allowed to create an array as follows:

Bar[] bars = new Bar[];

(This will cause the error "Cannot create a generic array of Bar").

But, as suggested by dimo414 in an answer to this question (Java how to: Generic Array creation), I can do the following:

Bar[] bars = (Bar[]) new Object[];

(This will "only" generate a warning: "Type safety: Unchecked cast from Object[] to Bar[]").

In the comments responding to dimo414's answer, some people claim that using this construct can cause problems in certain situations and others say it's fine, as the only reference to the array is bars, which is of the desired type already.

I'm a little confused in which cases this is OK and in which cases it can run me into trouble. The comments by newacct and Aaron McDaid, for example, seem to directly contradict each other. Unfortunately the comment stream in the original question simply ends with the unanswered "Why is this 'no longer correct'?", so I decided to make a new question for it:

If the bars-array only ever contains entries of type Bar, could there still be any run-time issues when using the array or its entries? Or is the only danger, that at run-time I could technically cast the array to something else (like String[]), which would then allow me to fill it with values of a type other than Bar?

I know I can use Array.newInstance(...) instead, but I am specifically interested in the type-casting construct above, since, for example, in GWT the newInstance(...)-option isn't available.

Was it helpful?

Solution

Since I was mentioned in the question, I will chime in.

Basically, it will not cause any problems if you don't expose this array variable to the outside of the class. (kinda like, What happens in Vegas stays in Vegas.)

The actual runtime type of the array is Object[]. So putting it into a variable of type Bar[] is effectively a "lie", since Object[] is not a subtype of Bar[] (unless Object is Bar). However, this lie is okay if it stays inside the class, since Bar is erased to Object inside the class. (The lower bound of Bar is Object in this question. In a case where the lower bound of Bar is something else, replace all occurrences of Object in this discussion with whatever that bound is.) However, if this lie gets somehow exposed to the outside (the simplest example is returning the bars variable directly as type Bar[], then it will cause problems.

To understand what is really going on, it is instructive to look at the code with and without generics. Any generics program can be re-written into an equivalent non-generics program, simply by removing generics and inserting casts in the right place. This transformation is called type erasure.

We consider a simple implementation of Foo<Bar>, with methods for getting and setting particular elements in the array, as well as a method for getting the whole array:

class Foo<Bar> {
    Bar[] bars = (Bar[])new Object[5];
    public Bar get(int i) {
        return bars[i];
    }
    public void set(int i, Bar x) {
        bars[i] = x;
    }
    public Bar[] getArray() {
        return bars;
    }
}

// in some method somewhere:
Foo<String> foo = new Foo<String>();
foo.set(2, "hello");
String other = foo.get(3);
String[] allStrings = foo.getArray();

After type erasure, this becomes:

class Foo {
    Object[] bars = new Object[5];
    public Object get(int i) {
        return bars[i];
    }
    public void set(int i, Object x) {
        bars[i] = x;
    }
    public Object[] getArray() {
        return bars;
    }
}

// in some method somewhere:
Foo foo = new Foo();
foo.set(2, "hello");
String other = (String)foo.get(3);
String[] allStrings = (String[])foo.getArray();

So there are no casts inside the class anymore. However, there are casts in the calling code -- when getting one element, and getting the entire array. The cast to get one element should not fail, because the only things we can put into the array are Bar, so the only things we can get out are also Bar. However, the cast when getting the entire array, that will fail, since the array has actual runtime type Object[].

Written non-generically, what is happening and the problem become much more apparent. What is especially troubling is that the cast failure does not happen in the class where we wrote the cast in generics -- it happens in someone else's code that uses our class. And that other person's code is completely safe and innocent. It also does not happen at the time where we did our cast in the generics code -- it happens later, when someone calls getArray(), without warning.

If we didn't have this getArray() method, then this class would be safe. With this method, it is unsafe. What characteristic makes it unsafe? It returns bars as type Bar[], which depends on the "lie" we made earlier. Since the lie is not true, it causes problems. If the method had instead returned the array as type Object[], then it would be safe, since it does not depend on the "lie".

People will tell you to not do such a cast like this, because it causes cast exceptions in unexpected places as seen above, not in the original place where the unchecked cast was. The compiler will not warn you that getArray() is unsafe (because from its point of view, given the types you told it, it is safe). Thus it depends on the programmer to be diligent about this pitfall and not to use it in an unsafe way.

However, I would argue that this is not a big concern in practice. Any well-designed API will never expose internal instance variables to the outside. (Even if there is a method to return the contents as an array, it would not return the internal variable directly; it would copy it, to prevent outside code from modifying the array directly.) So no method will be implemented like getArray() is anyway.

OTHER TIPS

As opposed to lists, Java's array types are reified, which means that the runtime type of Object[] is distinct from String[]. Therefore, when you write

Bar[] bars = (Bar[]) new Object[];

you have created an array of runtime type Object[] and have "cast" it to Bar[]. I say "cast" within quotes because this is not a real checked-cast operation: it is just a compile-time directive which allows you to assign an Object[] into a variable of type Bar[]. Naturally, this opens the door to all kinds of runtime type errors. Whether it will actually create the errors is entirely up to your programming prowess and attentiveness. Therefore, if you feel up to it, then it is OK to do it; if you don't or this code is a part of a larger project with many developers, then it is a dangerous thing to do.

Ok, I've played with this construct for a bit and it can be a REAL mess.

I think the answer to my question is: Everything works fine as long as you always handle the array as generic. But as soon as you try to treat it in a non-generic way, there's trouble. Let me give a couple of examples:

  • Inside Foo<Bar>, I can create the array as shown and work with it just fine. This is because (if I understand correctly) the compiler "erases" the Bar-type and simply turns it into Object everywhere. So essentially inside Foo<Bar> you are just handling an Object[], which is fine.
  • But if you have a function like this inside Foo<Bar>, which provides access to the array:

    public Bar[] getBars(Bar bar) {
        Bar[] result = (Bar[]) new Object[1];
        result[0] = bar;
        return result;
    }
    

    you can run into some serious issues, if you use it somewhere else. Here are some examples of the craziness (most of it actually makes sense, but it seems crazy at first sight) you get:

    • String[] bars = new Foo<String>().getBars("Hello World");

      will cause java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

    • for (String bar: new Foo<String>().getBars("Hello World"))

      will also cause the same java.lang.ClassCastException

    • but

      for (Object bar: new Foo<String>().getBars("Hello World"))
          System.out.println((String) bar);
      

      works...

    • Here's one that doesn't make sense to me:

      String bar = new Foo<String>().getBars("Hello World")[0];
      

      will cause the java.lang.ClassCastException, too, even though I'm not assigning it to a String[] anywhere.

    • Even

      Object bar = new Foo<String>().getBars("Hello World")[0];
      

      will cause the same java.lang.ClassCastException!

    • Only

      Object[] temp = new Foo<String>().getBars("Hello World");
      String bar = (String) temp[0];
      

      works...

    and none of these throw any compile-time errors, by the way.

  • Now, if you have another generic class, though:

    class Baz<Bar> {
        Bar getFirstBar(Bar bar) {
            Bar[] bars = new Foo<Bar>().getBars(bar);
            return bars[0];
        }
    }
    

    The following works just fine:

    String bar = new Baz<String>().getFirstBar("Hello World");
    

Most of this makes sense, once you realize that, after type-erasure, the getBars(...)-function actually returns an Object[], independent of Bar. This is why you can't (at runtime) assign the return value to a String[] without generating an exception, even if Bar was set as String. Why this prevents you to index into the array without first casting it back to an Object[] beats me, though. The reason why things work fine in the Baz<Bar>-class is that the Bar[] there will also be turned into an Object[], independent of Bar. Thus, this would be equivalent to casting the array to Object[], then indexing it, and then casting the returned entry back to String.

Overall, after seeing this, I'm definitely convinced that using this way to create arrays is a really bad idea, unless you don't ever return the array to anywhere outside your generic class. For my purposes, I'll be using a Collection<...> instead of an array.

Everything works okay until you want to use something in that array as it would of type Bar (or whatever type you initialize the generic class with) and not as an Object which it really is. For example having the method:

<T> void init(T t) {
    T[] ts = (T[]) new Object[2];
    ts[0] = t;
    System.out.println(ts[0]);
}

seems to work okay for all types. If you change it to:

<T> T[] init(T t) {
    T[] ts = (T[]) new Object[2];
    ts[0] = t;
    System.out.println(ts[0]);
    return ts;
}

and call it with

init("asdf");

it still works fine; but when you want to really use the real T[] array (which should be String[] in the above example):

String[] strings = init("asfd");

then you have a problem, since Object[] and String[] are two distinct classes and what you have is Object[] so a ClassCastException is thrown.

The problem arises more quickly if you try a bounded generic type:

<T extends Runnable> void init(T t) {
    T[] ts = (T[]) new Object[2];
    ts[0] = t;
    System.out.println(ts[0]);
    ts[0].run();
} 

As a good practice, try avoiding using generics and arrays since they do not mix very well together.

The cast:

  Bar[] bars = (Bar[]) new Object[];

is an operation that will happen at runtime. If the runtime type of Bar[] is anything other than Object[] then this will generate a ClassCastException.

Therefore, if you place bounds on Bar as in <Bar extends Something> it will fail. This is because the runtime type of Bar will be Something. If Bar doesn't have any upper bounds then it's type will be erased to Object and the compiler will generate all the relevant casts for placing objects into the array or for reading from it.

If you try to assign bars to something with a runtime type that isn't Object[] (e.g String[] z = bars) then the operation will fail. The compiler warns you about this usecase with the "unchecked cast" warning. So the following will fail even though it compiles with a warning:

class Foo<Bar> {
    Bar[] get() {
       return (Bar[])new Object[1];
    }
}
void test() {
    Foo<String> foo = new Foo<>();
    String[] z = foo.get();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top