Autor Tema: Java guardar objetos a disco implementar interfaz Serializable #codigoJava  (Leído 3598 veces)

doxp11

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 7
    • Ver Perfil
Hola, necesito ayuda para hacer un ejercicio donde habria que guardar informacion de objetos en un documento externo. Estoy trabajando con Java, con el IDE NetBeans.
« Última modificación: 12 de Enero 2022, 20:56 por Ogramar »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 988
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #1 en: 14 de Mayo 2021, 23:03 »
Publica aquí el enunciado del ejercicio.
Y también el código que lleves escrito y/o las dudas que tengas.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

doxp11

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 7
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #2 en: 15 de Mayo 2021, 00:10 »
El programa lo tengo hecho pero no se como podria llegar a hacer esto, tengo el programa principal que lo hace todo y otro donde guardo los productos, el emissor, cliente y la factura con sus getters y setters correspondientes.

Este seria:
Adaptar facturas plus para guardar el emisor, los productos, los clientes y las facturas, y poderlo cargar en cualquier momento. Cada cual deberá guardar en un archivo diferente.

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 988
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #3 en: 15 de Mayo 2021, 01:48 »
Sin ver cuál es el código sobre el que hay que trabajar, es difícil guiarte.

Supongo que Emisor, Cliente, Producto, Factura,.... son clases.

Y supongo que en el programa se van almacenando en algún tipo de colección, por ejemplo un ArrayList.

En ese caso, puedes emplear la clase ObjectOutputStream para guardar en disco esos ArrayList en un fichero de bytes.
Y luego recuperarlos del disco con la clase ObjectInputStream
Solo requiere que cada una de tus clases (Emisor, Cliente, Factura,...) implementen la interfaz Serializable.
Esto lo haces cuando se declara la clase, por ejemplo:

Código: [Seleccionar]
public class Cliente implements Serializable {

   //Atributos
   //Constructor
   //Métodos...

}

Implementando esa interfaz, ya pueden "serializarse" en forma de bytes para guardar en disco.

La otra opción sería guardar los datos en archivos de texto plano, pero esto es más complicado y requiere más código, sobre todo a la hora de recuperar los datos.
Porque hay que leer líneas de texto, trocearlas para separar los valores de cada atributo, reconstruir los objetos con esos valores...

Con ObjectOutputStream y ObjectInputStream es más sencillo, se guarda una copia en disco de los ArrayList tal cuál están en ese momento en memoria RAM, no hay que transformar datos en cadenas de String ni nada.

Si compartes el código del programa que hay que adaptar, podemos decirte cómo y dónde será más óptimo modificar el código para leer y guardar los datos de esta manera.

NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

doxp11

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 7
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #4 en: 15 de Mayo 2021, 02:03 »
Esto es lo que tengo, te lo adjunto.

Código comprimido en archivo adjunto (hay que estar logado en el foro para poder descargarlo). Incluye las clases:

  • Client.java
  • Emissor.java
  • Factura.java
  • GestioDeFacturesPlusPlus.java
  • Producte.java
  • ProducteFactura.java
« Última modificación: 13 de Enero 2022, 18:50 por Ogramar »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 988
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #5 en: 15 de Mayo 2021, 13:45 »
Vale. Está muy bien estructurado el programa, así que va a ser fácil hacer lo que se pide.

Vamos a ver por ejemplo, como guardar los Productos.

Lo primero es implementar la interfaz Serializable a la clase Producte.
Una vez implementada, es probable que el IDE lance un warning pidiendo que también agreguemos un "serial" identificador e incluso nos ofrecerá hacerlo el automáticamente.
Puedes ponérselo, aunque en este caso es algo irrelevante, así que yo normalmente no lo pongo y para que el warning desaparezca uso la etiqueta @SupressWarnings() para indicarle al compilador que no me lance ninguna advertencia por ese motivo.

