首页 > 解决方案 > 使用 ObjectOutputStream ObjectInputStream 在 Java 中存储和读取包含其他对象的对象

问题描述

我有一个类 Person 和一个类 BankAccount 如下(对不起,代码是西班牙语):

package aplicacion.banco;

import java.io.Serializable;

public class Persona implements Serializable {

    String nif; // se asume unico para cada Persona
    String nombre; // nombre
    String apellidos; // apellidos

    public Persona(String nif, String nombre, String apellidos) {
        this.nif = nif;
        this.nombre = nombre;
        this.apellidos = apellidos;
    }
    // bunch of methods including setters and getters
}
package aplicacion.banco;

import java.io.Serializable;

public class CuentaBancaria implements Serializable {

    int numeroCuenta; // se asume unico para cada CuentaBancaria/CuentaBancariaVip
    Persona persona; // objeto Persona al que pertenece esta Cuenta
    double saldo; // saldo disponible

    public CuentaBancaria(int numeroCuenta, Persona persona, double saldo) {
        this.numeroCuenta = numeroCuenta;
        this.persona = persona;
        this.saldo = saldo;
    }

    public CuentaBancaria(int numeroCuenta, Persona persona) {
        this(numeroCuenta, persona, 0d);
    }
    // bunch of methods including setters and getters
}

如您所见,BankAccount 类有一个 Person 字段,该字段指示它所属的 Person。这样,我可以将多个 BankAccounts 关联到同一个 Person ,因此我可以将所有 BankAccounts 关联到一个 Person 并将它们作为一个整体进行操作,并且效果很好。但是,在将 Person 和 BankAccount 的数据存储到文件中,然后在重新启动程序时将其读回时,我遇到了一个小问题......

下一个类是读取和写入数据的类。这里的重要方法是 cargarArchivos(),它将文件的内容加载到 ArrayLists,actualizarArchivoPersonas() 更新 Person 文件,以及actualizarArchivoCuentas(),它对 BankAccounts 执行相同的操作。每当我在代码的其他部分中更改 ArrayLists 时,都会调用这些更新方法。

package aplicacion.banco;

import java.util.ArrayList;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.EOFException;

public class OperadorDatos {

    private static final ArrayList<Persona> personas = new ArrayList<>();
    private static final ArrayList<CuentaBancaria> cuentasBancarias = new ArrayList<>();

    private static final File DIRECTORIO = new File ("data");
    private static final File PERSONAS = new File (DIRECTORIO.getPath() + "/" + "personas.dat");
    private static final File CUENTAS = new File (DIRECTORIO.getPath() + "/" + "cuentas.dat");

    /**
     * Comprueba la existencia de los archivos y directorio. Si no existen los crea.
     */
    private static void comprobarArchivos() {

        try {
            if (DIRECTORIO.exists()) {
                if (! PERSONAS.exists()) PERSONAS.createNewFile();
                if (! CUENTAS.exists()) CUENTAS.createNewFile();
            } else {
                DIRECTORIO.mkdir();
                PERSONAS.createNewFile();
                CUENTAS.createNewFile();
            }
        } catch (Exception e) {
            System.exit(-1);
        }
    }

    /**
     * Carga en memoria el contenido de los archivos.
     */
    public static void cargarArchivos() {

        comprobarArchivos();

        personas.clear();
        cuentasBancarias.clear();
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(new FileInputStream(PERSONAS));
            while (true) {
                Persona p = (Persona)ois.readObject();
                personas.add(p);
            }
        } catch (EOFException eof) {
            // todo correcto
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            ois = new ObjectInputStream(new FileInputStream(CUENTAS));
            while (true) {
                CuentaBancaria cb = (CuentaBancaria)ois.readObject();
                cuentasBancarias.add(cb);
            }
        } catch (EOFException eof) {
            // todo correcto
        } catch (Exception e) {
            e.printStackTrace();
        }

