oggetti

dalla programmazione procedurale a quella ad oggetti

Il problema

Vogliamo gestire una certa quantità di autoveicoli dei quali ci interessano alcuni dati: la targa, il modello e quanti km riesce a fare con un litro di carburante, fornendo poi la lunghezza di un percorso vorremmo sapere quanto si spende con un determinato autoveicolo (supponendo che il carburante costi 1,5€ al litro).

Il nostro programma dovrà consentire all'utente di inserire diversi autoveicoli (al massimo 100) e in base alla targa e alla distanza calcolare la spesa del percorso.

Possibile soluzione

Dato che gli autoveicoli potrebbero essere molti useremo un vettore, visto che però le informazioni da registrare sono almeno tre (targa, modello e km/l) saremo costretti ad usare tre vettori: uno di stringhe per la targa, un altro di stringhe per il modello ed uno di double per i consumi. Ovviamente tutte le informazioni di un veicolo saranno registrate allo stesso indice in tutti e 3 i vettori.

Quella mostrata qui sotto è una possibile soluzione.

File: Programma.java

public class Programma extends Application{
    String targa[];
    String modello[];
    double kmAlLitro[];
    int autoRegistrate = 0;
    
    TextField inTarga = new TextField();
    TextField inModello = new TextField();
    TextField inLitri = new TextField();
    TextField calcTarga = new TextField();
    TextField calcKm = new TextField();
    Label risposta = new Label();
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        Button aggiungi = new Button("aggiungi auto");
        Button calcolaSpesa = new Button("calcola spesa");
        GridPane gp = new GridPane();
        gp.add(new Label("targa:"),0,0);
        gp.add(inTarga,1,0);
        gp.add(new Label("modello:"),0,1);
        gp.add(inModello,1,1);
        gp.add(new Label("consumo:"),0,2);
        gp.add(inLitri,1,2);
        gp.add(aggiungi,1,3);
        gp.add(new Label("targa:"),0,4);
        gp.add(calcTarga,1,4);
        gp.add(new Label("km/l:"),0,5);
        gp.add(calcKm,1,5);
        gp.add(calcolaSpesa,1,6);
        gp.add(new Label("spesa:"),0,7);
        gp.add(risposta,1,7);
        
        Scene scena = new Scene(gp);
        primaryStage.setScene(scena);
        primaryStage.setTitle("automobili");
        primaryStage.show();
        
        aggiungi.setOnAction(e->aggiungiAuto());
        calcolaSpesa.setOnAction(e->azioneSpesa());
        // ci prendiamo una licenza: non ci saranno mai più di 100 automobili
        targa = new String[100];
        modello = new String[100];
        kmAlLitro = new double[100];
    }
    
    public static void main(String args[]){
        launch(args);
    }
    
    private void aggiungiAuto(){
        String t = inTarga.getText();
        String m = inModello.getText();
        double km = Double.parseDouble(inLitri.getText());
        
        targa[autoRegistrate] = t;
        modello[autoRegistrate] = m;
        kmAlLitro[autoRegistrate] = km;
        autoRegistrate++;
        risposta.setText(t+" registrata");
    }
    
    private void azioneSpesa(){
        String t = calcTarga.getText();
        double kilometri = Double.parseDouble(calcKm.getText());
        int trovato = -1;
        double spesa;
        
        for(int i=0;i<autoRegistrate;i++){
            if(targa[i].equals(t)) {
                trovato=i;
            }
        }
        if(trovato!=-1) {
            spesa = kilometri/kmAlLitro[trovato]*1.5;
            risposta.setText("l'automobile "+modello[trovato]+" spende "+spesa );
        }else{
            risposta.setText("non trovo "+t);
        }
        
    }
    
}

Questa soluzione funziona... e non è poco! Esiste però una diversa corrente di sviluppo del software che dice che è possibile seguire un altro approccio: visto che abbiamo parlato di autoveicoli dovremmo creare una loro rappresentazione!

Programmazione ad oggetti

Quello che ci proponiamo di fare è di spostare l'attenzione dal "come fare le cose" (programmazione procedurale) al "modellare le entità della realtà" (programmazione ad oggetti). La domanda a cui dobbiamo rispondere è: "quali sono gli elementi fondamentali che compongono la realtà di cui si deve occupare il programma?"
È immediato che in questo esempio l'elemento da gestire è l'autoveicolo, a questo punto segue la domanda: "cosa è un Autoveicolo?". E con questo intendiamo sia "cosa caratterizza una autoveicolo" che "quali azioni possiamo fare con un autoveicolo".

Visto quanto abbiamo detto finora per noi un Autoveicolo è un un oggetto che:

Java ci permette di modellare la realtà in questo modo usando le classi. In effetti le abbiamo già create ogni volta che abbiamo scritto un programma ma fino a questo momento i programmi erano composti da una sola di esse e le abbiamo scritte senza prestare particolare attenzione a cosa stavamo rappresentando (solitamente una interfaccia grafica con delle funzionalità), quello che succede di solito quando si realizzano programmi più grandi è che si ha a che fare con più classi che usate insieme ci permettono di realizzare le funzionalità desiderate modellando la realtà che ci interessa.

Questo approccio porta alcuni vantaggi che diventano sempre più evidenti all'aumentare della dimensione del programma che si sta trattando, due tra i più evidenti sono:

La struttura che Java ci mette a disposizione per modellare queste entità è la classe:

Una classe è un costrutto usato come modello per creare oggetti. Il modello comprende attributi e metodi e fornisce i valori iniziali per lo stato. In Java definendo una classe si definisce un nuovo tipo di dato.

Una classe in Java viene definita in un file che ha il suo stesso nome utilizzando la parola chiave class seguita dal nome dell'entità e da un blocco di codice racchiuso tra parentesi graffe: al loro interno è possibile trovare dichiarazione di proprietà o metodi che riguardano l'entità.

Le proprietà sono usuali dichiarazioni di variabili fatte al livello di classe e i metodi sono delle funzioni che possono far riferimento alle proprietà dell'oggetto.

class Nome {
    // proprietà
    
    // metodi 
}
Una proprietà è una variabile dichiarata all'interno di una classe e concorre a rappresentarne lo stato.

...e quindi lo stato di un oggetto è rappresentato dal valore di tutte le sue proprietà. Nel nostro esempio lo stato di un Autoveicolo sono i valori della sua targa, del modello e del consumo.

Un metodo è un blocco di istruzioni identificato da un nome e dalla sequenza dei tipi dei parametri. Il nome e la sequenza dei tipi di parametri si chiama firma. Viene usato per raggiungere uno specifico obiettivo, può avere zero o più parametri e ritornare o meno un risultato.

Mentre per distinguere una proprietà dall'altra si usa solo il nome, per distinguere due metodi non basta indicarne il nome perché con lo stesso nome possono esserci più metodi. Allora per chiarire a quale ci si riferisce, in modo tale da disambiguare l'identificazione e rendere l'invocazione del metodo univoca si passa a controllare i parametri. Di questi si controlla la sequenza dei tipi di argomenti e verrà invocato quello che ha l'esatta corrispondenza.
Chiaramente Java non permette di creare due metodi con la stessa firma, cioè se si crea un metodo con lo stesso nome di un altro fatto in precedenza ma la sequenza dei tipi è diversa non ci sono problemi. Se invece corrisponde verrà dato errore.

Esempio
public int faiMessaggio(String nome, int anni) e public int faiMessaggio(String nome, int eta) è errore perché corrisponde il nome del metodo, entrambi hanno come primo parametro una stringa (non importa il nome della variabile) ed entrambi hanno il tipo successivo intero quindi ci sarebbe ambiguità.
public int faiMessaggio(String nome, String anni) e public int faiMessaggio(String nome, int anni) è corretto perché l'ultimo argomento in un caso è una stringa e nell'altro caso è un intero.

Nell'esempio dell'autoveicolo un metodo ci consente data una distanza (parametro) di calcolare quanto spenderemo per il percorso (risultato). In pratica un metodo è una funzione definita in una classe che oltre ai parametri può utilizzare anche i valori presenti nello stato, per calcolare la spesa servirà anche di utilizzare il consumo di un determinato autoveicolo (cioè il valore della sua proprietà "consumo").

Quale delle seguenti fa parte dello stato di Autoveicolo?

spesa no, è una azione che può compiere, un metodo targa giusto, è una delle sue proprietà Autoveicolo no, questo è il nome della classe modello giusto, è una delle sue proprietà

Istanze

Una classe ci permette di definire come si comporta una certa entità... abbiamo ad esempio definito quali caratteristiche ha un Autoveicolo qualsiasi ma come posso fare per creare qualcosa che rappresenti la mia particolare automobile che è una "Stelvio" targata "CF876RT" che percorre 14km con un litro di carburante? Posso farlo creando una particolare istanza della classe Autoveicolo.

Data la nostra classe di esempio potremmo creare tre sue diverse istanze: automobileMario, automobileLucia e automobileLuigiche avranno lo stesso tipo ma ciascuna avrà un proprio stato e quindi una sua targa, un suo modello e un suo consumo.

Un oggetto è una istanza di una classe e viene creato a tempo di esecuzione con l'operatore new.

In pratica new riserva dello spazio in memoria per registrare le informazioni riguardanti una singola istanza di una classe (un oggetto) e quello che ritorna è un riferimento alla zona di memoria appena riservata. Per questo motivo se io creo due oggetti che hanno gli stessi valori su ogni proprietà (immaginiamo due Autoveicoli con stessa targa, stesso modello e stesso consumo) e poi li confronto usando == questo mi restituirà falso perché vengono confrontati i due riferimenti (che sono numeri che indicano l'indirizzo di memoria in cui si trova l'oggetto) che sono diversi (questo è il motivo per cui gli oggetti String non si confrontano con == ma utilizzando il metodo equals()).

Quale dei seguenti non è un operatore?

+ no, più è un operatore che esegue la somma degli operandi new no, new crea nuovi oggetti * no, esegue il prodotto tra due numeri _ giusto, la linea bassa non è un operatore

Cosa conterrà la variabile falcon dopo la seguente istruzione?
Autoveicolo falcon = new Autoveicolo("xf675ee", "astronave", 4)?

tre valori no, una variabile contiene un solo valore un riferimento giusto, alla memoria principale la classe Autoveicolo no, un riferimento ad una sua istanza

Ogni istanza di una classe avrà le stesse proprietà ma con valori diversi, per accedere a queste proprietà (o anche ai metodi) si usa la notazione punto: se voglio accedere ad una proprietà di un oggetto scrivo il nome di una istanza seguita dal punto e dal nome della proprietà (o del metodo) che si vuole usare. Se ho creato una istanza automobileMario di Autoveicolo posso impostare la sua targa scrivendo automobileMario.targa="xd987tt".

Costruttore