Código: [Seleccionar]
@SuppressWarnings("serial")
public class Producte implements Serializable{

Y ya está, eso es lo único que hay que cambiar en la clase Producte.

Ahora nos vamos al programa principal.
Ahí ya tienes declarado un método para guardar productos, ahora vamos a definirlo.
Lo que vamos a hacer consiste en transformar el array de productos, que está en memoria RAM, en un archivo de bytes (serializar) para poder guardarlo en disco.

Para esto se usa la clase ObjectOutputStream, quien a su vez requiere de un objeto FileOuputStream, quien a su vez nos va a pedir un objeto File (un fichero).

La lógica a seguir es crear primero el File, dándole el nombre con el que queremos que se guarde en disco. Podemos indicarle solo nombre, o una ruta completa.
Si solo damos nombre, se guardará en la carpeta de proyecto de este programa.

Acto seguido, preguntamos si NO existe, en cuyo caso lo crearemos.
Este acto de creación podría fallar, por ejemplo porque intentamos escribir en una ruta donde el S.O. no nos da permisos de escritura.
Con try catch capturamos esta posible excepción y si ocurre, anunciamos al usuario que no es posible guardar datos.

Si todo sale bien, pasamos a crear el ObjectOutputStream, de quién invocaremos el método writeObject() y le pasaremos el array que queremos guardar en disco, el de productos en este caso.
Todo este proceso también va a requerir de try catch por las posibles excepciones que puedan ocurrir.

Y ya está, tras esos pasos, si todo ha ido bien tendremos un archivo en disco con los datos del array en forma de bytes.

El método quedaría así:

Código: [Seleccionar]
  private static void guardarProductes() {
   
    File fitxerProductes = new File("productes.bin");
   
    if (!fitxerProductes.exists()) {
try {
fitxerProductes.createNewFile();
} catch (IOException e) {
System.out.println("\n-- ERROR. No s'ha pogut crear: " + fitxerProductes.getAbsolutePath());
return; //Posem fi a l'operació. No es pot guardar en aquesta ubicació.
}
    }
   
    try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fitxerProductes));
oos.writeObject(productes); //Guardem array productes
oos.close();
System.out.println("\nProductes guardats correctament\n");
} catch (FileNotFoundException e) {
System.out.println("\n-- ERROR. No es troba fitxer: " + fitxerProductes.getAbsolutePath());
} catch (IOException e) {
System.out.println("\n-- ERROR. No es pot accedir a fitxer: " + fitxerProductes.getAbsolutePath());
}
    }

Bien, podemos guardar productos.
Ahora vamos a querer recuperar esa copia guardada cuando queramos.

Para esto usamos la clase ObjectInputStream, junto con FileInputStream.
Hacen lo contrario de la anterior:
- Output --> salida/escritura
- Input--> entrada/lectura

De nuevo comenzamos creando un objeto File y preguntamos si existe o no.
Si no existe, pues entonces no hay nada que recuperar, así que lo anunciamos y se acabó.

Si existe, entonces creamos el ObjectInputStream e invocamos el método readObject().

Este método lo que hace es leer los bytes del objeto File al que está asociado y los retorna en forma de Object.

Este Object se lo vamos a asignar al array de productos, pero para que lo acepte será necesario hacer un casting a Producte[].

Si todo va bien, ya tendremos recuperado el array de productos. Aunque aún queda una cosa pendiente. El programa está usando un "contador de productos" para saber cuantos productos hay registrados, y en que posición del array debe ir registrando los nuevos productos, etc...

Tras recuperar el array que estaba guardado en disco, este contador necesita ser actualizado, pues su valor ya no se corresponde con el nuevo array.
Si por ejemplo nada más comenzar el programa recuperamos un array que tiene 20 productos, el contador en cambio nos va a decir que tiene 0 productos.

Esto hay que solucionarlo, y para ello he creado un segundo método que lo que hace es recibir un array de cualquier clase (así se puede usar también para los array de facturas, clientes..) y lo recorrer hasta encontrar la primera posición con valor null. Y retorna ese número de posición, que ha de ser el que le asignemos al contador para que tenga un valor correcto.

Este es el método para recuperar los datos guardados en disco:

Código: [Seleccionar]
    private static void recuperarProductes() {
   
    File fitxerProductes = new File("productes.bin");
   
    if (!fitxerProductes.exists())
    System.out.println("No hi ha productes guardats per recuperar.");
    else {
    try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fitxerProductes));
//Recuperem array productes
productes = (Producte[]) ois.readObject(); //Fem casting de Object a Producte[]
//Actualitzem comptador de productes
comptadorDeProductes = calculaComptador(productes);
ois.close();
System.out.println("\nProductes recuperats correctament\n");
} catch (FileNotFoundException e) {
System.out.println("\n-- ERROR. No es troba fitxer: " + fitxerProductes.getAbsolutePath());
} catch (IOException e) {
System.out.println("\n-- ERROR. No es pot accedir a fitxer: " + fitxerProductes.getAbsolutePath());
} catch (ClassNotFoundException e) {
System.out.println("\n-- ERROR. Les dades no es corresponen amb dades de Producte");
}
    }
   
    }

