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