reverse_engineer
reverse_engineer

Reputation: 4269

java transient variable type must be known for serialization?

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.

Upvotes: 3

Views: 2198

Answers (4)

user3746869
user3746869

Reputation: 13

I hope it will help you

Person.java

    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
        }

        private void writeObject(ObjectOutputStream os) {
            try {
                os.defaultWriteObject();
                os.writeUTF(this.address.getAddress());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void readObject(ObjectInputStream is) {
            try {
                is.defaultReadObject();
                this.address = new Address(is.readUTF());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

    }

Address.java

    public class Address {
        private String address;
        public String getAddress()  { return address; }
        public void setAddress() { this address = address}
    }

Test.java

 public class Test {
    public static void main(String...args) throws Exception {
        Person person = new Person("John Doe");
        person.setAddress("test address");

        try {
            FileOutputStream fis = new FileOutputStream("simple.txt");
            ObjectOutputStream os = new ObjectOutputStream(fis);
            os.writeObject(person);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream fis = new FileInputStream("simple.txt");
            ObjectInputStream os = new ObjectInputStream(fis);
            person = (Person) os.readObject();           

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Upvotes: 0

matts
matts

Reputation: 6897

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

Upvotes: 4

JuanZe
JuanZe

Reputation: 8167

For a class to be serialized, two conditions must be met:

  • The class must implement the java.io.Serializable interface.
  • All of the fields in the class must be serializable. If a field is not serializable, it must be marked transient.

You need to know Address class in order to build the project. That's unrelated to serialization of the object. Address class must be available at compile time...

Upvotes: 0

Peter Lawrey
Peter Lawrey

Reputation: 533820

The problem is that to load an object, it must first load the class. To load the class, it must resolve the class of all the fields and if you don't have class Address you cannot load the class or example the field to determine that it is transient.

You can solve this by providing a dummy Address class which is at the end of your class path. i.e. is only used if not defined.

Upvotes: 2

Related Questions