Y este es el método que acepta arrays de cualquier clase (por eso el argumento lo recoge como Object[] ) y retorna el valor correcto para los contadores:
Código: [Seleccionar]
    private static int calculaComptador(Object[] array) {
    int comptador = 0;
   
    for (int i = 0; i < array.length; i++)
    if (array[i] == null) { //Busquem la primera posició buida per determinar el comptador.
    comptador = i;
    break;
    }
   
    return comptador;
    }

Y listo, ya puedes probar a guardar y recuperar productos. En el menú, eso sí, tendrás que añadir nuevas opciones para recuperar y actualizar el switch

Citar
    public static void menu(){
        Scanner sc = new Scanner(System.in);
        boolean sortir = false;
        do{
            System.out.println("***** Gestió de factures *****");
            System.out.println("Dades emissor: " + Emissor.getString());
            System.out.println();
            System.out.println("--- Menú d'usuari ---");
            System.out.println("\t1) Crear nova factura");
            System.out.println("\t2) Visualitzar totes les factures");
            System.out.println("\t3) Cercar factura");
            System.out.println("\t4) Eliminar factura");
            System.out.println("\t5) Crear client");
            System.out.println("\t6) Visualitzar client");
            System.out.println("\t7) Crear producte");
            System.out.println("\t8) Visualitzar producte");
            System.out.println("\t9) Canviar emissor");
            System.out.println("\t10) Guardar emissor");
            System.out.println("\t11) Guardar productes");
            System.out.println("\t12) Guardar clients");
            System.out.println("\t13) Guardar factures");
            System.out.println("\t14) Recuperar productes");
            System.out.println("\t15) Sortir");
            System.out.print("La teva opció:");
            int opcio = 0;
            try{
                opcio = sc.nextInt();
            }catch(InputMismatchException ex){
                System.out.print("Entrada no vàlida. No és un número");
            }finally{
                sc.nextLine();
            }
            try{
                switch(opcio){
                    case 1: crearNovaFactura();
                            break;
                    case 2: visualitzarFactures();
                            break;
                    case 3: cercarFactura();
                            break;
                    case 4: eliminarFactura();
                            break;
                    case 5: crearClient();
                            break;
                    case 6: visualitzarClients();
                            break;
                    case 7: crearProducte();
                            break;
                    case 8: visualitzarProductes();
                            break;
                    case 9: canviarEmissor();
                            break;
                    case 10: guardarEmissor();
                            break;
                    case 11: guardarProductes();
                            break;
                    case 12: guardarClients();
                            break;
                    case 13: guardarFactures();
                            break;
                    case 14:
                       recuperarProductes();
                       break;
                    case 15: sortir = true;
                            break;

                    default:
                        System.out.println("Opció no vàlida!");
                }
            }catch(Exception ex){
                System.out.println("S'ha produit un error:");
                System.out.println(ex.getMessage());
            }
        }while(!sortir); 
    }



Siguiendo este ejemplo, tú mismo puedes escribir los restantes métodos para guardar clientes y facturas. Recuerda implementarles la interfaz Serializable a sus correspondientes clases.


Lo que no tengo claro es lo del Emisor, ¿también se ha de guardar? Pero no tienes ningún array con emisores.
Puedes guardarlo, ObjectOutputStream guardará cualquier objeto que le indiques (si implementa Serializable).
Pero como no usas ningún array, tu programa solo trabaja con un único Emisor (se puede cambiar sus atributos, pero siempre es uno) pues eso es lo que se guardará.

No tiene mucho sentido guardar simplemente un Emisor, revisa el enunciado de tu ejercicio a ver si realmente hay que guardarlo, o si bien lo que pasa es que está faltando un array de Emisores.

Pregunta cualquier duda que surja.
Un saludo.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

doxp11

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 7
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #6 en: 15 de Mayo 2021, 14:36 »
Ostia, gracias por la ayuda no me lo esperaba. Me lo mirare como sigo haciendolo. La respuesta a tu pregunta final, es SI hay que guardar tambien el Emissor, asi que falta un array de emissores seguramente

doxp11

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 7
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #7 en: 20 de Mayo 2021, 19:50 »
Para llegar a hacer las otras partes que seria hacer lo mismo o no?? Estoy confuso...
Luego al guardar los productos se me queda como un fichero que al abrirlo poco se puede leer.

Te lo adjunto como se me guarda y como se queda en su interior.

