Невозможно прочитать сериализуемый класс из файла .dat

Я пытаюсь написать держатель ключей и хочу записать пароли в файл .dat с помощью ObjectOutputStream, а затем прочитать их с помощью ObjectInputStream. Это мой код для записи объектов:

public void toFile()
{    
    try
    {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("passwords.dat"));     
        for(int i = 0; i<this.nrOfPW; i++)
        {
            if(this.PWlist[i] instanceof longPW)
            {
                oos.writeObject((longPW)this.PWlist[i]);
            }
            else
            {
                oos.writeObject((PinPW)this.PWlist[i]);
            }   
        }
        oos.close();
    }
    catch(IOException e)
    {
        e.getStackTrace();
    }
}

Кажется, это работает, но когда я пытаюсь снова прочитать файл и поместить объекты в свой массив PWlist, он говорит, что PinPW не сериализуем, хотя PinPW реализует Serializable и импортируется. Базовый класс PinPW (Info) также реализует Serializable и импортирует его. Это код, в котором я прочитал файл:

public void fromFile() 
{
    try 
    {
        ObjectInputStream objIn =  new ObjectInputStream(new FileInputStream("passwords.dat"));
        while(objIn.readObject() != null)
        {
            if(this.nrOfPW == this.PWlist.length)
            {
                expand(10);
            }
            if(objIn.readObject() instanceof PinPW)
            {
                this.PWlist[this.nrOfPW] = (PinPW)objIn.readObject();
                this.nrOfPW++;
            }
            else
            {
                this.PWlist[this.nrOfPW] = (longPW)objIn.readObject();
                this.nrOfPW++;
            }
        }
        objIn.close();
    }
    catch(EOFException e)
    {
        e.getStackTrace();
    }
    catch(IOException ex)   
    {
        ex.printStackTrace();   
    }
    catch(ClassNotFoundException ex)
    {
        ex.printStackTrace();   
    }
}

Массив PWlist является массивом Info, а PinPW и longPW расширяют Info.

Что мне делать, чтобы решить эту проблему?


person Psyberion    schedule 07.06.2012    source источник
comment
Не могли бы вы опубликовать сгенерированное исключение и трассировку стека - см. мой ответ для возможного решения этой проблемы и определенного исправления ошибки.   -  person Greg Kopff    schedule 08.06.2012
comment
[Не по теме] По соглашению классы Java называются с использованием UppercaseStartingCamelCase — это означает, что ваш longPW класс должен быть LongPW. Точно так же переменные именуются с использованием строчных букв StartingCamelCase, поэтому ваша переменная PWList должна называться pwList.   -  person Greg Kopff    schedule 08.06.2012
comment
Вы можете упростить свой метод создания файла. Вам не нужно ни проверять тип объекта, ни приводить их при вызове writeObject, поскольку параметр здесь равен Object. Однако вы должны убедиться, что все переданные объекты реализуют интерфейс java.io.Serializable, поскольку он не применяется компилятором (это обеспечивается потоком вывода сериализации).   -  person Kevin Brock    schedule 08.06.2012


Ответы (3)


Давайте исправим "первый баг, сначала"...

В этом коде:

while(objIn.readObject() != null)                         // reads object, tests then *discards* it
{
  ...

  if(objIn.readObject() instanceof PinPW)                 // reads object, tests then *discards* it
  {
    this.PWlist[this.nrOfPW] = (PinPW)objIn.readObject(); // conditionally read an object
    this.nrOfPW++;
  }
  else
  {
    this.PWlist[this.nrOfPW] = (longPW)objIn.readObject(); // conditionally read an object
    this.nrOfPW++;
  }
}

Каждый раз во время итерации цикла вы фактически читаете 3 объекта. В первый раз, когда вы читаете объект, чтобы проверить, есть ли он в потоке, в следующий раз, когда вы читаете объект и определяете его тип, а затем отбрасываете его. Затем вы читаете третий объект и приводите его в соответствие с типом отброшенного объекта.

Кроме того, как правильно указывает EJP, правильный способ определить конец потока для ObjectInputStream состоит в том, чтобы поймать конец исключение файла.

Вместо этого вы хотите сделать это:

 try
 {
   while (true)
   {
     final Object o = objIn.readObject();            // read the object from the stream

     ...

     if (o instanceof PinPW)
     {
       this.PWlist[this.nrOfPW] = (PinPW) o;         // cast to correct type
       this.nrOfPW++;
     }
     else
     {
       this.PWlist[this.nrOfPW] = (longPW) o;        // cast to correct type
       this.nrOfPW++;
     }
   }
 }
 catch (EOFException e)
 {
   // end of stream reached ...
   // ... close the file descriptor etc ...
 }
person Greg Kopff    schedule 07.06.2012
comment
@Psyberion Нет причин вообще проверять значение null, если только вы не пишете значения null при сериализации. Правильный тест для EOS в сериализованном потоке — поймать EOFException. - person user207421; 08.06.2012

У вас есть проблема здесь.

while(objIn.readObject() != null)
    {
        if(this.nrOfPW == this.PWlist.length)
        {
            expand(10);
        }
        if(objIn.readObject() instanceof PinPW)
        {
            this.PWlist[this.nrOfPW] = (PinPW)objIn.readObject();
            this.nrOfPW++;
        }
        else
        {
            this.PWlist[this.nrOfPW] = (longPW)objIn.readObject();
            this.nrOfPW++;
        }
    }

Вы читаете объект много раз. Попробуйте сохранить его, а затем работать с ним. if(objIn.readObject() instanceof PinPW) читает один раз, читает дважды, this.PWlist[this.nrOfPW] = (PinPW)objIn.readObject(); читается три раза, когда это должно быть только один раз. PS: используйте синтаксис Грега Копфа через некоторое время и без ключевого слова final, потому что вы хотите сохранить в нем больше объектов.

person Claudiu C    schedule 07.06.2012

Я просто хотел указать, что блок if-else в функции toFile() совершенно бессмысленен. writeObject() принимает аргумент объекта. Неважно, какой это тип объекта, если он сериализуем.

Хотя ваш метод fromFile() имеет серьезные недостатки, он не вызовет NotSerializableException. Я считаю, что исключение, о котором вы говорите, действительно произошло в toFile().

Причина очень проста: вы не полностью прочитали и не поняли документацию ObjectOutputStream. Чтобы быть конкретным, объект и все его непереходные поля и все непереходные поля в его классах-предках должны реализовывать Serializable. Он также должен иметь общедоступный конструктор без аргументов.

person billc.cn    schedule 07.06.2012
comment
У него должен быть общедоступный конструктор без аргументов — это верно для класса Externalizable, но не для класса Serializable. - person Greg Kopff; 08.06.2012
comment
Что ж, вы правы, это не является строгим требованием, но они у меня всегда были, потому что с подклассами возникают сложности, если их нет. Не удосужился запомнить все правила по этому поводу, поэтому я просто унифицирую это таким образом :) - person billc.cn; 08.06.2012
comment
Вы думаете об этом: < i> Чтобы разрешить сериализацию подтипов несериализуемых классов, подтип может взять на себя ответственность за сохранение и восстановление состояния общедоступных, защищенных и (если доступно) полей пакета супертипа. Подтип может взять на себя эту ответственность только в том случае, если класс, который он расширяет, имеет доступный конструктор без аргументов для инициализации состояния класса. - person Greg Kopff; 08.06.2012