Reputation:
I have class Solution, that contains inner class A, class B, class C. Class B extends class A, class C extends class B.
I want class C to be Serializable, so I implements Serializable in class C and in class Solution (class C is inner class of class Solution).
Because class C extends class B and class B extends class A, I create a no-arg constructor in class B and class A (I don't want them implements Serializable).
So the following code:
public class Solution implements Serializable {
public class A {
String name = "A";
public A(String name) {
this.name += name;
}
public A() {}
}
public class B extends A {
String name = "B";
public B(String name) {
super(name);
this.name += name;
}
public B() {}
}
public class C extends B implements Serializable {
String name;
public C(String name) {
super(name);
this.name = name;
}
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Solution.C c = new Solution().new C("C");
System.out.println(c.name);
System.out.println("serialization");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\1.txt"));
oos.writeObject(c);
oos.close();
System.out.println("deserialization");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\1.txt"));
Solution.C c1 = (Solution.C) ois.readObject();
ois.close();
System.out.println(c1.name);
}
}
throws the following exception:
C
serialization
deserialization
Exception in thread "main" java.io.InvalidClassException: Test3.Solution$C; no valid constructor
Upvotes: 0
Views: 580
Reputation: 25950
What happens in your case is that the deserialization code is eventually going to construct an instance of ObjectStreamClass
for your class Solution.C
. In the constructor of this class, it tries to find a constructor of the class to deserialize that is suitable. In your case, it will call ReflectionFactory#newConstructorForSerialization
. Here's what the documentation of this method says:
Returns a constructor that allocates an instance of cl and that then initializes the instance by calling the no-arg constructor of its first non-serializable superclass. This is specified in the Serialization Specification, section 3.1, in step 11 of the deserialization process. If cl is not serializable, returns cl's no-arg constructor. If no accessible constructor is found, or if the class hierarchy is somehow malformed (e.g., a serializable class has no superclass), null is returned.
Your first non-serializable superclass is B, however B has no no-arg constructor because all its constructors have a synthetic parameter of type Solution
. Thus, no constructor is found and deserialization fails. When you make B
and A
serializable, it works because newConstructorForSerialization
will go up to Object
(see loop below) and use its no-arg constructor.
public final Constructor<?> newConstructorForSerialization(Class<?> cl) {
Class<?> initCl = cl;
while (Serializable.class.isAssignableFrom(initCl)) {
Class<?> prev = initCl;
if ((initCl = initCl.getSuperclass()) == null ||
(!disableSerialConstructorChecks && !superHasAccessibleConstructor(prev))) {
return null;
}
}
Constructor<?> constructorToCall;
try {
constructorToCall = initCl.getDeclaredConstructor();
int mods = constructorToCall.getModifiers();
if ((mods & Modifier.PRIVATE) != 0 ||
((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
!packageEquals(cl, initCl))) {
return null;
}
} catch (NoSuchMethodException ex) {
return null;
}
return generateConstructor(cl, constructorToCall);
}
Can you just avoid doing this altogether? Reading the answers to this question, it seems strongly discouraged to serialized inner instances like you're doing now (full explanation in the link). You're giving yourself a hard time trying to do this, but at least now we know why this was failing.
Upvotes: 2
Reputation: 1
Default constructor for parent classes are not required. Making class A as serializable it will work. You may also remove the serialization from class C.
public class Solution implements Serializable {
public class A implements Serializable{
String name = "A";
public A(String name) {
this.name += name;
}
public A() {}
}
public class B extends A {
String name = "B";
public B(String name) {
super(name);
this.name += name;
}
public B() {}
}
public class C extends B {
String name;
public C(String name) {
super(name);
this.name = name;
}
}
}
Upvotes: 0