« Última modificación: 20 de Mayo 2021, 20:56 por doxp11 »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 988
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #8 en: 21 de Mayo 2021, 02:40 »
Al guardar el archivo en disco, no se puede leer un carajo... porque NO es un archivo de texto.
Es un archivo de bytes, lo que guardamos es una copia del "lenguaje máquina" tal cuál está en la memoria RAM.

Por eso en el programa lo puse para que se guardase con extensión .bin (binary code).
Se le puede poner cualquier extensión, sus datos internos son los mismos. Pero si le ponemos .txt, se puede dar a entender que es un archivo de texto. Y no lo es.


La ventaja de guardar los datos como bytes, es que el código para hacer esto es muy sencillo.
Básicamente necesitamos una instrucción para escribir, y otra para leer. Punto pelota.

La desventaja, que si luego queremos poder leer su contenido abriéndolo como un archivo de texto, nos vamos a encontrar un galimatías.


Se puede guardar también como archivo de texto plano, es decir, un verdadero y genuino .txt

El proceso es muy parecido, pero implica hacer más (muchas más) operaciones extra, además de decidir cómo nos conviene que se escriban los datos.
Porque según como los escribamos, luego será más fácil o más difícil leerlos.
Me explico:

Un Producto tiene cuatro atributos: referencia(int),  nombre(String), precio(double) y descripcion(String)

Supongamos que tenemos 5 Productos registrados en un ArrayList y queremos guardarlos en un archivo de texto plano.
Tendremos que recorrer el ArrayList con un bucle para obtener los tres atributos de cada Producto y escribirlos en el archivo de texto (esto como decía, ya supone realizar varias instrucciones de más comparado con antes).

Si simplemente vamos escribiendo en el archivo de texto, según vamos obteniendo valores de atributos, tendremos un archivo de texto como este:

Citar
1Coca-Cola1.25Bebida refrescante azucarada2Coca-Cola Light1.30Bebida refrescante ligera en azucares3Coca-Cola Zero1.30Bebida refrescante sin azucar4Fanta Limon1.25Bebida refrescante sabor limon5Fanta Naranja1.25Bebida refrescante sabor naranja

Vale, ahora si tenemos texto que entiende un humano, pero esta todo apelotonado en una sola línea.
No solo es feo a nuestros ojos, esto es lo de menos.
El auténtico problema es que luego para recuperar los datos en nuestro programa, tenemos que saber que palabras y números corresponden a cada atributo, para podre reconstruir los objetos Producto que habíamos guardado de la sesión anterior.

¿Entiendes por donde voy?
Antes, serializando todo en un archivo de bytes, era muy sencillo porque los objetos Producto se guardan tal cuál estaban en memoria RAM. Luego al leerlos no hay que reconstruir nada, porque permanecen "construidos" en el archivo de bytes.

Esto no es posible hacerlo en el archivo de texto plano, aquí solo podemos guardar los valores de los atributos de los productos, pero no los objetos Producto tal cual.
Y luego, hay que recuperar esos valores y separarlos, y utilizarlos para volver a construir otra vez los Productos (habrá que hacer otra vez new Producto() para cada uno y agregarlos otra vez al array de Producte[] )

Así que para facilitar esta tarea, primero hay que decidir una forma más adecuada para escribir los valores en el txt.
Lo habitual es hacer una línea para cada Producto.
Citar
1Coca-Cola1.25Bebida refrescante azucarada
2Coca-Cola Light1.30Bebida refrescante ligera en azucares
3Coca-Cola Zero1.30Bebida refrescante sin azucar
4Fanta Limon1.25Bebida refrescante sabor limon
5Fanta Naranja1.25Bebida refrescante sabor naranja

Bien, ahora ya sabemos donde comienza y termina cada Producto. Cada línea es un Producto.
Pero no sabemos bien donde comienzan y terminan cada valor de los atributos.
Tenemos que decidir alguna forma óptima de separarlos.
Así después, leeremos las líneas una por una. Y cada línea la partiremos en trozos allá donde detectemos lo que separa un valor de otro.
¿Y con qué separamos un valor de otro?

No podemos usar espacios en blanco, porque por ejemplo los nombres de producto o el texto de la descripción contienen espacios en blanco. Así que esto no nos va a servir para saber con certeza donde comienza un valor y termina otro

