Не разбирам защо възниква това ClassCastException

// 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

Защо предаването към Uri[] се прекъсва, когато Uri е Parcelable?


person Tom Fobear    schedule 05.01.2012    source източник
comment
Uri наследява ли от Parcelable или обратното?   -  person fge    schedule 05.01.2012


Отговори (5)


За съжаление няма начин за кастиране по този начин за масиви в Java. Ще трябва да итерирате своя масив и да преобразувате всеки обект поотделно.

Причината за това е безопасността на типа, JVM просто не може да гарантира, че съдържанието на вашия масив може да бъде прехвърлено към Uri, без да се налага да ги итерирате, поради което трябва да ги итерирате и да ги прехвърляте поотделно.

По принцип, тъй като Parcelable може да бъде наследен от други обекти, няма гаранция, че масивът съдържа само Uri обекта. Въпреки това кастингът към супертип ще работи, тъй като тогава безопасността на типа ще бъде наред.

person Oscar Gomez    schedule 05.01.2012
comment
Помислих си същото (тъй като в C# е така), така че потърсих ковариацията на java масив и изглежда, че се поддържа (и е дупка в безопасността на типа). Моите референции просто остарели ли са с текущата JVM? : angelikalanger.com/Articles/Papers/JavaGenerics/ , c2.com/cgi/wiki?JavaArraysBreakTypeSafety - person Kevin Coulombe; 05.01.2012
comment
Мисля, че безопасността на типа е свързана с генеричните, които са ограничени до времето за компилиране. Масивите не се генерират. Те са референции и можете да правите кастинг. - person Bhesh Gurung; 05.01.2012
comment
правилно, но Parcelable[] беше нов Uri[] на първо място... така че трябва просто да е, въпреки че Uri е Parcelable, но Uri[] не е Parcelable[]... - person Tom Fobear; 05.01.2012
comment
Uri[] е Parcelable[], но List<Uri> не е List<Parcelable>. - person Bhesh Gurung; 05.01.2012
comment
@βnɛƨn Ǥʋяʋиɢ: Но Parcelable[] е List‹Parcelable› ... хаха, това става объркващо... между другото, смисълът на кода ми казва, че getParcelableArrayExtra наистина връща Parcelable[] - person Tom Fobear; 05.01.2012
comment
Масивът не може да бъде прехвърлен към списък, независимо от типа на елемента. Това, което ще трябва да направите, вероятно е да преминете през елементите в Parcelable[] и да ги прехвърлите/зададете в Uri[]. Проблемът с ковариацията е, че ако можете да прехвърлите своя Uri[] в Parcelable[], тогава можете да направите това [array[0] = new Parcelable()], което ще вмъкне нещо, което не е Uri, в това, което всъщност е Uri []. - person Kevin Coulombe; 05.01.2012
comment
@KevinCoulombe: Ако методът връща нещо като Parcelable[] parcelables = new Uri[]{};, тогава Uri[] uris = (Uri[])intent.getParcelableArrayExtra(EXTRA_FILE_UPLOAD_URIS); определено не трябва да се проваля по време на изпълнение с CCE. Въпреки че Parcelable е интерфейс, но дори и да е клас тогава, array[0] = new Parcelable() се проваля по време на изпълнение с ArrayStoreException. И не знам каква е разликата, когато преминавате през целия масив и прехвърляте всеки отделен елемент. - person Bhesh Gurung; 05.01.2012
comment
@βnɛƨnǤʋяʋиɢ също няма смисъл за мен, но зациклянето на елементите работи - person Tom Fobear; 05.01.2012

Използвайте този метод, работи за мен.

Parcelable[] ps = getIntent().getParcelableArrayExtra();
Uri[] uri = new Uri[ps.length];
System.arraycopy(ps, 0, uri, 0, ps.length);
person solo    schedule 14.02.2013

Масивите имат полиморфно поведение - само общите типове нямат.

Тоест, ако Uri имплементира Parcelable тогава

можеш да кажеш:

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

НЕ МОЖЕШ да кажеш:

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

Както виждате, можем да прехвърлим pa обратно към Uri[]. Тогава какъв е проблемът? Това ClassCastException се случва, когато приложението ви бъде унищожено и по-късно запазеният масив се създава отново. Когато се пресъздаде, времето за изпълнение не знае какъв вид масив (Uri[]) е бил, така че просто създава Parcelable[] и поставя елементи в него. Следователно ClassCastException, когато се опитате да го прехвърлите към Uri[].

Имайте предвид, че изключението не се случва (теоретично), когато процесът не е убит и първоначално създаденият масив (Uri[]) се използва повторно между кръговете за запазване/възстановяване на състоянието. Например, когато промените ориентацията.

Просто исках да изясня ЗАЩО се случи. Ако искате решение, @solo предостави прилично.

наздраве

person Zsolt Safrany    schedule 19.11.2013
comment
Това е правилният отговор. Попаднах на спорадично повторно появяване на ClassCastException, когато услугата ми се опита да прехвърли Parcelable[] от Bundle на AccountManager към Account[]. Случвало се е много рядко след телефонни обаждания и накрая – ето ви виновника! - person user1643723; 15.08.2014

Мисля, че това, което се случва, е нещо като следното:

class Parent { }

class MaleParent extends Parent { }

class FemaleParent extends Parent { }

Ако сценарият е подобен на горния, тогава следното ще се провали по време на изпълнение:

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

Следното не води до изключение:

Parent[] parents = new MaleParent[]{};
MaleParent[] maleParents = (MaleParent[]) parents;
person Bhesh Gurung    schedule 05.01.2012

https://stackoverflow.com/a/8745966/72437 и https://stackoverflow.com/a/20073367/72437 има добре обяснение защо се случва такъв срив.

https://stackoverflow.com/a/14866690/72437 също има пример как можем да заобиколим това.

Бих искал да предоставя примери за код, за да помогна за по-добро разбиране.

Позволете ми да демонстрирам пример защо подобен инцидент понякога се проваля.

Пример за демонстрация защо се случва такъв срив

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);
            }
        }
    }    
}

Пример за демонстрация как можем да поправим това

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);
            }
        }
    }    
}
person Cheok Yan Cheng    schedule 14.06.2019