简体   繁体   中英

StackOverflowError while writing Parcelable

I have two classes, let's assume they look like these:

Movie.java

public class Movie implements Parcelable { 
     private int year;
     private List<Actor> actors;

     // Constructor, getters and setters, Parcelable implementation
}

Actor.java

public class Actor implements Parcelable {
    private String name;
    private Movie movie;

    // Constructor, getters and setters, Parcelable implementation
}

And now when I'm trying to write Movie into parcelable I get StackOverflowError:

java.lang.StackOverflowError
    at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1012)
    at java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1535)
    at java.lang.ClassLoader.getClassLoadingLock(ClassLoader.java:463)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at android.os.Parcel.writeParcelable(Parcel.java)
    atcom.example.android.data.model.Actor.writeToParcel(Actor.java:58)
    at android.os.Parcel.writeTypedList(Parcel.java:1106)
    at com.example.android.data.model.Movie.writeToParcel(Movie.java:70)

I understand that here is problem with Nested classes, because when it tries to write Movie into Parcel, it tries to write Actor, but in Actor it tries to write Movie again.

QUESTION

How to avoid problem with nested classes?

Don't store the Movie inside the Actor . The two-way relationship between them is creating the error.

Also, it doesn't even make sense that the Actor should store a Movie . A real life actor can be in many movies, or none, or maybe they only do TV acting.

Implement an Externalizable instead. Then use writeObject to serialize the references with account for object identity.

Java serialization protocol was designed to handle circular interdependencies between objects. Parcelables do not support those by design — attempting to hack in such support would pointlessly duplicate work, already done by creators of ObjectInputStream and ObjectOutputStream . Note, that I am not suggesting to implement Serializable (which is slow, because of being reflection-based), but rather implementing Externalizable, which is basically the same thing as Parcelable, except it plays well with serialization.

ObjectOutputStream itself is neither Serializable nor Parcelable, but you can direct it to ByteArrayOutputStream and pass resulting byte array around:

public static byte[] serialize(Externalizable object) {
    final ByteArrayOutputStream buffer = new ByteArrayOutputStream();

    ObjectOutputStream objectStream = null;
    try {
        objectStream = new ObjectOutputStream(buffer);
        objectStream.writeObject(object);
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        if (objectStream != null) {
            try { objectStream.close(); } catch (IOException ignored) {}
        }
    }

    return buffer.toByteArray();
}

public static <T extends Externalizable> T deserialize(byte[] bytes) {
    ObjectInputStream objectStream = null;
    try {
        objectStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
        return (T) objectStream.readObject();
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException(e);
    } finally {
        if (objectStream != null) {
            try { objectStream.close(); } catch (IOException ignored) {}
        }
    }
}

And here is how your classes would look now:

Actor:

class Actor implements Externalizable {
  private String name;
  private Movie movie;

  public Actor(String name, Movie movie) {
    this.name = name;
    this.movie = movie;
  }

  // required by Externalizable contract
  public Actor() {
  }

  @Override
  public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {
    name = input.readUTF();
    movie = (Movie) input.readObject();
  }

  @Override
  public void writeExternal(ObjectOutput output) throws IOException {
    output.writeUTF(name);
    output.writeObject(movie);
  }

  ...
}

Movie:

class Movie implements Externalizable {
  private List<Actor> actors;

  private int year;

  public Movie(int year) {
    this.year = year;

    actors = new ArrayList<>();
  }

  public void addActors(Actor... actors) {
    Collections.addAll(this.actors, actors);
  }

  // required by Externalizable contract
  public Movie() {
  }

  @Override
  @SuppressWarnings("unchecked")
  public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {
    year = input.read();
    actors = (List<Actor>) input.readObject();
  }

  @Override
  public void writeExternal(ObjectOutput output) throws IOException {
    output.write(year);
    output.writeObject(actors);
  }

  ...
}

I have just tested on my device and was able to successfully pass cross-referencing Movie/Actor across Activities via Intent.

This will create an infinite loop while writing the Movie object to a Parcelable , if you really need the reference to the Movie from within the Actor you could try adding an ID to the Movie class and give this ID to the Actor , later you could use this ID to track the corresponding Movie , altho Im not sure if this still meets your requirements. Anyway, using a circular reference is strongly disencouraged in OO languages.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM