ClassCastException when Appending Object OutputStream
The problem here is that the previous poster who gave you an appendable ObjectOutputStream
led you astray. An ObjectOutputStream
/ObjectInputStream
tries to store each object only once, and then later refer back to the object already stored. That is, in the stream you can end up with something like this if you have a bunch of objects of the same class:
CLASS_1_DESCRIPTION
OBJECT_1
REF_TO_CLASS_1
OBJECT_2
REF_TO_CLASS_1
OBJECT_3
...
When an ObjectInputStream
is converting the stream back into a bunch of objects, it maintains a list of what things it's already deserialized. The error it's telling you is that it was trying to deserialize an object, read what should have been a reference to the description of the object's class, but when it looked up that reference in its internal table it saw a String
. Quite naturally, it blew up.
I think the fix is as simple as this - in your AppendableObjectOutputStream
, change this method:
@Override
protected void writeStreamHeader() throws IOException {
// do not write a header, but reset the handle list
reset();
}
the reset()
method in ObjectOutputStream
inserts a marker into the stream saying "throw away all the state at this point". Then, when you read this back in with a ObjectInputStream
, the input stream's idea of what's been deserialized will match what the output stream thought the state was when it deserialized the stuff in the first place.
(EDIT: Answer question from comments)
The only detrimental consequences of this that I can think of:
The final file will be longer than it would have been if you'd written everything all to one
ObjectOutputStream
, especially if the sameProfile
object appears multiple times. Even if not, you'll repeat class descriptors in the stream, so lots of repetitions of {openAppendableObjectOutputStream
, write one object, close stream} could bloat the file size a bit.Related to that, after you deserialize everything you may end up with multiple copies of what should have been the identical object. For example, suppose you write a bunch of things including some
PPRestrictedAccount
objects, then close the stream, open it as anAppendableObjectOutputStream
, and write out aPPBusinessAccount
that has in itsoperators
list some of thePPRestrictedAccount
s you wrote out earlier. When you read all that back in, thePPRestrictedAccount
s you read initially won't be the same objects (that is, they won't be==
) to thePPRestrictedAccount
s that you find in thePPBusinessAccount
'soperators
list. They'll be instantiated separately. To avoid this, you'd need to de-duplicate them with areadResolve
method. Everything that was written to a singleAppendableObjectOutputStream
instance will be connected correctly however. Depending on your application, this might not be something to worry about at all.
In terms of will-this-blow-up-or-not safety, this is as safe as any other use of java serialization; there isn't anything specific about your classes that makes it work. Just be aware that any object written in multiple separate openings of the output file will be deserialized as separate copies of the original object. (Absent any readResolve
magic)
Comments
-
sasidhar almost 2 years
I have been trying to do a little project that needs an appendable ObjectOutputStream. I have gone through a couple of solutions and i found this It seemed to solve my problem at first. But on further development of my project i started getting unexpected exceptions. The following are my classes.
public class PPAccount implements Serializable { private Profile profile; private String email; private float accountBal; private boolean isActivated; private String activationCode; private ArrayList<Transaction> transactions; //a few functions } public class PPRestrictedAccount extends PPAccount { private String parentEmail; private float withdrawLimit; //a few functions } public class PPBusinessAccount extends PPAccount { private ArrayList <PPRestrictedAccount> accountOperators; //a few functions } public class PPStudentAccount extends PPAccount { private String parentEmail; //a few functions }
What i have observed is, using the this i have overridden the ObjectOutputStream and used it while i am appending the objects to the file. But what happens is if i write:
PPBusinessAccount
first, repeat any number of times... then writePPAccount
all is well.PPAccount
first, repeat.... then writePPBusinessAccount
then writePPAccount
, it writes well but while reading i get aClassCastException
.I am tried reading the Objects and storing them directly in an instance of
Object
class to avoid the class cast but stillreadObject()
throwsClassCastException
.I tried best to describe my scenario, tell if you don't get anything. Why is this happening?? has it got something to do with the header that it is writing for this first time?? Along the lines of Base class header cannot support child class?? What's the turn around?
I am doing the cast like this:
Object o = ois.readObject(); //Surprisingly exception is raised here (line:50 in DataStore) PPAccount ppa = (PPAccount)o;
The stack trace
java.lang.ClassCastException: java.lang.String cannot be cast to java.io.ObjectStreamClass at java.io.ObjectInputStream.readClassDesc(Unknown Source) at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source) at java.io.ObjectInputStream.readClassDesc(Unknown Source) at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source) at java.io.ObjectInputStream.readObject0(Unknown Source) at java.io.ObjectInputStream.readObject(Unknown Source) at java.util.ArrayList.readObject(Unknown Source) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at java.io.ObjectStreamClass.invokeReadObject(Unknown Source) at java.io.ObjectInputStream.readSerialData(Unknown Source) at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source) at java.io.ObjectInputStream.readObject0(Unknown Source) at java.io.ObjectInputStream.defaultReadFields(Unknown Source) at java.io.ObjectInputStream.readSerialData(Unknown Source) at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source) at java.io.ObjectInputStream.readObject0(Unknown Source) at java.io.ObjectInputStream.readObject(Unknown Source) at in.msitprogram.iiit.paypal.persistance.DataStore.lookupAccount(DataStore.java:50) at in.msitprogram.iiit.paypal.persistance.DataStore.writeAccount(DataStore.java:131) at in.msitprogram.iiit.paypal.console.PPNewAccountScreen.show(PPNewAccountScreen.java:78) at in.msitprogram.iiit.paypal.console.MainMenu.show(MainMenu.java:42) at in.msitprogram.iiit.paypal.PPSystem.main(PPSystem.java:17) Exception in thread "main" java.lang.NullPointerException at in.msitprogram.iiit.paypal.persistance.DataStore.lookupAccount(DataStore.java:66) at in.msitprogram.iiit.paypal.persistance.DataStore.writeAccount(DataStore.java:131) at in.msitprogram.iiit.paypal.console.PPNewAccountScreen.show(PPNewAccountScreen.java:78) at in.msitprogram.iiit.paypal.console.MainMenu.show(MainMenu.java:42) at in.msitprogram.iiit.paypal.PPSystem.main(PPSystem.java:17)
The
lookUpAccount
reads from the stream whilewriteAccount
writes to the stream, here is the code:public static PPAccount lookupAccount(String email) throws IOException, ClassNotFoundException { PPAccount account = null; //initialize it after reading from file // write code to open the files, read PPAccount foundAccount=null; ObjectInputStream ois=null; FileInputStream fis=null; File ff = new File(PPConstants.AllAccountDetails); if(!ff.exists()) { //System.out.println("Required file not found"); return null; } try { fis=new FileInputStream(PPConstants.AllAccountDetails); ois = new ObjectInputStream(fis); while(fis.available()>0 && foundAccount==null) { //Object o=null; PPAccount ppa=null; try { ppa = (PPAccount)ois.readObject(); if(ppa==null) return null; System.out.println(ppa); } catch(ClassCastException cce) { System.out.println("Class cast exception "+cce.getCause()); cce.printStackTrace(); } if(email.equals(ppa.getEmail())) { foundAccount=ppa; break; } if(ppa instanceof PPBusinessAccount) { PPBusinessAccount ppba = (PPBusinessAccount)ppa; ArrayList<PPRestrictedAccount> alist=ppba.getAccountOperators(); if(alist==null) continue; Iterator<PPRestrictedAccount> it = alist.iterator(); while(it.hasNext()) { PPRestrictedAccount ppr=(PPRestrictedAccount) it.next(); System.out.println(ppr); if(email.equals(ppr.getEmail())) { foundAccount = ppr; break; } }//iterators while loop }//if it is a businessAccount }//outer while }//try finally { if(ois!=null) ois.close(); if(fis!=null) fis.close(); } return foundAccount; } public static void writeAccount(PPAccount account,Boolean append) throws IOException, ClassNotFoundException, DuplicateAccountException { ObjectOutputStream oos=null; FileOutputStream fos=null; try { if(!append) { fos= new FileOutputStream(PPConstants.AllAccountDetails); oos = new ObjectOutputStream(fos); //System.out.println("Not Appending"); oos.writeObject(account); } else { File ff = new File(PPConstants.AllAccountDetails); if(!ff.exists()) { System.out.println("Required file not found"); return; } PPAccount aa=lookupAccount(account.getEmail()); if(aa!=null) throw new DuplicateAccountException("An Account already exits with this email-ID"); oos = new AppendingObjectOutputStream(new FileOutputStream(PPConstants.AllAccountDetails,append)); oos.writeObject(account); } } finally { if(oos!=null) oos.close(); if(fos!=null) fos.close(); } }
-
sasidhar over 11 yearsEdited my question please see
-
sasidhar over 11 yearsthat is the original code i had, to clarify where the exception is, i change it like this, the
readObject()
method is throwing the error. I can't understand why? -
user207421 over 11 yearsDoesn't answer the question. Have a look at the stack trace. In any case ClassCastException could only be thrown if there already was a typecast.
-
sasidhar over 11 years@EJP true.... but reading through the documentation of
readObject()
i doubt that there may be a few other cases this exception is thrown.. but i quite couldn't get what it meant and when it is thrown. -
sasidhar over 11 yearsI am facing no problems when using without the appender. And kindly note that i am also able to read and write into the stream, but only in specific order. If the first class to be written is of
PPBusinessAccount
everything seems to work fine. If the ArrayList is the culprit then it should fail in a different order too right? Anyways tested by removing the array list and also by making it null. The results weren't any different. -
Richard Sitze over 11 yearsOk, so let's leave the ArrayList out just to minimize noise for the moment. Can you generate a file (a) "PBAccount, PBAccount, PBAccount, PBBusinessAccount" using the appender for each object after the first, and (b) without using the appender at all. Run
diff
on the two files. -
sasidhar over 11 years
Perfect
After all the time i spent fixing this, the solution is one line. Thanks. I Will accept the answer and bounty tomorrow. I am interested to know, ifthrowing away the state at this point
is safe or not? The solution worked for me, fine. But applying this to a general case scenario. Would it make the solution safe in all scenarios?? -
Daniel Martin over 11 yearsEdited main post to answer the question.