Podemos usar una coma para separar cada valor
Citar
1,Coca-Cola,1.25,Bebida refrescante azucarada
2,Coca-Cola Light,1.30,Bebida refrescante ligera en azucares
3,Coca-Cola Zero,1.30,Bebida refrescante sin azucar
4,Fanta Limon,1.25,Bebida refrescante sabor limon
5,Fanta Naranja,1.25,Bebida refrescante sabor naranja
Esta solución puede servir, si estamos seguros de que ni el nombre ni la descripción van a tener comas.
Sin embargo es arriesgado no solo por esos atributos, si no también por el precio. Yo lo he puesto con punto decimal, pero en realidad, cuando se guarde en archivo es posible que se guarden con coma decimal, esto puede variar según el sistema operativo y la configuración regional del usuario.
De hecho, luego al leer los datos, el que corresponde al precio habrá que parsearlo a double porque al leer del txt lo vamos a obtener como String, pero el atributo es un double.
Y habrá que controlar que no nos llegue con coma decimal, porque entonces el parseo a double fallará...solo es posible parsear a double si el texto leído tiene un punto decimal, no coma decimal.

Así que por todo esto, mejor usar otro símbolo para separar los productos. Para curarnos en salud, lo mejor es usar un conjunto de símbolos, por ejemplo dos guiones seguidos con espacio en blanco a cada lado.

Citar
1 -- Coca-Cola -- 1.25 -- Bebida refrescante azucarada
2 -- Coca-Cola Light -- 1.30 -- Bebida refrescante ligera en azucares
3 -- Coca-Cola Zero -- 1.30 -- Bebida refrescante sin azucar
4 -- Fanta Limon -- 1.25 -- Bebida refrescante sabor limon
5 -- Fanta Naranja -- 1.25 -- Bebida refrescante sabor naranja
Así se lee mejor a simple vista, y lo mejor de todo es que luego raro sería que ocurriesen errores al trocear las líneas correctamente en cuatro valores, uno para cada atributo.

Disculpa si me extiendo mucho con la explicación, pero es para que quede claro que guardar los valores como texto plano, supone ciertas complicaciones.

Ahora veremos como resolver todo esto con código Java.
De nuevo trabajaremos con los Productos y usaremos métodos específicos para guardar como texto plano, sin quitar lo que habíamos hecho antes. Así tendremos las dos versiones para practicar.

Lo primero que haremos, para facilitar las cosas, es crear un método en la clase Producto que nos construya una línea de texto con los atributos, tal y como queremos que se guarden en el archivo txt.
Algo parecido a lo que hacemos con el método toString(), pero este lo usaremos para escribir el Producto en una línea de texto.

Dicho método, puede ser como este:
Código: [Seleccionar]
    public String toTxt() {
    return String.format("%d -- %s -- %.2f -- %s", refProducte, nom, preu, descripcio);
    }
Eso ya nos devuelve una línea de texto, con los atributos separados por los dobles guiones.
También conviene reiniciar a 0 el contador interno de Productos creados que tiene la clase Producte.
Este contador se usa para establecer el atributo de la referencia cuando creamos un Producto nuevo. Como al leer líneas de texto, vamos a crear Productos nuevos (con valores guardados de antes) para evitar inconsistencias con las referencias, es mejor que comience desde 0 otra vez.
Así que le añadimos también este método a la clase Producte (ha de ser estatico):
Código: [Seleccionar]
    public static void resetTotalProductes() {
    totalDeProductes = 0;
    }
Esto no lo tuvimos en cuenta la vez anterior, y creo que también habría sido importante hacerlo.


Bien, pues ahora en la clase principal, añadimos nuevo método para guardar productos, esta vez en un archivo de texto plano.
Para esto usaremos la clase BufferedWriter, que a su vez necesita un FileWriter, que a su vez necesita un File.
El proceso es parecido a la forma anterior, pero fíjate que antes escribíamos tal cual el array de Productos.
Ahora, hay que recorrer el array y a cada Producto, pedirle que nos retorne la línea de texto, que será lo que guardaremos en el archivo.

Código: [Seleccionar]
private static void guardarProductesTXT() {

File fitxerProductes = new File("productes.txt");

if (!fitxerProductes.exists()) {
try {
fitxerProductes.createNewFile();
} catch (IOException e) {
System.out.println("\n-- ERROR. No s'ha pogut crear: " + fitxerProductes.getAbsolutePath());
return; //Posem fi a l'operació. No es pot guardar en aquesta ubicació.
}
}

try {
BufferedWriter bw = new BufferedWriter(new FileWriter(fitxerProductes));
//Per cada Producte de l'array, escriurem una linia
for (int i = 0; i < comptadorDeProductes; i++) {
bw.write(productes[i].toTxt());
bw.newLine(); //Fen una nova linia per al següent producte
}
bw.close();
} catch (IOException e) {
System.out.println("\n-- ERROR. No es pot accedir a fitxer: " + fitxerProductes.getAbsolutePath());
}

}

