【问题标题】:ClassCastException when Appending Object OutputStream附加对象 OutputStream 时出现 ClassCastException
【发布时间】:2012-08-30 01:02:37
【问题描述】:

我一直在尝试做一个需要可附加 ObjectOutputStream 的小项目。 我经历了几个解决方案,我发现this 起初它似乎解决了我的问题。但是在我的项目的进一步发展中,我开始遇到意外的异常。 以下是我的课程。

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
}

我观察到的是,使用this 我已经覆盖了 ObjectOutputStream 并在我将对象附加到文件时使用了它。但是如果我写了会发生什么:

PPBusinessAccount 首先,重复任意次数...然后写PPAccount 一切都很好。 PPAccount 首先,重复....然后写PPBusinessAccount 然后写PPAccount,它写得很好,但是在阅读时我得到一个ClassCastException。

我尝试读取对象并将它们直接存储在Object 类的实例中以避免类强制转换,但仍然readObject() 抛出ClassCastException。

我尽力描述我的场景,告诉你是否没有得到任何东西。为什么会这样??它与它第一次编写的标题有关吗?沿着基类标题不能支持子类?转机是什么?

我正在做这样的演员:

Object o = ois.readObject();        //Surprisingly exception is raised here (line:50 in DataStore)
PPAccount ppa = (PPAccount)o;

堆栈跟踪

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)

lookUpAccount 从流中读取,而writeAccount 向流中写入,代码如下:

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();
        }

    }

【问题讨论】:

  • 您也可以发布异常跟踪吗?它还提供了错误的明确线索......
  • 您是否正在向流中写入其他内容?例如,使用 writeObject() 以外的任何方法?
  • @EJP 不,除了writeObject() 这是写入这些类中的任何一个的唯一方法之外,我没有向流中写入任何其他内容。
  • 您能否展示正在编写对象的代码以及正在读取它的代码周围的更多上下文(即ois 是如何创建的)?

标签: java serialization classcastexception java-io


【解决方案1】:

这里的问题是,之前给你一个可附加的ObjectOutputStream 的海报让你误入歧途。 ObjectOutputStream/ObjectInputStream 尝试仅存储每个对象一次,然后稍后再引用已存储的对象。也就是说,如果你有一堆同一个类的对象,你可以在流中得到这样的结果:

CLASS_1_DESCRIPTION
OBJECT_1
REF_TO_CLASS_1
OBJECT_2
REF_TO_CLASS_1
OBJECT_3
...

当ObjectInputStream 将流转换回一堆对象时,它会维护一个列表,其中列出了它已经反序列化的内容。它告诉你的错误是它试图反序列化一个对象,读取应该是对对象类描述的引用,但是当它在其内部表中查找该引用时,它看到了String。很自然,它爆炸了。

我认为修复就像这样简单 - 在您的 AppendableObjectOutputStream 中,更改此方法:

  @Override
  protected void writeStreamHeader() throws IOException {
    // do not write a header, but reset the handle list
    reset();
  }

ObjectOutputStream 中的 reset() 方法在流中插入一个标记,表示“此时丢弃所有状态”。然后,当您使用ObjectInputStream 回读此内容时,输入流对已反序列化内容的想法将与输出流认为的状态相匹配,当它首先反序列化这些内容时。

(编辑:回答 cmets 的问题)

我能想到的唯一有害后果:

  • 最终文件会比将所有内容都写入一个 ObjectOutputStream 时的文件长度要长,尤其是当同一个 Profile 对象出现多次时。即使没有,您也会在流中重复类描述符,因此大量重复 {open AppendableObjectOutputStream, write one object, close stream} 可能会使文件大小有点膨胀。

  • 与此相关,在反序列化所有内容后,您可能会得到本应是相同对象的多个副本。例如,假设你写了一堆东西,包括一些 PPRestrictedAccount 对象,然后关闭流,将其作为 AppendableObjectOutputStream 打开,并写出一个 PPBusinessAccount,它的 operators 列表中有一些 @987654340 @s 你之前写的。当您重新阅读所有内容时,您最初阅读的 PPRestrictedAccounts 与您在 PPBusinessAccount 中找到的 PPRestrictedAccounts 将不是相同的对象(也就是说,它们不会是 ==)的operators 列表。它们将被单独实例化。为避免这种情况,您需要使用 readResolve 方法对它们进行重复数据删除。但是,写入单个 AppendableObjectOutputStream 实例的所有内容都将正确连接。根据您的应用程序,这可能完全不用担心。

就是否会爆炸的安全性而言,这与使用 java 序列化的任何其他用途一样安全;您的课程没有任何特定的内容可以使其正常工作。请注意,在输出文件的多个单独开口中写入的任何对象都将被反序列化为原始对象的单独副本。 (没有任何readResolve魔法)

【讨论】:

  • Perfect 在我花了这么多时间修复这个问题之后,解决方案就是一行。谢谢。我明天会接受答案和赏金。我很想知道throwing away the state at this point 是否安全?该解决方案对我有用,很好。但是将此应用于一般情况。它会使解决方案在所有情况下都安全吗??
  • 编辑主帖以回答问题。
【解决方案2】:

试试这样......

readObject() 返回类型 Object的 objects,因此您需要将它们显式转换为其原始类型...强>

例如:

PPAccount pa = (PPAccount) readObject();

【讨论】:

  • 编辑了我的问题,请参阅
  • 那是我的原始代码,为了澄清异常在哪里,我像这样更改它,readObject() 方法抛出错误。我不明白为什么?
  • 不回答问题。查看堆栈跟踪。在任何情况下,ClassCastException 只能在已经有类型转换的情况下被抛出。
  • @EJP true.... 但是阅读readObject() 的文档我怀疑可能还有其他一些情况会引发此异常.. 但我完全不明白它的含义以及何时抛出。
【解决方案3】:

没有明确的答案,但可以尝试一些想法和事情:

  • 堆栈跟踪包括at java.util.ArrayList.readObject(Unknown Source),这表明在反序列化包含ArrayList 的类时会出现问题。通过注释掉 private ArrayList&lt;Transaction&gt; transactions;

  • 来缩小您的问题
  • 如果您不使用使用 appender 生成单个文件,您是否也会遇到同样的问题?如果是这样,请创建两种相同内容的形式:一种带有 appender,一种没有。差异?

  • 我确实看到另一个关于类似问题的参考,但没有解决方案。也使用相同的附加程序:Serialization/deserialization ClassCastException: x cannot be cast to java.io.ObjectStreamClass

【讨论】:

  • 我在使用 without appender 时没有遇到任何问题。请注意,我也可以读写流,但只能按特定顺序。如果要编写的第一个类是PPBusinessAccount,那么一切似乎都可以正常工作。如果 ArrayList 是罪魁祸首,那么它也应该以不同的顺序失败,对吧?无论如何,通过删除数组列表并将其设为空来进行测试。结果并没有什么不同。
  • 好的,让我们暂时将 ArrayList 排除在外,以尽量减少噪音。您能否在第一个之后为每个对象使用附加程序生成文件(a)“PBAccount,PBAccount,PBAccount,PBBusinessAccount”,以及(b)根本不使用附加程序。在这两个文件上运行diff。
猜你喜欢
  • 2017-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-24
  • 2012-01-06
相关资源
最近更新 更多