I File in Java: introduzione alla Serializzazione e Deserializzazione degli oggetti e le Classi Generiche

Introduzione alla Serializzazione degli oggetti:

Con l’utilizzo dei file in Java abbiamo visto come effettuare l’input/output da file di testo in modo da poter leggere e scrivere delle stringhe di carattere da file con estensione .txt ma con la Serializzazione possiamo memorizzare l’intero stato di un oggetto

La serializzazione di un oggetto è un processo che permette di salvarlo in un supporto di memorizzazione sequenziale (come un file), o di trasmetterlo su un canale di comunicazione sequenziale (come una connessione di rete). Lo scopo della serializzazione è quello di salvare e/o trasmettere l’intero stato dell’oggetto in modo che esso possa essere successivamente ricreato nello stesso identico stato dal processo inverso, denominato deserializzazione.


Implementazione della Serializzazione:

Prendiamo in considerazione il gestionale per l’anagrafica realizzato nel “How To” dedicato alle liste in Java, segue il diagramma UML del programma di partenza:

Il programma di base si dimostra abbastanza elementare in quanto abbiamo la classe Persona che ci descrive una persona in base al nome,cognome,età.

Con un piccolo aggiornamento alla classe Persona sono stati implementati i metodi e gli attributi per la gestione della data di nascita e il compleanno della persona sfruttando la classe LocalDate, segue il codice della nuova classe Persona

package pacchetto;

import java.io.Serializable;
import java.time.*;

/**
 * Classe per la gestione del dato persona
 * @author MarioLazzaro
 */
public class Persona implements Serializable {
	
	private String nome, cognome;
	private LocalDate dataNascita, compleanno;
	private int eta;
	
	/**
	 * Costruttore con parametri
	 * @param nome nome della persona
	 * @param cognome cognome della persona
	 * @param dataNascita data di nascita della persona 
	 */
	public Persona(String nome, String cognome, LocalDate dataNascita) {
		this.nome = nome;
		this.cognome=cognome;
		this.dataNascita=dataNascita;
		compleanno = LocalDate.of(LocalDate.now().getYear(),dataNascita.getMonthValue(),dataNascita.getDayOfMonth());
		eta=calcEta();
	}

    public Persona(String nome,String cognome, int eta) {
        this.cognome = cognome;
        this.eta = eta;
        this.nome = nome;
    }
	private int calcEta() {
		LocalDate annoCalcolo = LocalDate.now().minusYears(dataNascita.getYear());
		eta= annoCalcolo.getYear();
		if (LocalDate.now().isBefore(compleanno)) {
			eta--;
		}
		return eta;
	}

	public void printAll(){
		System.out.println(" Nome: "+this.getNome()+"\n Cognome: "+this.getCognome()+"\n Data di Nascità: "+this.getDataDiNascita()+"\n Eta: "+this.getEta());
	}
	
	public String getNome() {
		return nome;
	}
	public String getCognome() {
		return cognome;
	}
	public LocalDate getDataDiNascita() {
		return dataNascita;
	}
	public LocalDate getCompleanno() {
		return compleanno;
	}
	public int getEta(){
		return eta;
	}
	
	

    public void setNome(String nome) {
        this.nome = nome;
    }

    public void setCognome(String cognome) {
        this.cognome = cognome;
    }
	public void setDataDiNascita(LocalDate data){
		this.dataNascita=data;
	}
	public void setEta(int eta){
		this.eta=eta;
	}
	public void setCompleanno(LocalDate compl){
		this.compleanno=compl;
	}
}

Adesso che abbiamo illustrato il codice della classe Persona proseguiamo con l’illustrazione del diagramma UML del programma completo;

Come possiamo vedere la classe Persona si relazione con le seguenti classi:

Persona è associata con la MainClass che è associata con la Classe FileTools e nel mentre Persona implementa l’interfaccia Serializable di Java.io

Def. di Interfaccia

Un’interfaccia è un costrutto della programmazione orientata agli oggetti che definisce un contratto per le classi che la implementano. Si tratta di un insieme di metodi astratti (cioè metodi senza corpo) che devono essere forniti dalle classi che dichiarano di implementare l’interfaccia. Le interfacce stabiliscono quindi “cosa” una classe dovrebbe fare, ma non “come” farlo, lasciando alle classi concrete il compito di fornire l’implementazione effettiva.

Adesso che abbiamo definito la classe Persona sia nel codice che nelle sue relazioni, andiamo ora ad analizzare la classe FileTools che ha come obiettivo quello di gestire i file e la Serializzazione/Deserializzazione degli oggetti, segue il codice della classe FileTools

package pacchetto;
import java.io.*;

/**
 * Classe contenente metodi utili all'utilizzo delle classi bult-in Java per la gestione dei file 
 * @author Mario Lazzaro
 * @version 0.0.1
 */
public class FileTools <T> {
    static File file;

