24
// Application ...
Intent i = new Intent();
i.putExtra(EXTRA_FILE_UPLOAD_URIS, mGalleryAdapter.getItems()); 

Uri[] getItems() { return mItems; }

// Service ...
intent.getParcelableArrayExtra(EXTRA_FILE_UPLOAD_URIS); //works, returns Parcelable[]
Uri[] uris = (Uri[])intent.getParcelableArrayExtra(EXTRA_FILE_UPLOAD_URIS);
// ... Breaks with ClassCastException

Why does the cast to Uri[] break, when Uri is Parcelable?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Tom Fobear
  • 6,729
  • 7
  • 42
  • 74

5 Answers5

29

Use this method, it work for me.

Parcelable[] ps = getIntent().getParcelableArrayExtra();
Uri[] uri = new Uri[ps.length];
System.arraycopy(ps, 0, uri, 0, ps.length);
solo
  • 300
  • 3
  • 4
22

Unfortunately there is no way to cast like that for arrays in Java. You will have to iterate your array and cast each object individually.

The reason for this is type Safety, the JVM simply cannot ensure that the contents of your array can be casted to Uri, without having to iterate thru them, which is why you have to iterate them and cast them individually.

Basically because Parcelable could be inherited by other Objects, there is no guarantee that the Array contains only Uri objects. However casting to a supertype would work since then type safety would be ok.