Un particolare metodo è quello che viene usato quando costruiamo una nuova istanza di una classe: il costruttore. Si riconosce perché ha lo stesso nome della classe e nessun tipo ritornato (non c'è scritto void, è proprio assente). Per ogni classe è possibile definire molteplici costruttori con parametri diversi, posso per esempio pensare ad un costruttore di Autoveicolo che voglia il solo numero di targa e uno diverso che voglia sia numero di targa che il modello. Se non si definisce nessun costruttore nella classe java ne definisce uno predefinito che non accetta nessun parametro.

Quale è un possibile costruttore per la classe Tavolo?

public int calcola(int a, int b){...} non si chiama come la classe e ritorna un valore di tipo int public void Tavolo(){...} void non deve essere presente public Tavolo(){...} giusto, un costruttore senza parametri public Tavolo(int gambe){...} giusto, un costruttore che vuol sapere quante gambe ha il tavolo

Possiamo a questo punto rivedere una riga di codice che abbiamo scritto molte volte: Button pulsante = new Button(). Con questa istruzione abbiamo creato una nuova istanza (un oggetto) della classe Button usando l'operatore new e chiamando dopo di lui uno dei suoi costruttori.
Ma si può scrivere anche la dichiarazione: Button pulsante = new Button("Premi qui") e anche questa volta abbiamo invocato un metodo che si chiama come la classe. L'unica differenza è che il primo metodo invocato non aveva argomenti, questo qui ha un argomento di tipo stringa. Quale è lo scopo dei 2 costruttori della classe Button? Avere modi diversi per creare dei Button (sono tre), chi ha scritto la classe ha deciso in quali modi sia possibile instanziarla, possiamo ipotizzare che nel primo caso si vuole un pulsante senza testo e nel secondo caso si vuole un pulsante con del testo al suo interno.

Confronto

Proviamo a confrontare due programmi che svoilgono lo stesso lavoro: il primo con un approccio ad oggetti, il secondo procedurale.

La nuova versione ad oggetti usa un oggetto: Autoveicolo con le proprietà marca, modello e kmAlLitro mentre i metodi sono: un costruttore un altro per calcolare il costo di un percorso.

La vecchia versione procedurale del programma non usa gli oggetti

File: Autoveicolo.java

public class Autoveicolo {
	String targa;
	String modello;
	double kmAlLitro;
	
	public Autoveicolo(String t, String m, double l){
		targa = t;
		modello = m;
		kmAlLitro = l;
	}
	
	public double costoPercorso(double numeroKilometri) {
		return numeroKilometri/kmAlLitro*1.5;
	}
}

File: Programma.java

public class Programma extends Application{
	
	String targa[];
	String modello[];
	double kmAlLitro[];
	Autoveicolo automobili[];
	int autoRegistrate = 0;
	
	TextField inTarga = new TextField();
	TextField inModello = new TextField();
	TextField inLitri = new TextField();
	TextField calcTarga = new TextField();
	TextField calcKm = new TextField();
	Label risposta = new Label();
	
	@Override
	public void start(Stage primaryStage) throws Exception {
		Button aggiungi = new Button("aggiungi auto");
		Button calcolaSpesa = new Button("calcola spesa");
		GridPane gp = new GridPane();
		gp.add(new Label("targa:"),0,0);
		gp.add(inTarga,1,0);
		gp.add(new Label("modello:"),0,1);
		gp.add(inModello,1,1);
		gp.add(new Label("consumo:"),0,2);
		gp.add(inLitri,1,2);
		gp.add(aggiungi,1,3);
		gp.add(new Label("targa:"),0,4);
		gp.add(calcTarga,1,4);
		gp.add(new Label("km/l:"),0,5);
		gp.add(calcKm,1,5);
		gp.add(calcolaSpesa,1,6);
		gp.add(new Label("spesa:"),0,7);
		gp.add(risposta,1,7);
		
		Scene scena = new Scene(gp);
		primaryStage.setScene(scena);
		primaryStage.setTitle("automobili");
		primaryStage.show();
		
		aggiungi.setOnAction(e->aggiungiAuto());
		calcolaSpesa.setOnAction(e->azioneSpesa());
		// ci prendiamo una licenza: non ci saranno mai più di 100 automobili
		targa = new String[100];
		modello = new String[100];
		kmAlLitro = new double[100];
		automobili = new Autoveicolo[100];
	}
	
	public static void main(String args[]){
		launch(args);
	}
	
	private void aggiungiAuto(){
		String t = inTarga.getText();
		String m = inModello.getText();
		double km = Double.parseDouble(inLitri.getText());
		
		targa[autoRegistrate] = t;
		modello[autoRegistrate] = m;
		kmAlLitro[autoRegistrate] = km;automobili[autoRegistrate] = new Autoveicolo(t, m, km);
		autoRegistrate++;
		risposta.setText(t+" registrata");
	}
	
	private void azioneSpesa(){
		String t = calcTarga.getText();
		double kilometri = Double.parseDouble(calcKm.getText());
		int trovato = -1;Autoveicolo trovata = null;
		double spesa;
		
		for(int i=0;i<autoRegistrate;i++){
			if(targa[i].equals(t)) {
				trovato=i;
			}if(automobili[i].targa.equals(t)) {
				trovata = automobili[i];
			}
		}
		if(trovato!=-1) {
			spesa = kilometri/kmAlLitro[trovato]*1.5;
			risposta.setText("l'automobile "+modello[trovato]+" spende "+spesa );
		}
		if(trovata!=null) {
			spesa = trovata.costoPercorso(kilometri);
			risposta.setText("l'automobile "+trovata.modello+" spende "+spesa );
		}else{
			risposta.setText("non trovo "+t);
		}
	}
}

Facendo riferimento alla classe Autoveicolo definito qui sopra

Autoveicolo autoAnna = new Autoveicolo("uj7643pl", "Ferrari 488", 11);
Autoveicolo autoLuigi = new Autoveicolo("tg530oo", "Fiat panda", 21); 
autoLuigi.kmAlLitro = 29;

Quanto valgono rispettivamente autoAnna.kmAlLitro e autoLuigi.kmAlLitro ?

11 e 29 giusto, la prorietà kmAlLitro di autoAnna è stata impostata con il costruttore, quella di autoLuigi è stata impostata nell'ultima riga 29 e 29 nella terza riga abbiamo modificato soltanto il valore delle proprietà kmAlLitro di autoLuigi 11 e 21 il valore della proprietà kmAlLitro di autoLuigi è stato modificato

me stesso

Capita a volte che all'interno di un metodo in una classe serva di far riferimento all'oggetto stesso, in questo caso è possibile usare la parola chiave this che indica appunto l'oggetto corrente. Perché mai dovrebbe servire? In generale per evitare di far confusione tra i nomi delle proprietà e i nomi dei parametri dei metodi.

Proviamo a vedere come è possibile riscrivere il costruttore del nostro Autoveicolo (in effetti normalmente vengono scritti così):

File: Autoveicolo.java aggiornato

public class Autoveicolo {
    String targa;
    String modello;
    double kmAlLitro;
    
    public Autoveicolo(String t, String m, double l){
        targa = t;
        modello = m;
        kmAlLitro = l;
    }public Autoveicolo(String targa, String modello, double kmAlLitro){
        this.targa = targa;
        this.modello = modello;
        this.kmAlLitro = kmAlLitro;
    }
}

L'istruzione this.targa = targa serve a copiare il valore della variabile targa passata al costruttore nella proprietà targa di questo oggetto (quello che stiamo creando).

Dopo un possibile senso di confusione iniziale il costruttore che usa this è più chiaro da leggere rispetto a quello che non usa this.

Organizzare gli oggetti

Abbiamo già detto che un programma è spesso formato da molte classi, un paio di meccanismi ci consentono di tenerle organizzate:

I modificatori public o private che vengono scritti prima del tipo nella dichiarazione delle variabili o dei metodi servono per definire se una proprietà (o un metodo) possono essere visti al di fuori della classe che si sta definendo, in realtà questi modificatori sono quattro: public, protected, private e uno senza nome, sono però facilmente comprensibili:

modificatore di visibilitàla proprità/metodo si può usare nella
classepacchettosottoclassetutti
publicsisisisi
protectedsisisino
sisinono
privatesinonono

Attenzione alla terza riga: se non scrivo nulla faccio una scelta ben precisa e diversa dalle altre!

Una buona strategia generale è quella di evitare che gli altri oggetti possano modificare lo stato (cioè una delle proprietà) degli oggetti direttamente ma di creare appositi metodi che chiedano all'oggetto di modificare il suo stato. Doppio vantaggio:

Per un approfondimento vedi Controlling Access to Members of a Class

Esempio: il risparmio vincolato

Nel momento in cui otteniamo del denaro si presentano varie opportunità: la prima è quella di spenderlo tutto e subito, un'altra è quella di investirlo in modo che produca del guadagno. Tra le possibilità di investimento c'è quella di vincolarlo per un periodo di tempo predeterminato (ad esempio un anno), in tal caso verremo messi a conoscenza, nel momento del deposito, e quindi nel momento della creazione del vincolo quale è la percentuale di interessi (percentuale di guadagno) che avrò e da questa percentuale si calcola la somma in effetti guadagnata.

Quali sono le informazioni che caratterizzano un risparmio vincolato?
Innanzi tutto chi è il possessore di tale risparmio, quindi il nome ed il cognome. Si potrebbe aggiungere qualcosa come il codice fiscale per identificare in modo unico colui a cui si riferisce tale risparmio vincolato perché ci sono alcune persone con lo stesso nome e cognome (omonimi). Visto lo scopo puramente esemplificativo possiamo comunque ipotizzare che gli omonimi non esistono e quindi per capire di quale individuo si tratta è sufficiente il nome e cognome. Un'altra informazione necessaria è il numero del risparmio vincolato e questo deve essere unico; una persona può avere più risparmi vincolati e quindi serve un sistema per distinguerli. Seguono la cifra vincolata, la percentuale di interessi garantita, l'anno del vincolo. Anche in quest'ultima informazione, per semplificare, ipotizziamo una cosa che non accade nella realtà: i soldi verranno vincolati solo il primo gennaio di un dato anno (i soldi andranno chiaramente consegnati prima).
Abbiamo ora fatto l'analisi della realtà che andava modellata avendo scelto ciò che è importante registrare, andiamo a scrivere intanto queste informazioni ottenendo:

file RisparmioVincolato.java

public class RisparmioVincolato {
   String nome;
   String cognome;
   int codice;
   double importo;
   short anno;
   double interessiPercentuale;
}

Quali sono le informazioni che deve avere un oggetto appena si crea?
Stabiliamo 2 alternative:

avendo previsto le 2 alternative sopra descritte avremo bisogno di due costruttori la cui sintassi, lo ricordiamo, è piuttosto semplice: NomeClasse(tipo argomento, tipo argomento, ...) { ... }.

La classe sopra con in più i costruttori sarà:

file RisparmioVincolato.java con i costruttori

public class RisparmioVincolato {
   String nome;
   String cognome;
   int codice;
   double importo;
   short anno;
   double interessiPercentuale;

   /* Imposto tutti valori scelti da me senza nessun significato particolare
    * ma che chiaramente non possono riferirsi ad alcun vincolo 
    * di risparmio reale 
    */
   RisparmioVincolato() {
      nome = "<indefinito>";      
      cognome = "<indefinito>";
      codice = -1;
      importo = 0.0;
      anno = -1;
      interessiPercentuale = 0.0;
   }

   public RisparmioVincolato(String nome, String cognome, int codice, double importo, short anno, double interessiPercentuale) {
      this.nome = nome;
      this.cognome = cognome;
      this.codice = codice;
      this.importo = importo;
      // Ci mettiamo anche un banale controllo sulle date: l'anno dal 1970 al 2025 
      if(anno < 1970) {
         anno = 1970;
      } else if(anno > 2025) {
         anno = 2025;
      }      
      this.anno = anno;
      this.interessiPercentuale = interessiPercentuale;
   }
}

Sarebbe utile che l'oggetto ci permetta di sapere sia quanto si guadagnerà allo scadere del periodo, sia quanti soldi si avranno in totale (la somma vincolata alla quale si aggiungono gli interessi). Affinché il metodo possa fornire il guadagno, che è un numero con la virgola, dobbiamo scegliere tra i tipi float e double. La preferennza ricade su double per avere la massima precisione. Il metodo ha bisogno di argomenti? Detto in modo diverso, il metodo ha bisogno di ulteriori informazioni per poter calcolare il risultato? La risposta è no perché tutte le informazioni necessarie sono già memorizzate nelle proprietà della classe: sono necessari sia l'importo che il tasso (la percentuale) di interessi.
Discorso analogo per il capitale (cioè i soldi posseduti) totale.

public class RisparmioVincolato {

    // stesse dichiarazioni di proprietà e costruttori dell'esempio sopra 

   public double fornisciGuadagno() {
      double guadagno = importo*interessiPercentuale/100; //formula per il calcolo degli interessi
      return guadagno;
   }

   public double fornisciCapitale() {
      double guadagno = fornisciGuadagno(); //anziché rifare il calcolo invochiamo il metodo sopra
      double capitale = guadagno + importo;
      return capitale;
   }
}

Ora, dopo aver dichiarato la classe RisparmioVincolato con tutte le sue caratteristiche, usiamola in un programma normale. Possiamo pensare di avere un form per l'inserimento dei dati relativi ad un investimento di tipo risparmio vincolato. Al click di un pulsante per l'acquisizione dei dati, tutto ciò che è presente nel form verrà usato per costruire un oggetto della classe definita qui sopra, subito dopo si puliranno tutti i campi e si farà una sintesi di quanto letto in una etichetta.
Al click di un ulteriore pulsante, poi, verrà mostrato il guadagno ed il capitale totale alla fine del periodo.

public class InterfacciaGrafica extends Application {
    TextField tNome = new TextField();
    TextField tCognome = new TextField();
    TextField tCodice = new TextField();
    TextField tImporto = new TextField();
    TextField tAnno = new TextField();
    TextField tInteressiPercentuale = new TextField();
    Label lNomeCognome = new Label("Nome e cognome");
    Label lData = new Label("Data vincolo");
    Label lGuadagnoETotale = new Label("Guadagno in €");
    RisparmioVincolato risparmio;

    public void start(Stage finestra) {
       Label lNome = new Label("Nome");
       Label lCognome = new Label("Cognome");
       Label lCodice = new Label("Codice");
       Label lImporto = new Label("Importo");
       Label lDataVincolo = new Label("Anno");
       Label lInteressiPercentuale = new Label("Interessi");
       Button bAcquisisci = new Button("Acquisisci");
       Button bMostra = new Button("Mostra guadagno");
       bAcquisisci.setOnAction(e->acquisisci());
       bMostra.setOnAction(e->mostra());
       GridPane pannello = new GridPane();
       pannello.add(lNome, 0, 0);
       pannello.add(tNome, 1, 0);
       pannello.add(lCognome, 0, 1);
       pannello.add(tCognome, 1, 1);
       pannello.add(lCodice, 0, 2);
       pannello.add(tCodice, 1, 2);
       pannello.add(lImporto, 0, 3);
       pannello.add(tImporto, 1, 3);
       pannello.add(lInteressiPercentuale, 0, 4);
       pannello.add(tInteressiPercentuale, 1, 4);
       pannello.add(lDataVincolo, 0, 5);
       pannello.add(tAnno, 1, 5);
       pannello.add(bAcquisisci, 0, 6);
       pannello.add(bMostra, 1, 6);
       pannello.add(lNomeCognome, 0, 7, 2, 1);
       pannello.add(lData, 0, 8, 2, 1);
       pannello.add(lGuadagnoETotale, 0, 9, 2, 1);
       Scene scena = new Scene(pannello,500,500);
       finestra.setScene(scena);
       finestra.setTitle("prospetto d'investimento");
       finestra.show();
    }

    public void acquisisci() {
       String sNome = tNome.getText();
       String sCognome = tCognome.getText();
       int iCodice = Integer.parseInt(tCodice.getText());
       double fImporto = Double.parseDouble(tImporto.getText());
       double fInteressi = Double.parseDouble(tInteressiPercentuale.getText());
       short iAnno = (short) Integer.parseInt(tAnno.getText());
       // La new la facciamo qui piuttosto che insieme agli altri oggetti di tipo 
       // TextField perché solo qui sappiamo quali valori usare nel costruttore
       risparmio = new RisparmioVincolato(sNome, sCognome, iCodice, fImporto, iAnno, fInteressi);
       // In questo momento tutte le informazioni lette sono memorizzate nelle proprietà dell'oggetto risparmio,
       // non sevono più i dati nel form che quindi viene pulito per un prossimo eventuale inserimento
       tNome.setText("");
       tCognome.setText("");
       tCodice.setText("");
       tImporto.setText("");
       tInteressiPercentuale.setText("");
       tAnno.setText("");
       lNomeCognome.setText("Vincolo di " + risparmio.nome + " " + risparmio.cognome);
       lData.setText("fatto l'anno " + risparmio.anno + "con interessi del " + risparmio.interessiPercentuale + "%");
    }

    public void mostra() {
       lGuadagnoETotale.setText("Guadagno previsto " + 
         risparmio.fornisciGuadagno() + "€; il capitale diventerà " + 
         risparmio.fornisciCapitale() + "€");
    }

    public static void main(String args[]) {
       launch();
    }
}

Nel metodo acquisisci() del programma sopra proposto, vengono prima lette tutte le informazioni del form, poste in delle variabili e poi queste vengono usate per invocare il costruttore opportuno nel quale vengono eseguiti dei controlli sulla correttezza dei dati. Più avanti, nelle ultime 2 righe del metodo, si accede in lettura alle proprietà poco sopra impostate.
Segue ora una alternativa del metodo acquisisci() che vuole raggiungere lo stesso obiettivo utilizzando però l'altro costruttore:

public class InterfacciaGrafica extends Application {
[...]

    public void acquisisci() {
       String sNome = tNome.getText();
       String sCognome = tCognome.getText();
       int iCodice = Integer.parseInt(tCodice.getText());
       double fImporto = Double.parseDouble(tImporto.getText());
       double fInteressi = Double.parseDouble(tInteressiPercentuale.getText());
       short iAnno = (short) Integer.parseInt(tAnno.getText());
       // Alternativa, utilizziamo il costruttore senza argomenti
       risparmio = new RisparmioVincolato();
       // A questo punto vanno impostati i valori delle proprietà
       risparmio.nome = sNome;
       risparmio.cognome = sCognome;
       risparmio.codice = iCodice;
       risparmio.importo = fImporto;
       risparmio.anno = iAnno;
       risparmio.interessiPercentuale = fInteressi;
       // E, come prima, si puliscono i campi del form
       tNome.setText("");
       tCognome.setText("");
       tCodice.setText("");
       tImporto.setText("");
       tInteressiPercentuale.setText("");
       tAnno.setText("");
       lNomeCognome.setText("Vincolo di " + risparmio.nome + " " + risparmio.cognome);
       lData.setText("fatto l'anno " + risparmio.anno + "con interessi del " + risparmio.interessiPercentuale + "%");
    }

[...]
}

La variante sopra proposta ha un difetto: lascia alla classe InterfacciaGrafica la possibilità di modificare direttamente le variabili interne alla classe. In questo modo dalla classe RisparmioVincolato non è possibile eseguire alcun controllo sulla validità dei valori che vengono loro assegnati. Se ad esempio un utente inserisse nella casella anno del form il valore 44, per il programma in esecuzione tutto sarebbe corretto, così come tutto funzionerebbe se il solito utente, stavolta nel campo importo, inserisse il valore -12.0. Chiaramente tutto ciò è concettualmente sbagliato.

Per prevenire tali situazioni è necessario un sistema per cui risulti impossibile accedere direttamente alle proprietà della classe RisparmioVincolato da qualsiasi altra classe, come nel caso di InterfacciaGrafica.

Se rendiamo private tutte le proprietà di RisparmioVincolato, come si può fare ad impostare il capitale o la percentuale di interessi al di là del costruttore?
La soluzione è fornire dei metodi che impostano, dopo aver effettuato controlli di correttezza, le variabili private: tali metodi vengono chiamati setters. Serviranno poi altri metodi che forniscono il valore di queste variabili private (ogni metodo ne fornisce una): sono i getters.

Facciamo l'esempio con la sola proprietà importo, chiaramente il lavoro fatto su questa proprietà va replicato per tutte le altre. La riga della dichiarazione della proprietà differisce per la sola aggiunta della parola chiave private mentre compaiono in più i metodi setImporto() e getImporto():

private double importo;

public void setImporto(double euro) {
   if(euro > 0) {
      importo = euro; //Anche se la proprietà è privata, il metodo della stessa classe accede a tutte le variabili
   } else {
      importo = 0;
}

public double getImporto() {
   return importo;
}

A questo punto il metodo acquisisci() sopra riportato non funzionerebbe più (provare per credere). La nuova versione sostituisce necessariamente l'accesso alle proprietà con l'accesso ai metodo setters e getters:

public class InterfacciaGrafica extends Application {
[...]

    public void acquisisci() {
       String sNome = tNome.getText();
       String sCognome = tCognome.getText();
       int iCodice = Integer.parseInt(tCodice.getText());
       double fImporto = Double.parseDouble(tImporto.getText());
       double fInteressi = Double.parseDouble(tInteressiPercentuale.getText());
       short iAnno = (short) Integer.parseInt(tAnno.getText());
       // Alternativa, utilizziamo il costruttore senza argomenti
       risparmio = new RisparmioVincolato();
       // A questo punto vanno impostati i valori tramite l'invocazione dei setters
       risparmio.setNome(sNome);
       risparmio.setCognome(sCognome);
       risparmio.setCodice(iCodice);
       risparmio.setImporto(fImporto);
       risparmio.setAnno(iAnno);
       risparmio.setInteressiPercentuale(fInteressi);
       // Come prima, si puliscono i campi del form
       tNome.setText("");
       tCognome.setText("");
       tCodice.setText("");
       tImporto.setText("");
       tInteressiPercentuale.setText("");
       tAnno.setText("");
       // A questo punto anche per la lettura dei dati non si può accedere alle proprietà
       // ma bisogna usare i metodi: i getters
       lNomeCognome.setText("Vincolo di " + risparmio.getNome() + " " + risparmio.getCognome());
       lData.setText("fatto l'anno " + risparmio.getAnno() + "con interessi del " + risparmio.getInteressiPercentuale() + "%");
    }

[...]
}

Eclipse può aiutarci nella generazione dei getters and setters: click col destro sulla classe nel Package Explorer o sullo sfondo nella finestra del sorgente quindi click sulla voce Generate getters and setters...
Nella finestra che si aprirà si scelgono i setters e getters per le singole proprietà o per tutte.

file it/esempi/Primo.java

 package it.esempi;

public class Primo {
    int x;
    boolean y;
    
    public Primo(int k, boolean p) {
        x = k;
        y = p;
    }
    
    public int leggiVal() {
        int p;
        if(y) {
            p = x;
        } else {
            p = -x;
        }
        return p;
    }
    
    public String descrivi(){
        return "n:"+x;
    }
    
    private boolean check(){
        return x<100;
    }
}

Cosa è Primo?

il nome della classe il nome del pacchetto il nome del pacchetto è it.esempio un nome di variabile no, segue la parola chiave class un nome di un metodo descrivi o check sono nomi di metodi

Quanti parametri ha il costruttore della classe precedente?

0c'è un costruttore dichiarato con più di zero parametri quindi in questo caso quello di default non viene inserito da java) 1 2 un int e un boolean non c'è il costruttore

Considera il seguente frammento di programma inserito in un'altra classe:
Primo o;
o = new Primo(3,false);
int q = o.leggiVal();

Quanto vale q?

3la condizione dell'if è falsa -3 il frammento è erratonon ci sono errori

Considera il seguente frammento di programma inserito in un'altra classe:
Primo o;
o = new Primo(3,false);
boolean q = o.check();

Quanto vale q?

true false il frammento è errato il metodo non è visibile

Considera il seguente frammento di programma inserito in un'altra classe:
Primo o;
o = new Primo(3,false);
String m = o.descrivi();

Quanto vale m?

non si sa 3 n:3 #3#

Una per tutti: le proprietà statiche

Riprendiamo l'esempio del risparmio vincolato. Abbiamo fatto i conti senza le tasse: sappiamo infatti che in tutti i tipi di risparmio lo Stato ci chiede una percentuale sull'intero capitale o sul guadagno. In questo esempio supponiamo che si debbano pagare le tasse sul capitale totale. Tali tasse saranno una percentuale o una quota fissa (ad esempio 5€) e in entrambi i casi tali tasse non sono diverse per i vari risparmi: il risparmio vincolato di Luca ha le stesse tasse del risparmio vincolato di Valentina e così via.
A cosa serve allora una proprietà che si deve impostare sempre allo stesso valore per ogni oggetto creato?

Sarebbe senz'altro più comodo dire: tutti gli oggetti creati di tipo RisparmioVincolato condividono il fatto che ci sono tasse forfettarie (cioè fisse) e per tutti tali tasse sono uguali. Non ha allora significato dire che il risparmio di Luca ha tasse per 5€ perché lascia sott'intendere che tutti gli altri risparmi possono avere valori diversi. Quello che vogliamo dire è che tutti gli oggetti della classe RisparmioVincolato hanno tasse per 5€.
Quindi, ipotizzando di avere la proprietà private double tasse; e relativi metodi public void setTasse(double cifra) e public double getTasse():

RisparmioVincolato risparmiDiLuca = new RisparmioVincolato("Luca", "Pecci", 123321, 5000.0f, 2022, 0.005f);
risparmioVincolato.setTasse(5.0f);

Lo consideriamo concettualmente sbagliato perché, come detto sopra, ogni oggetto ha una sua tassazione cosa che invece, secondo il nostro modello, non è vera: le tasse sono uguali per tutti gli importi e tutti i risparmiatori. Per rappresentare in Java questa situazione, andiamo a scrivere la proprietà aggiungendo una parola chiave: private static double tasse; e osserviamo che tale parola è static. La variabile per rappresentare una proprietà creata in questo modo è unica e condivisa da tutti gli oggetti della classe. È creata e unica sia nel caso in cui c'è un solo oggetto creato, sia che ce ne sono molti, sia che non ce n'è nessuno.
I metodi che intervengono sulle proprietà statiche potrebbero essere come quelli riportati qualche riga più su, ma noi vogliamo enfatizzare il fatto che tali metodi possono essere invocati indipendentemente da quale oggetto si stia lavorando, o anche semplicemente se esiste o meno un oggetto della classe RisparmioVincolato. Anche qui si inserisce la parola static:

private static double tasse;

    public static double getTasse() {
        return tasse;
    }

    public static void setTasse(double tasse) {
        this.tasse = tasse;
    }

Quando nel programma si vuole utilizzare uno di questi due metodi, si dovrà utilizzare la forma RisparmioVincolato.getTasse() oppure RisparmioVincolato.setTasse(5.0f) cioè si fa riferimento direttamente alla classe piuttosto che ad una sua istanza.

Un altro possibile utilizzo delle variabili statiche lo potremmo pensare sul numero del risparmio vincolato. Perché il numero del risparmio lo dovremmo considerare come un progressivo che parte da 1 (il primo risparmio che si registra) e ogni nuovo risparmio vincolato avrà come valore il numero intero successivo, quindi si aggiungerà 1 al precedente. Per far questo è necessario mantenere memoria di quale sia stato l'ultimo valore assegnato. Facilmente lo possiamo fare con la proprietà statica. In questo caso al costruttore non verrà dato il codice come argomento perché se lo calcolerà da solo e questo spesso è un bene perché evita la possibilità di duplicazione del codice:

versione finale di RisparmioVincolato.java

public class RisparmioVincolato {
   private String nome;
   private String cognome;
   private int codice;
   private double importo;
   private short anno;
   private double interessiPercentuale;
   private static int ultimoAssegnato = 0;
   private static double tasse;

   // Dato che devo controllare l'intervallo ammissibile dell'anno del vincolo in 2 punti
   // diversi, creo un metodo privato che verrà poi invocato dal costruttore e dal setter
   private boolean impostaDataCorretta(short anno) { 
      if(anno < 1970) {
         anno = 1970;
      } else if(anno >2020) {
         anno = 2025;
      }
      this.anno = anno;
   }

   public RisparmioVincolato() {
      nome = "<indefinito>";     //this può essere omesso perché non c'è ambiguità con variabili locali
      cognome = "<indefinito>";
      ultimoAssegnato++;
      codice = ultimoAssegnato;
      importo = 1.0;
      impostaDataCorretta(1970);
      interessiPercentuale = 0.0;
   }

   public RisparmioVincolato(String nome, String cognome, double importo, short anno, double interessiPercentuale) {
      this.nome = nome;
      this.cognome = cognome;
      ultimoAssegnato++;
      codice = ultimoAssegnato;
      this.importo = importo;
      this.interessiPercentuale = interessiPercentuale;
      impostaDataCorretta(anno);
   }

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

   public String getNome() {
      return nome;
   }

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

   public String getCognome() {
      return cognome;
   }

   public void setData(short anno) {
      impostaDataCorretta(anno);
   }

   public String getDescrizione() {
      String d = "Importo di " + importo + " vincolato da " + nome + " " + cognome;
      return d;
   }

   public double fornisciGuadagno() {
      double guadagno = importo * interessiPercentuale / 100 - tasse;
      return guadagno;
   }

   public void setImporto(double importo) {
      this.importo = importo;
   }

   public double getImporto() {
      return importo;
   }

   public double fornisciCapitale() {
      double guadagno = fornisciGuadagno();
      double capitale = guadagno + importo;
      return capitale;
   }

   public static double getTasse() {
      return tasse;
   }

   public static void setTasse(double tasse) {
      this.tasse = tasse;
   }

   public static int getUltimoCodiceUsato() {
      return ultimoAssegnato;
   }
}

Nella classe Interfaccia si può conoscere quale è stato l'ultimo codice assegnato invocando RisparmioVincolato.getUltimoCodiceUsato().

In definitiva esiste un modo per far sì che tutti gli oggetti della stessa classe condividano un valore: usare il modificatore static. Le variabili e i metodi così definiti possono essere usati senza creare una istanza della classe cioè senza creare un oggetto con new. Esempi di proprietà e metodi statici già disponibili in Java sono Math.PI o Math.sin() o anche Integer.parseInt().
Attenzione ad una cosa: i metodi static possono vedere soltanto le variabili static perché non facendo parte di una istanza di una classe non possono vedere le varaibili di istanza (quelle non static) le quali vengono create, appunto, col new.

file it/esempi/Esempio.java

Data la classe:

package it.esempio;

public class Esempio {
    public static n;
    public int w;
    String c;
    public Esempio(String u){
        c = u;
    }
    public String descrivi(){
        return n + c;
    }
}

Considera il seguente frammento di programma inserito in un'altra classe:
Esempio t1 = new Esempio("1");
Esempio t2 = new Esempio("2");
t1.n = 5;
t2.n = 7;

Quanto vale t1.n?

5 essendo n static t2.n=7 la sovrascrive 7

Considera il seguente frammento di programma inserito in un'altra classe:
Esempio t1 = new Esempio("1");
Esempio t2 = new Esempio("2");
t1.w = 5;
t2.w = 7;

Quanto vale t1.w?

5 essendo w una variabile di istanza (non static) t2.n=7 non la sovrascrive 7 essendo w una variabile di istanza (non static) t2.n=7 non la sovrascrive

Considera il seguente frammento di programma inserito in un'altra classe:
Esempio t1 = new Esempio("1");
Esempio t2 = new Esempio("2");
Esempio.n = 10;

Quanto vale t1.n?

il frammento è errato no, anzi, le variabili static di solito si usano facendo riferimento alla classe piuttosto che alle sue istanze non si sa 10 giusto, Esempio.n è il modo giusto per accedere ad una variabile static