I noticed that in Java, you can use a class which has members of types that are unavailable to you. For example, say you have a precompiled .class
file for the following Person
class; but not for Address
, which it uses:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String sex;
private String name;
private transient Address address;
public Person(String sex, String name) {
this.sex = sex; this.name = name;
}
public void setAddress(Address address) {
this.address = address
}
}
You can instantiate this class with Person person = Person("Male", "Andrew");
even if the type Address
is not available to your Java project.
Now the problem is, if you want to serialize a Person
object, you cannot do this unless the Address
type is available (i.e. can be loaded by the class loader), even though the Address
field is transient.
For some reason Java needs to know the type Address
even though it will not need to serialize it...
Any help on how I could serialize Person
anyway without having to redefine a Person
class without an address? And any explanation of why Java needs to know the type of address although it will not need to serialize it?
Many thanks.
I am operating under the assumption that you have a .class
file for Person
, but no .class
file for Address
. (Perhaps Person
is a dependency from a different project?)
The reason you cannot serialize Person
(even though the Address
field is transient) is that the serialization API uses the Java reflection API. Although the JVM will load a class without loading all of its dependencies, the reflection API is not as forgiving.
The first time the reflection API is used to retrieve field information for a particular class, it retrieves and caches information for all fields in the class. To do this, it has to resolve the types for every field in the class, and thus will attempt to load the class files for every field. (You can see this in the Java source code: ObjectOutputStream
uses ObjectStreamClass
, which calls Class.getDeclaredField
, which calls a private method privateGetDeclaredFields
, which resolves and caches all the field definitions for the class.)
As a workaround, if your code never actually uses any objects of type Address
, you can simply create an empty Address
class in the correct package, compile it, and add it to your class path:
public class Address { }
For those who think it isn't possible to use Person
at runtime without an Address
class definition, the following three classes demonstrate what the OP is talking about:
Person.java
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private transient Address address;
public Person(String name) { this.name = name; }
public Address getAddress() { return address; }
public String getName() { return name; }
}
Address.java
public class Address {
private String address;
public String getAddress() { return address; }
}
Test.java
import java.io.FileOutputStream;
import java.io.ObjectOuputStream;
public class Test {
public static void main(String...args) throws Exception {
Person person = new Person("John Doe");
System.out.println("Person successfully instantiated with name " + person.getName());
// now attempt to serialize
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.out"));
out.writeObject(person); // NoClassDefFoundError thrown here if Address.class doesn't exist
out.close();
}
}
Now compile Test.java
, delete Address.class
, and run Test
:
$ javac Test.java
$ rm Address.class
$ java Test
Person successfully instantiated with name John Doe
Exception in thread "main" java.lang.NoClassDefFoundError: LAddress;
at java.lang.Class.getDeclaredFields0(Native Method)
at java.lang.Class.privateGetDeclaredFields(Class.java:2308)
at java.lang.Class.getDeclaredField(Class.java:1897)
at java.io.ObjectStreamClass.getDeclaredSUID(ObjectStreamClass.java:1624)
at java.io.ObjectStreamClass.access$700(ObjectStreamClass.java:69)
at java.io.ObjectStreamClass$2.run(ObjectStreamClass.java:442)
at java.security.AccessController.doPrivileged(Native Method)
at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:430)
at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:327)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1130)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346)
at Test.main(Test.java:10)
Caused by: java.lang.ClassNotFoundException: Address
at java.net.URLClassLoader$1.run(URLClassLoader.java:217)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)
at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
... 12 more