Y ahora otro método para el proceso opuesto, leer las líneas de texto, separar los valores, parsear a int la referencia, parsear a double el precio, y crear nuevos Productos mediante estos valores.
Para leer usaremos la clase BufferedReader, que a su vez necesita un FileReader, que a su vez necesita un File...

Las líneas que vamos a leer las recibimos como String, y String tiene el método split() con el que podemos ordenar que "trocee" la línea allá donde encuentre los dobles guiones que hemos usado para separar los valores.
Al trocear la linea, vamos a recibir un array que contiene cada "trozo" obtenido. Estos "trozos" son los valores de los atributos, que usaremos para reconstruir los Productos.
El primer "trozo" (posicion 0 del array que nos da split()) es la referencia, pero como este valor se autoasigna de forma interna con el contador que tiene la clase Producte, no lo usaremos para nada.
Por eso antes hicimos el método para resetear este contador.

Con el resto de trozos, crearemos un Producto, lo añadimos al array, y pasamos a la siguiente línea.
Código: [Seleccionar]
public static void recuperarProductesTXT() {

File fitxerProductes = new File("productes.txt");

if (!fitxerProductes.exists())
System.out.println("No hi ha productes guardats per recuperar.");
else {
try {
//Reiniciem l'array actual de Productes. Si tenía Productes, ¡¡s'esborrarán!!
productes = new Producte[MAXIM_PRODUCTES];
comptadorDeProductes = 0;
//També el comptador intern de la clase Producte
Producte.resetTotalProductes();

//Declarem el Reader
BufferedReader br = new BufferedReader(new FileReader(fitxerProductes));
//Obtenim primera linia
String linia = br.readLine();
do {
//Dividim linia per separar valors de atributs
String[] atributs = linia.split(" -- ");
//Reconstruim producte, alguns valors requereixen ser parsejats.
//atributs[0] es la referencia, pero aquest valor s'encarrega el comptador intern de Producte
String nom = atributs[1];
//El preu segurament tindrà coma decimal. Cal canviar-lo per un punt decimal.
atributs[2] = atributs[2].replaceAll(",", ".");
double preu = Double.parseDouble(atributs[2]);
String descrip = atributs[3];
productes[comptadorDeProductes] = new Producte(nom, preu, descrip);
comptadorDeProductes++;

//Producte reconstruir, llegim linia següent
linia = br.readLine();
}while (linia != null); //Repetim procés fins que no quedin linies
//Ja no quedan linies, procés finalitzat
br.close();

} catch (FileNotFoundException e) {
System.out.println("\n-- ERROR. No es troba fitxer: " + fitxerProductes.getAbsolutePath());
} catch (IOException e) {
System.out.println("\n-- ERROR. No es pot accedir a fitxer: " + fitxerProductes.getAbsolutePath());
}
}

}

Importante comprender que cada vez que recuperamos Productos, ya sea del archivo binario o de texto, los posibles Productos creados en ese momento en el array de Productos se borrarán.


