71

I understand Enum is Serializable. Hence, it is safe to do so. (selectedCountry is enum Country)

Original enum without customer member variables

public enum Country {
    Australia,
    Austria,
    UnitedState;
}

Fragment

@Override
public void onActivityCreated (Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        selectedCountry = (Country)savedInstanceState.getSerializable(SELECTED_COUNTRY_KEY);
    }
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putSerializable(SELECTED_COUNTRY_KEY, selectedCountry);
}

However, what if I have non-serializable members in custom enum class? For instance,

Original enum customer member variables

package org.yccheok;

import org.yccheok.R;

/**
 *
 * @author yccheok
 */
public enum Country {
    Australia(R.drawable.flag_au),
    Austria(R.drawable.flag_at),
    UnitedState(R.drawable.flag_us);

    Country(int icon) {
        this.icon = icon;
        nonSerializableClass = new NonSerializableClass(this.toString());
    }

    public int getIcon() {
        return icon;
    }

    public static class NonSerializableClass {
        public NonSerializableClass(String dummy) { this.dummy = dummy; }
        public String dummy;
    }

    private final int icon;

    public NonSerializableClass nonSerializableClass;
}

I tested. It works. (I tested by printing out all the value of member variables before and after serialization. They are same before and after)

However, I do not understand why it works? As I do not provide proper readObject and writeObject, as required by Serializable interface.

As pointed in Effective Java Item 75: Consider using a custom serialized form, do I need to provide my own readObject and writeObject, if I have custom member variables in my enum?

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

3 Answers3

119

The reason it works is that serialization process for Enum's is different from serialization process for other classes. From the official documentation:

1.12 Serialization of Enum Constants

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream.

That means, all your custom fields won't be serialized. In your case everything works well because your application process is still running and you are getting the same Enum instance that you passed to savedInstanceState.putSerializable.

But imagine a situation where your app get killed because Android has not enough memory. The next time user opens the app you will get a new Enum instance and all custom fields will have been lost and reinitialized by the constructor. Thus, mutable fields in an enum are always effectively transient.

matdev
  • 4,115
  • 6
  • 35
  • 56
Vladimir Mironov
  • 30,514
  • 3
  • 65
  • 62
  • 1
    But do you have idea how do they handle some customer member variables inside enum class, like `nonSerializableClass`, `icon`, ...? – Cheok Yan Cheng Mar 20 '13 at 11:31
  • They are **NOT** serilized at all. – Vladimir Mironov Mar 20 '13 at 11:33
  • I expect it to not serialized and broken as well. However, my testing shows serialization work some how... – Cheok Yan Cheng Mar 20 '13 at 11:36
  • Ya. I think most probably is the in-memory enum which causes the thing to "work". – Cheok Yan Cheng Mar 20 '13 at 12:04
  • In that case, I will use Parcelable (Using enum.name instead of enum.ordinal, because new enum's member can break ordinal). Thanks for the explanation why Serialization seems to "work" (In fact it is not working). – Cheok Yan Cheng Mar 20 '13 at 12:10
  • Correction to you last paragraph: **custom fields can never be lost**. As mentioned in the linked documentation, when the enum value (say Australia) is deserialized, Country.valueOf("Australia") is invoked and the unique Country.Australia enum instance is returned (with all its custom fields). For the purpose of this discussion, think about Contry.Australia as a static field and the serialization just storing a reference to that static field (the name). – Bogdan Calmac Oct 11 '14 at 20:25
  • @BogdanCalmac Yes, they can. Application lifecycle and JVM lifecycle are not the same in Android. The same android application can survive several JVM shutdowns and after each of them all static references are cleared. – Vladimir Mironov Oct 12 '14 at 15:31
  • I don't think this is relevant. Irrespective of JVM vs Android, enums are de-serialized by using the **valueOf(String)** factory method, not by instantiating a new object and populating the fields. Can you actually prove with code that the custom fields of an enum are lost during de-serialization? – Bogdan Calmac Oct 12 '14 at 15:44
  • @BogdanCalmac, no, I can't, because there is no way to control an application lifecycle. – Vladimir Mironov Oct 13 '14 at 04:38
  • @VladimirMironov You can control Application Lifecycle from instrumentation tests. – Igor Čordaš May 26 '16 at 07:48
10

As per Serializable documentation, readObject and writeObject are not needed at all, so your question might be not fully correct.

Serializable is a marker interface and doesn't have any methods.

I refer you to this answer which provides additional details about the Serialization implementation (which explains why you don't necessary need write and read functions).

And, as mentioned here by Dianne Hackborn, Parcelable is much more efficient for Android.

If you're particularly interested in Enum, refer to below paragraph:

1.12 Serialization of Enum Constants

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream.

The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored--all enum types have a fixed serialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

So, I don't think that the Enum is the right choice to test internal non-serializable classes work.

Community
  • 1
  • 1
sandrstar
  • 12,503
  • 8
  • 58
  • 65
  • Ya. I came across Parcelable is more efficient. However, I want to reduce code (Less code less error), by directly using Serializable characteristics of enum. Just that, I'm not sure without providing my own readObject and writeObject (As pointed in Effective Java #75), will custom member variables in enum behave correctly. – Cheok Yan Cheng Mar 20 '13 at 11:27
  • Please, refer my updated answer with details from Serialization Specification which is quite specific for Enums. – sandrstar Mar 20 '13 at 11:56
4

The serialization of enum members is not working. The nonSerializable field is never serialized as @vmironov answered. Here is a test:

public enum Country {
Australia;

    public static class NonSerializableClass {
       public NonSerializableClass() {}
       public String dummy;
    }

    public NonSerializableClass nonSerializableClass;
}

The code writing the enum to the serialization stream:

public class SerializationTestWrite {
    public static void main(String[] args) throws Exception{
        FileOutputStream f = new FileOutputStream("tmp");
        ObjectOutput s = new ObjectOutputStream(f);

        Country.Australia.nonSerializableClass = new Country.NonSerializableClass();
        Country.Australia.nonSerializableClass.dummy = "abc";

        s.writeObject(Country.Australia);
        s.flush();

        System.out.println(Country.Australia.nonSerializableClass.dummy);
    }
}    

On writing the value of the dummy field is: abc

The code reading the enum from the serialization stream:

public class SerializationTestRead {
    public static void main(String[] args) throws Exception{
        FileInputStream in = new FileInputStream("tmp");
        ObjectInputStream so = new ObjectInputStream(in);
        Country readed = (Country) so.readObject();

        System.out.println(readed.nonSerializableClass);
    }
}

But on reading, the value of the field nonSerializableClass is: null

dcernahoschi
  • 14,968
  • 5
  • 37
  • 59
  • Thanks. I think most probably my testing method is wrong, as I am using back the same process for serialization and de-serialization. – Cheok Yan Cheng Mar 20 '13 at 12:05
  • @dcernahoschi, yep, this result is expected. But if you add all code from `SerializationTestRead.main` to the end of `SerializationTestWrite.main` and run it, you will see that `nonSerializableClass` is **not** null. – Vladimir Mironov Mar 20 '13 at 12:12