    public FileTools(String nomeFile) throws IOException{
        file = new File((nomeFile+".bin"));
        if(file.createNewFile()==true){
            System.out.println("Il file "+file.getName()+" è stato creato nel seguente percorso: "+file.getAbsolutePath());
        }
        System.out.println("Il file "+file.getName()+" è stato caricato nel seguente percorso: "+file.getAbsolutePath());
    };
    /**
     * Metodo per serializzare l'oggetto nel file
     * @param oggetto l'oggetto da serializzare nel file
     * @throws IOException
     */
    public void serializza(T oggetto) throws IOException{
        FileOutputStream out = new FileOutputStream(file);
        try (ObjectOutputStream stream = new ObjectOutputStream(out)) {
            stream.writeObject(oggetto);
        }
    };
    /**
     * Metodo per deserializzare l'oggetto dal file
     * @return oggetto deserializzato dal file
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws EOFException
     */
    @SuppressWarnings("unchecked")
    public T deserializza()throws IOException, ClassNotFoundException,EOFException{
        FileInputStream in = new FileInputStream(file);
        T oggetto=null;
        try (ObjectInputStream stream = new ObjectInputStream(in)) {
            oggetto = (T)stream.readObject();
        }catch(ClassNotFoundException exception){
            System.err.println(exception.getMessage());
        }catch(EOFException eofException){
            System.err.println(eofException.getMessage());
        }
        return oggetto;  
    };
}

Come possiamo vedere dal codice per la serializzazione e deserializzazione vengono utilizzate le classe built-in di Java FileInputStream e FileOutputStream:

Le classi FileInputStream e FileOutputStream in Java appartengono al pacchetto java.io e vengono utilizzate per leggere e scrivere dati da e verso file, a livello di byte. Queste classi permettono di lavorare direttamente con i file di sistema e sono particolarmente adatte per la gestione di dati binari, come immagini, file audio, video o file compressi.

1. FileInputStream

La classe FileInputStream serve per leggere dati da un file sotto forma di byte.

2. FileOutputStream

La classe FileOutputStream viene utilizzata per scrivere dati in un file, byte per byte.

Gestione risorse: È fondamentale chiudere gli stream dopo l’uso, per liberare risorse di sistema. In Java, si consiglia di utilizzare il costrutto try-with-resources per gestire automaticamente la chiusura degli stream.

Approfondimento sulle classi generiche:

La Classe FileTools per come è stata scritta si definisce una Classe generica (Che si contraddistingue con i tipo dato parametrizzati con le parentesi angolari <>); Le classi generiche in Java sono un potente meccanismo che consente di scrivere classi e metodi in modo indipendente dal tipo di dato utilizzato, rendendo il codice più riutilizzabile e sicuro. In pratica, le generics permettono di specificare il tipo di dato quando si crea un’istanza della classe, evitando l’uso di conversioni esplicite e prevenendo errori di tipo a tempo di esecuzione.

Struttura di una classe generica

Una classe generica viene dichiarata con un parametro di tipo (o più parametri di tipo) che viene specificato tra angoli acuti <>. Questo parametro può essere utilizzato all’interno della classe in tutti i punti in cui si farebbe riferimento a un tipo di dato specifico

public class FileTools <T>{
...
public void serializza(T oggetto) throws IOException{
        FileOutputStream out = new FileOutputStream(file);
        try (ObjectOutputStream stream = new ObjectOutputStream(out)) {
            stream.writeObject(oggetto);
        }

    }
...
}

Qui T è un parametro di tipo generico. Quando creiamo un’istanza della classe, possiamo specificare il tipo effettivo che vogliamo utilizzare:

...
private static FileTools<ArrayList<Persona>> file = null;
...
file = new FileTools<>("dati");

Adesso andiamo ad analizzare la MainClass:

package pacchetto;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Scanner;


public class MainClass {

    private static ArrayList<Persona> anagrafica =null;
    private static FileTools<ArrayList<Persona>> file =null;


private static void waitUser(){
    Scanner input= new Scanner(System.in);
    System.out.println("Press ENTER to continue");
    input.nextLine();
    System.out.println("");
}

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
    file = new FileTools<>("dati");

    Scanner input= new Scanner(System.in);  
    


    
    
    try {
        anagrafica = file.deserializza();
        MainClass.waitUser();
    } catch (Exception e) {
        System.err.print("ATTENZIONE SI È RISCONTRATO UN PROBLEMA NELLA DESERIALIZZAZIONE DEGLI OGGETTI:");
        System.err.println(e);
        anagrafica = new ArrayList<>();
        MainClass.waitUser();
    }