Y bueno, ya por último, para poder escoger una forma u otra tanto para guardar como para leer, he cambiado el método menu() añadiendo una variable char para que el usuario elija como quiere hacerlo.
Marco en negrita los cambios:
Citar
   public static void menu(){
      Scanner sc = new Scanner(System.in);
      boolean sortir = false;
      char opcioArxiu = ' ';
      do{
         System.out.println("***** Gestió de factures *****");
         System.out.println("Dades emissor: " + Emissor.getString());
         System.out.println();
         System.out.println("--- Menú d'usuari ---");
         System.out.println("\t1) Crear nova factura");
         System.out.println("\t2) Visualitzar totes les factures");
         System.out.println("\t3) Cercar factura");
         System.out.println("\t4) Eliminar factura");
         System.out.println("\t5) Crear client");
         System.out.println("\t6) Visualitzar client");
         System.out.println("\t7) Crear producte");
         System.out.println("\t8) Visualitzar producte");
         System.out.println("\t9) Canviar emissor");
         System.out.println("\t10) Guardar emissor");
         System.out.println("\t11) Guardar productes");
         System.out.println("\t12) Guardar clients");
         System.out.println("\t13) Guardar factures");
         System.out.println("\t14) Recuperar productes");
         System.out.println("\t15) Sortir");
         System.out.print("La teva opció:");
         int opcio = 0;
         try{
            opcio = sc.nextInt();
         }catch(InputMismatchException ex){
            System.out.print("Entrada no vàlida. No és un número");
         }finally{
            sc.nextLine();
         }
         try{
            switch(opcio){
            case 1: crearNovaFactura();
            break;
            case 2: visualitzarFactures();
            break;
            case 3: cercarFactura();
            break;
            case 4: eliminarFactura();
            break;
            case 5: crearClient();
            break;
            case 6: visualitzarClients();
            break;
            case 7: crearProducte();
            break;
            case 8: visualitzarProductes();
            break;
            case 9: canviarEmissor();
            break;
            case 10: guardarEmissor();
            break;
            case 11:
               System.out.println("[ b ] - Desar com arxiu de bytes");
               System.out.println("[ t ] - Desar com arxiu de text");
               System.out.print("Opcio: ");
               opcioArxiu = sc.nextLine().toLowerCase().charAt(0);
               if (opcioArxiu == 'b')
                  guardarProductes();
               else
                  guardarProductesTXT();
               break;

            case 12: guardarClients();
            break;
            case 13: guardarFactures();
            break;
            case 14:
               System.out.println("[ b ] - Recuperar de l'arxiu de bytes");
               System.out.println("[ t ] - Recuperar de l'arxiu de text");
               System.out.print("Opcio: ");
               opcioArxiu = sc.nextLine().toLowerCase().charAt(0);
               if (opcioArxiu == 'b')
                  recuperarProductes();
               else
                  recuperarProductesTXT();
               break;

            case 15: sortir = true;
            break;
            default:
               System.out.println("Opció no vàlida!");
            }
         }catch(Exception ex){
            System.out.println("S'ha produit un error:");
            System.out.println(ex.getMessage());
         }
      }while(!sortir);
   }


Y ya está, ahora se pueda guardar y recuperar de un archivo de texto plano. Archivo que si lo abrimos con el bloc de notas, ahora si lo puede leer un humano.

Como has visto, este proceso requiere de más pasos, pero bueno, tampoco es muy distinto de la forma anterior.


Revísalo bien y pregunta si algo no lo entiendes. Un saludo.
« Última modificación: 21 de Mayo 2021, 02:45 por Kabuto »
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

doxp11

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 7
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #9 en: 24 de Mayo 2021, 19:15 »
Como podria solucionar este error cuando intento recuperar el fichero txt de clientes?? El codigo es lo mismo pero sustituyendo lo de productes por lo relacionado con clientes...

Error que aparece:
Citar
La teva opció: 15
[ b ] - Recuperar de l'arxiu de bytes
[ t ] - Recuperar de l'arxiu de text
Opcio: t
S'ha produit un error:
Index 3 out of bounds for lenght 3
« Última modificación: 12 de Enero 2022, 20:53 por Ogramar »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 988
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #10 en: 24 de Mayo 2021, 20:37 »
Ese error parece como que alguna línea leída, al hacer split(),no ha proporcionado un array de cuatro elementos, si no de tres. (el error dice que length es 3)
Entonces, al intentar obtener el valor que estaría en la posición [3] del array que retorna el split() se produce ese error de "índice fuera de limites" (out of bounds), porque no existe dicha posición.

Comprueba que las líneas que se escriben en el archivo de texto de clientes, tienen 4 valores, ya que los Clientes tienen cuatro atributos.

Si a caso has decidido guardar solo tres atributos, porque por ejemplo no estás guardando el idCliente, entonces cambia el código para luego solo recoger los elementos de las posiciones [ 0 ], [1] y [2].

NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

doxp11

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 7
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #11 en: 25 de Mayo 2021, 18:28 »
Para poder guardar la factura, podria hacer el mismo codigo que para guardar clientes y productos?

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 988
    • Ver Perfil
Re: Ayuda ejercicio Netbeans Java
« Respuesta #12 en: 26 de Mayo 2021, 02:36 »
Para poder guardar la factura, podria hacer el mismo codigo que para guardar clientes y productos?

Sí, aunque si lo quieres guardar también como texto plano.... va a resultar bastante más complicado.
¿Por qué?
Porque la clase Factura tiene algunos atributos que son objetos "complejos".

Cuando se trata de un String, un int, double.... no hay problema para escribirlos como texto.
Lo único a tener en cuenta, es que al leerlos habrá que convertir a int, a double o lo que sea..

Pero que pasa cuando nos encontramos con atributos más complicados.