Oscar Gomez
  • 18,436
  • 13
  • 85
  • 118
  • I thought the same (since it's that way in C#) so I looked up java array covariance and it seems to be supported (and to be a hole in type safety). Are my references simply outdated with the current JVM? : http://www.angelikalanger.com/Articles/Papers/JavaGenerics/ArraysInJavaGenerics.htm , http://c2.com/cgi/wiki?JavaArraysBreakTypeSafety – Kevin Coulombe Jan 05 '12 at 16:38
  • 1
    I think the type safety is related to generics, which is limited to compiletime. Arrays are not generified. They are references and you can do casting. – Bhesh Gurung Jan 05 '12 at 16:41
  • right, but the Parcelable[] was a new Uri[] in the first place... so it must just be even though Uri is a Parcelable but Uri[] is not a Parcelable[]... – Tom Fobear Jan 05 '12 at 16:48
  • `Uri[]` is `Parcelable[]`, but `List` is not `List`. – Bhesh Gurung Jan 05 '12 at 17:03
  • @βнɛƨн Ǥʋяʋиɢ: But Parcelable[] is List ... lol this is getting confusing... by the way, code sence is telling me getParcelableArrayExtra indeed returns a Parcelable[] – Tom Fobear Jan 05 '12 at 17:08
  • An array cannot be cast to a List no matter the item type. What you will need to do is likely to loop through the items in the Parcelable[] and cast/set them into a Uri[]. The problem with covariance is that if you can cast your Uri[] into a Parcelable[], then you can do this [array[0] = new Parcelable()] which would insert something that is not a Uri into what is actually a Uri[]. – Kevin Coulombe Jan 05 '12 at 17:15
  • 1
    @KevinCoulombe: If the method is returning something like `Parcelable[] parcelables = new Uri[]{};` then `Uri[] uris = (Uri[])intent.getParcelableArrayExtra(EXTRA_FILE_UPLOAD_URIS);` should definitely not fail at runtime with CCE. Even though `Parcelable` is an interface, but even if were a class then, `array[0] = new Parcelable()` fails at runtime with `ArrayStoreException`. And I don't know what difference it makes when looping through the whole array and casting each individual element. – Bhesh Gurung Jan 05 '12 at 17:42
  • 1
    @βнɛƨнǤʋяʋиɢ doesnt make sence to me either but looping the elements works – Tom Fobear Jan 05 '12 at 18:29
10

Arrays do have polymorphic behaviour - only generic types don't have.

That is, if Uri implements Parcelable then

you CAN say:

Parcelable[] pa = new Uri[size];
Uri[] ua = (Uri[]) pa;

you CANNOT say:

List<Parcelable> pl = new ArrayList<Uri>();

As you see we can cast pa back to Uri[]. Then what is the problem? This ClassCastException happens when your app is killed and later the saved array is recreated. When it is recreated the runtime does not know what kind of array (Uri[]) it was so it just creates a Parcelable[] and puts elements into it. Hence the ClassCastException when you try to cast it to Uri[].

Note that the exception does not happen (theoretically) when the process is not killed and the originally created array (Uri[]) is reused between state save/restore rounds. Like when you change the orientation.

I just wanted to make clear WHY it happened. If you want a solution @solo provided a decent one.

Cheers

Zsolt Safrany
  • 13,290
  • 6
  • 50
  • 62
  • 1
    This is THE right answer. I stumbled upon sporadically re-appearing ClassCastException when my Service attempted to cast Parcelable[] from AccountManager's Bundle to Account[]. It happened very rarely after phone calls, and finally - here is the culprit! – user1643723 Aug 15 '14 at 11:08
0

https://stackoverflow.com/a/8745966/72437 and https://stackoverflow.com/a/20073367/72437 have well explanation on why such crash happens.

https://stackoverflow.com/a/14866690/72437 also has a example on how we can workaround with this.

I would like to provide code examples, to help better understanding.

Let me demonstrate an example on why such incident fails sometimes.

An example to demonstrate why such crash happens

package javaapplication12;

/**
 *
 * @author yccheok
 */
public class JavaApplication12 {

    public static class Parcelable {

    }

    public static class Uri extends Parcelable {

    }

    public static Parcelable[] getParcelableArrayExtraDuringLowMemoryRestoration() {    
        // The Android system has no way to know it needs to create Uri[],
        // during low memory restoration process.

        Parcelable[] parcelables = new Parcelable[3];

        for (int i=0; i<parcelables.length; i++) {
            parcelables[i] = new Uri();
        }

        return parcelables;
    }

    public static Parcelable[] getParcelableArrayExtra() {        
        // The Android system has enough information that it needs to create Uri[]

        Uri[] temp = new Uri[3];
        for (int i=0; i<temp.length; i++) {
            temp[i] = new Uri();
        }
        Parcelable[] parcelables = temp;
        return parcelables;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // OK
        {
            // true
            System.out.println(getParcelableArrayExtra() instanceof Uri[]);

            Uri[] uris = (Uri[])getParcelableArrayExtra();
            for (Uri uri : uris) {
                System.out.println(uri);
            }
        }

        // Crash!
        {
            // false
            System.out.println(getParcelableArrayExtraDuringLowMemoryRestoration() instanceof Uri[]);

            // ClassCastException.
            Uri[] uris = (Uri[])getParcelableArrayExtraDuringLowMemoryRestoration();         
            for (Uri uri : uris) {
                System.out.println(uri);
            }
        }
    }    
}

An example to demonstrate on how we can fix this

package javaapplication12;

/**
 *
 * @author yccheok
 */
public class JavaApplication12 {

    public static class Parcelable {

    }

    public static class Uri extends Parcelable {

    }

    public static Parcelable[] getParcelableArrayExtraDuringLowMemoryRestoration() {    
        // The Android system has no way to know it needs to create Uri[],
        // during low memory restoration process.
        Parcelable[] parcelables = new Parcelable[3];

        for (int i=0; i<parcelables.length; i++) {
            parcelables[i] = new Uri();
        }

        return parcelables;
    }

    public static Parcelable[] getParcelableArrayExtra() {        
        // The Android system has enough information that it needs to create Uri[]
        Uri[] temp = new Uri[3];
        for (int i=0; i<temp.length; i++) {
            temp[i] = new Uri();
        }
        Parcelable[] parcelables = temp;
        return parcelables;
    }

    private static Uri[] safeCastToUris(Parcelable[] parcelables) {
        if (parcelables instanceof Uri[]) {
            return (Uri[])parcelables;
        }
        int length = parcelables.length;
        Uri[] uris = new Uri[length];
        System.arraycopy(parcelables, 0, uris, 0, length);
        return uris;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // OK
        {
            Uri[] uris = safeCastToUris(getParcelableArrayExtra());
            for (Uri uri : uris) {
                System.out.println(uri);
            }
        }

        // OK too!
        {
            Uri[] uris = safeCastToUris(getParcelableArrayExtraDuringLowMemoryRestoration());            
            for (Uri uri : uris) {
                System.out.println(uri);
            }
        }
    }    
}
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
0

I think what's happening is something as follows:

class Parent { }

class MaleParent extends Parent { }

class FemaleParent extends Parent { }

If the scenario is something as above then the following will fail at runtime:

Parent[] parents = new FemaleParent[]{};
MaleParent[] maleParents = (MaleParent[]) parents;

Something as follows does not raise the exception:

Parent[] parents = new MaleParent[]{};
MaleParent[] maleParents = (MaleParent[]) parents;
Bhesh Gurung
  • 50,430
  • 22
  • 93
  • 142