    int scelta;
    do { 
        String num = String.valueOf(anagrafica.size());
        System.out.print("\033[H\033[2J");  
        System.out.flush(); 
        System.out.println("Seleziona le seguenti opzioni:");
        System.out.print("""

                ┌──────────────────────────────────────────────────────┐┌────────────────────────────────────────┐
                │ Gestionale anagrafica                                ││ Elementi in archivio:%s                │
                └──────────────────────────────────────────────────────┘└────────────────────────────────────────┘
                0 - Reinizializza il File
                1 - Salva i dati in Memoria
                2 - Crea una nuova persona
                3 - Stampa tutta la anagrafica caricata
                4 - Ricarica i dati dalla memoria
                5 - Elimina elemento by Indice
                6 - Elimina elemento by Cognome
                7 - Modifica elemento by Indice

                11- Chiudi il programma
                >>
                """.formatted(num,"s"));
        
        scelta=input.nextInt();
        switch (scelta) {
            case 0 : //Nuovo array
                anagrafica = new ArrayList<>();
                break;
            case 1 : //salva
                file.serializza(anagrafica);
                break;
            case 2 : {
            //nuova persona
                System.out.println("Fornire il nome:");
                String nome =input.next();
                System.out.println("Fornire il cognome:");
                String cognome =input.next();
                System.out.println("Fornire la data di nascita (YYYY-MM-DD):");
                CharSequence eta =input.next();
                anagrafica.addLast(new Persona(nome, cognome, LocalDate.parse(eta)));
                MainClass.waitUser();  
                break;  
            }

            case 3 : {
            //stampa anagrafica
                for (int i=0; i<anagrafica.size();i++) {
                    System.out.println("____________________ \n Indice: "+i+"");
                    Persona persona = (Persona) anagrafica.get(i);
                    persona.printAll();
                }
                MainClass.waitUser();
                break;
                }
            case 4 : {
                //ricarica da file
                anagrafica = file.deserializza();
                break;
                }
            case 5 : {
                //elimina elemento da indice
                System.out.println("Stampo a schermo tutti gli elementi in base all'indice");
                for (int i=0; i<anagrafica.size();i++) {
                    System.out.println("____________________ \n Indice: "+i+"");
                    Persona persona = (Persona) anagrafica.get(i);
                    persona.printAll();
                }
                System.out.print("Fornire l'indice della persona da eliminare:");
                int elementodaeliminare = input.nextInt();
                anagrafica.remove(elementodaeliminare);
                System.out.println("Elemento eliminato");
                MainClass.waitUser();
                break;
            }
            case 6 :{
                //elimina in base al cognome
                ArrayList<Persona> anagraficabycognome;
                anagraficabycognome = new ArrayList<>();
                System.out.print("Fornire il cognome:");
                String cognome = input.next();
                for (int i=0; i<anagrafica.size();i++) {
                    if(anagrafica.get(i).getCognome().contains(cognome)){
                        anagraficabycognome.add(anagrafica.get(i));
                    }
                }
                System.out.println("Ricerca effettuata! Proseguo con la stampa degli elementi trovati:");
                for (int i=0; i<anagraficabycognome.size();i++) {
                    System.out.println("____________________ \n Indice: "+i+"");
                    Persona persona = (Persona) anagraficabycognome.get(i);
                    persona.printAll();
                }
                if(anagraficabycognome.isEmpty()){
                    System.err.println("Nessun elemento trovato!");
                    System.console().wait();
                    break;
                }else{
                System.out.print("Fornire l'indice della persona da eliminare:");
                int elementodaeliminare = input.nextInt();
                anagrafica.remove(elementodaeliminare);
                System.out.println("Elemento eliminato");
                }
                MainClass.waitUser();
                break;
            }
            case 7 : {
                //Modifica by indice
                System.out.println("Stampo a schermo tutti gli elementi in base all'indice");
                for (int i=0; i<anagrafica.size();i++) {
                    System.out.println("____________________ \n Indice: "+i+"");
                    Persona persona = (Persona) anagrafica.get(i);
                    persona.printAll();
                }
                System.out.print("Fornire l'indice della persona da modificare:");
                int elementodamodicare = input.nextInt();
                System.out.print("\033[H\033[2J");  
                System.out.flush(); 
                System.out.println("Elemento da modificare:");
                anagrafica.get(elementodamodicare).printAll();
                System.out.print("""
                        --------------------
                        Selezionare il campo da modicare:
                        1 - Nome
                        2 - Cognome
                        3 - Data di nascità

                        >>>
                        """);
                int s = input.nextInt();
                switch (s) {
                    case 1:
                        System.out.print("\n\n Fornire il nuovo nome dell'elemento:");
                        String nome = input.next();
                        anagrafica.get(elementodamodicare).setNome(nome);
                        break;
                    case 2:
                        System.out.print("\n\n Fornire il nuovo Cognome dell'elemento:");
                        String cognome = input.next();
                        anagrafica.get(elementodamodicare).setCognome(cognome);;     
                        break;   
                    case 3:
                        System.out.println("Fornire la nuova data di nascita (YYYY-MM-DD):");
                        CharSequence data =input.next();
                        anagrafica.get(elementodamodicare).setDataDiNascita(LocalDate.parse(data));       
                    default:
                        break;
                }
                System.out.print("\033[H\033[2J");  
                System.out.flush();
                System.out.println("L'elemento modificato ora risulta avere le seguenti informazioni:");
                anagrafica.get(elementodamodicare).printAll();
                MainClass.waitUser(); 
                break;
            }
            case 11: {
                scelta =-1;
                break;
            }

            default : {
                //
                System.err.println("Nessuna opzione valida scelta");
                MainClass.waitUser();
                break;
             }
            }
    } while (scelta!=-1);
    input.close();
    
    
}}

di Mario Lazzaro

Studente V ITI B

Related Post