Hay un atributo que es el Cliente
Código: [Seleccionar]
    /**
     * El client de la factura
     */
    private Client client = null;
   

Para guardar esto como texto, se puede desglosar sus atributos para luego reconstruirlo.
Aunque se podría optimizar guardando como texto solo el id de cliente, y luego al leer el archivo de texto para reconstruir las Facturas, buscar el cliente a través del id

También hay un atributo que es LocalDateTime

Código: [Seleccionar]
    /**
     * Data de creació de la factura
     */
    private LocalDateTime dataFactura = null;

¿Cómo escribes eso en una línea de texto?
Se puede hacer, extraer dia, mes y año(que son valores int) y escribirlos como texto plano.
Luego al leer, con ese dia, mes y año se puede reconstruir el objeto LocalDateTime.

Pero esto ya exige escribir más código y desarrollar una línea de texto más compleja.


Y espera, ahora viene lo mejor, hay un atributo que es un array de objetos ProducteFactura
Código: [Seleccionar]
    /**
     * Array amb els productes de la factura
     */
    private ProducteFactura[] producteFactures = null;

¿Cómo guardas ese array en texto plano?  :P

Pufff.... se puede guardar desglosando los atributos correspondientes a cada objeto ProducteFactures.
Y luego al leer, reconstruir esos objetos y con ellos, reconstruir el array.
De nuevo esto exige un código más complejo.

Pero es que hay un problema añadido.
Las líneas de texto que usamos para guardar Productos o Clientes.., tienen un número de atributos fijos, así que todas las líneas van a tener la misma cantidad de valores.
Así cuando hacemos split(), ya sabemos de antemano cuántos elementos nos va a devolver y en el código ya sabemos a que atributo corresponde cada elemento obtenido mediante split().

Pero en este caso, resulta que cada objeto Factura, su array de ProducteFactura puede tener distintas longitudes.
Una Factura puede que solo tenga dos productos facturados, otra puede que tenga cinco, otra puede tener diez....

Es decir, que cada línea de texto plano que representa una Factura, el método split() nos va a retornar distintas cantidades de elementos.
Porque cada Factura tendrá un número distinto de elementos facturados.

Aquí ya no solo se va a requerir más código, si no más ingenio para encontrar una forma de crear una línea con toda esa cantidad de datos y que luego sepamos leerla bien para reconstruir los datos.

Tal vez se podría cambiar la estrategia de "una línea por objeto".
Es decir, hasta ahora hacíamos una línea de texto por cada Producto, otra por cada Cliente...

En el caso de las Facturas, se podría usar varias líneas por cada Factura.
La primera línea, todos los atributos menos el array.
Y al final de la línea un número que nos diga cuantas líneas de las que vienen a continuación, corresponde a los elementos del array.

Algo parecido a esto:
Citar
0001 -- 22/03/2021 -- 21.0 -- Efectivo -- 0045 -- 3
-- Datos de un objeto ProducteFacture
-- Datos de un objeto ProducteFacture
-- Datos de un objeto ProducteFacture
0002 -- 12/01/2021 -- 16.0 -- Visa -- 0023 -- 2
-- Datos de un objeto ProducteFacture
-- Datos de un objeto ProducteFacture

Como ves, resulta bastante más complejo y trabajoso guardar las Facturas como texto plano.
Pero se puede.

En cambio si serializamos para guardar en un archivo de bytes, no hay problema, no importa lo complejos que sean los objetos que queremos guardar.

Por cierto, ahora que me fijo, la clase ProducteFacture tiene atributos redundantes.
Podría bastar con que solo guardase referencia del producto y cantidad.
Lo que es el nombre, precio y descripción... ya lo tiene la clase Producte y esos datos se podrían obtener consultando al array de Productes a través de la referencia.
 
« Última modificación: 26 de Mayo 2021, 02:37 por Kabuto »
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

 

Sobre la educación, sólo puedo decir que es el tema más importante en el que nosotros, como pueblo, debemos involucrarnos.

Abraham Lincoln (1808-1865) Presidente estadounidense.

aprenderaprogramar.com: Desde 2006 comprometidos con la didáctica y divulgación de la programación

Preguntas y respuestas

¿Cómo establecer o cambiar la imagen asociada (avatar) de usuario?
  1. Inicia sesión con tu nombre de usuario y contraseña.
  2. Pulsa en perfil --> perfil del foro
  3. Elige la imagen personalizada que quieras usar. Puedes escogerla de una galería de imágenes o subirla desde tu ordenador.
  4. En la parte final de la página pulsa el botón "cambiar perfil".