        reasociar();
    }

    /**
     * Este metodo se encarga de reasociar cada Cuenta con su Persona al cargar el programa
     * en base al campo NIF que se entiende como clave unica (ya que no puede haber dos Persona con mismo nif)
     * Esto hace falta hacerlo ya que, como estoy serializando objetos que tienen como atributo otros objetos,
     * al volver a cargarlos la asociacion en direcciones de memoria se rompe. Este metodo se encarga de solucionar eso.
     */
    public static void reasociar() {

        for (CuentaBancaria cb : cuentasBancarias)
            for (Persona p : personas)
                if(cb.getPersona().getNif().equals(p.getNif()))
                    cb.setPersona(p);
    }

    /**
     * Comprueba si existe una persona con el nif indicado y devuelve un valor de posición
     * @param  nif nif a comprobar
     * @return     numero entero equivalente a la posicion de la persona en el array, -1 si no existe
     */
    public static int comprobarNif(String nif) {

        for (int i = 0; i < personas.size(); i++) {
            if (nif.equals(personas.get(i).getNif())) return i;
        }
        return -1;
    }

    /**
     * Crea un nuevo objeto Persona a partir de los datos,
     * actualiza el archivo de datos, y devuelve la Persona recien creada
     * @param  nif       NIF
     * @param  nombre    nombre
     * @param  apellidos apellidos
     * @return           el nuevo objeto Persona
     */
    public static Persona crearPersona(String nif, String nombre, String apellidos) {
        Persona p = new Persona(nif,nombre,apellidos);
        personas.add(p);
        actualizarArchivoPersonas();
        return p;
    }

    /**
     * Obtiene un objeto Persona a partir de busqueda por NIF
     * @param  nif NIF de la persona a buscar
     * @return     Objeto Persona al que corresponde este NIF, null si no existe.
     */
    public static Persona getPersona(String nif) {

        for (Persona p : personas) {
            if (p.getNif().equals(nif)) return p;
        }

        return null;
    }

    public static ArrayList<Persona> getListaPersonas() {
        return personas;
    }

    public static ArrayList<CuentaBancaria> getListaCuentas() {
        return cuentasBancarias;
    }

    /**
     * Crea un Objeto CuentaBancaria asociado a la Persona indicada,
     * actualiza el archivo, y devuelve el nuevo Objeto
     * @param  p Objeto Persona al que se asociará la nueva cuenta
     * @return   Objeto CuentaBancaria recién creado
     */
    public static CuentaBancaria crearCuentaBancaria(Persona p) {
        CuentaBancaria cb = new CuentaBancaria(cuentasBancarias.size()+1,p);
        cuentasBancarias.add(cb);
        actualizarArchivoCuentas();
        return cb;
    }

    /**
     * Crea un Objeto CuentaBancariaVip asociado a la Persona indicada,
     * actualiza el archivo, y devuelve el nuevo Objeto
     * @param  p Objeto Persona al que se asociará la nueva cuenta
     * @return   Objeto CuentaBancariaVip recién creado
     */
    public static CuentaBancariaVip crearCuentaBancariaVip(Persona p) {
        CuentaBancariaVip cb = new CuentaBancariaVip(cuentasBancarias.size()+1,p);
        cuentasBancarias.add(cb);
        actualizarArchivoCuentas();
        return cb;
    }

    /**
     * Actualiza el archivo personas.dat, que contiene informacion de las personas.
     */
    public static void actualizarArchivoPersonas() {

        try {

            PERSONAS.delete();
            PERSONAS.createNewFile();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(PERSONAS, false));
            for (Persona p : personas) {
                oos.writeObject(p);
            }
            oos.close();

        } catch (Exception e) {

        }
    }

    /**
     * Actualiza el archivo cuentas.dat, que contiene informacion de las cuentas.
     */
    public static void actualizarArchivoCuentas() {

        try {

            CUENTAS.delete();
            CUENTAS.createNewFile();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(CUENTAS, false));
            for (CuentaBancaria cb : cuentasBancarias) {
                oos.writeObject(cb);
            }
            oos.close();

        } catch (Exception e) {

        }
    }

    /**
     * Cuenta la cantidad de cuentas de una persona
     * y el valor total de las cuentas.
     * @param  p Persona cuyas cuentas contar
     * @return   array de dos posiciones, [0] es la cantidad de cuentas, [1] el valor total
     */
    public static double[] contarCuentas(Persona p) {

        double[] sum = new double[2];
        for (CuentaBancaria cb : cuentasBancarias) {
            if(cb.getPersona() == p) {
                sum[0]++;
                sum[1]+=cb.getSaldo();
            }
        }
        return sum;
    }
}

问题是,当我重新启动程序并将这两个文件重新加载到 ArrayLists 中时,每个 BankAccount 仍然有一个具有正确数据的 Person,但是该 Person 现在与存储在 Personas ArrayList 中的 Person 是不同的对象,即使他们在它们的字段中具有相同的值。

为了解决这个问题,我在这个类中也有方法 reasociar(),其目的是在从文件中加载数据后,根据 Person nif(这是每个人的唯一 ID)将每个 BankAccount 与每个人重新关联起来. 如果我不使用方法 reasociar() 使每个 BankAccount 中的 Person 实际上指向角色 ArrayList 中的正确 Person,则每个 BankAccounts 中的对象 Person 与已读取并存储到 Person 的内存中的项目不同角色 ArrayList,即使每个 BankAccount 仍然具有关联的所有者的“正确”nif、姓名和姓氏。所以方法 reasociar() 可以很好地解决问题,并且由于它在重新启动程序时一切正常。

但我的问题是......有没有合适的方法来做到这一点,所以当我写两个数据文件并在我重新启动程序时读回它们时,对象指针保持一致而无需重新关联它们?我希望我能很好地解释自己...谢谢

标签: javafileserializationobjectinputstreamobjectoutputstream

解决方案


推荐阅读