11. La programmazione a oggetti

Andrea Pastore 19/03/2020 0

Finora abbiamo programmato in modo procedurale, ovvero le istruzioni che abbiamo scritto venivano eseguite una dopo l’altra. Questo sistema di programmazione non è molto efficiente, perché i codici diventano molto grandi e difficilmente manutenibili. La programmazione ad oggetti è nata per risolvere questo problema. Si basa su due principi fondamentali: le classi e gli oggetti. Una classe è un file che definisce un tipo di dato, nonché le operazioni che possono essere effettuate dagli oggetti di quella classe. Sarà possibile poi creare oggetti di quel tipo di dato che possono eseguire le operazioni stabilite.

 

Anatomia di una classe

Ogni classe è composta da tre tipi di elementi:

 

Variabili

le variabili di una classe si dichairano come le variabili che abbiamo sempre visto, ovvero specificando il tipo di dato (int, double, String e cosi via) ma in più dobbiamo specificare il tipo di accesso, ovvero public, private, protected. Questo è necessario per utilizzare una caratteristica della programmazione ad oggetti, l’incapsulamento, che vedremo più avanti.

 

Metodi

I metodi rappresentano le operazioni che possono essere eseguite dagli oggetti di quella classe. Possono avere parametri in input e possono essere usati per eseguire le operazioni più varie.

Anche per i metodi vale l’incapsulamento, per cui per ogni metodo che creiamo dovremmo specificare un indicatore di accesso tra public, private, protected. Subito dopo abbiamo un altro indicatore, che specifica il tipo di valore ritornato dal metodo. Void indica che non viene restituito alcun valore, int indica invece che viene restituito un intero, String indica che viene restituita una stringa e cosi via.

 

Costruttore

una casse di per se è solo un elenco di metodi e variabili, definisce il comportamento di un oggetto ma non viene eseguita direttamente, per utilizzarla dobbiamo creare un oggetto di quel tipo. Per questo ogni classe ha un metodo speciale chiamato costruttore, che serve a creare l’oggetto su cui noi possiamo poi usare i metodi definiti nella classe.

Il costruttore si differenzia dagli altri metodi perché è chiamato con lo stesso nome della classe e non ha specifica alcun tipo di dato. Come gli altri metodi può prendere in input dei parametri con i quali inizializzerà le variabili di classe. Il costruttore si invoca con la parola chiave new seguita dal nome della classe e i parametri che abbiamo stabilito nel costruttore.

 

Cosa succede quando invochiamo un costruttore?

Quando invochiamo un costruttore di una classe viene creato un oggetto che ha al suo interno le variabili definite da quella classe, che ci mette a disposizione i metodi definiti nella classe. Per ogni classe possiamo creare tutti gli oggetti che vogliamo.

 

Caratteristiche della programmazione ad oggetti

Incapsulamento

Nelle classi abbiamo la possibilità di scegliere, oltre al tipo di variabile, anche il livello di accesso, tra questi tre valori:

  • public
    se dichiariamo una variabile public questa sarà accessibile anche all’esterno della classe, qualunque parte di un programma potrà leggere quei valori.
  • private
    Se invece la dichiariamo private questi valori potranno essere letti solo all’interno della classe.   
  • protected
    è una via di mezzo: consente la lettura di questa variabile alle classi che estendono la classe che stiamo scrivendo. Vedi il paragrafo successivo sull’ereditarietà.

la regola è quella di dichiarare le variabili private e poi utilizzare dei metodi per restituire o modificare dei valori

Facciamo un esempio, supponiamo di voler creare la classe ContoBancario che gestisce i conti correnti di una banca. Questa classe avrà questi parametri:

  • numeroConto
  • nome
  • cognome
  • saldo

Una possibile implementazione potrebbe essere questa:

public class ContoBancario {

public int numeroConto;

public String nome;

public String cognome;

public double saldo;



public ContoBancario(int numeroConto, String nome, String cognome, double saldo) {

this.numeroConto = numeroConto;

this.nome = nome;

this.cognome = cognome;

this.saldo = saldo;



}




… altri metodi

}


le variabili di classe sono state dichiarate public, quindi tutti hanno accesso al loro valore, anche i codici esterni. Questo però può creare dei problemi, perché potrebbe esserci un codice del genere:

ContoBancario contoMario = new ContoBancario(123456, “Mario”, “Rossi”, 10);

contoMario.saldo = 100000;

con la seconda istruzione un codice esterno può modificare direttamente il saldo del conto, perché la variabile è stata dichiarata public e tutti ne hanno accesso. Facciamo una modifica alla nostra classe, trasformiamo tutte le variabili da public a private:

public class ContoBancario {

private int numeroConto;

private String nome;

private String cognome;

private double saldo;


public ContoBancario(int numeroConto, String nome, String cognome, double saldo) {

this.numeroConto = numeroConto;

this.nome = nome;

this.cognome = cognome;

this.saldo = saldo;

}

// getter & setter

public void setNome(String nome) {

this.nome = nome;

}

public String getNome() {

return this.nome;

}

public void setCognome(String cognome) {

this.cognome = cognome;

}

public String getCognome() {

return this.cognome;

}

public void setSaldo(int ammontare) {

this.saldo = ammontare;

}

public int getSaldo() {

return this.saldo;

}


… altri metodi

}

 

Ora questa istruzione:

contoMario.saldo = 100000;

non potrà più essere eseguita, perché le variabili private di una classe sono accessibili solo ai metodi della classe e possono essere  modificate solo da loro. Per effettuare una modifica del saldo di questa classe bisognerà usare un metodo della classe, che abbiamo chiamato setSaldo(), che potrà avere dei sistemi di controllo che impediranno di scrivere cifre a caso. La stessa cosa vale per recuperare il valore del saldo, che può essere recuperato solo usando il metodo getSaldo(). Questi due metodi sono una tipologia di metodi molto comune: i metodi getter e setter, che hanno proprio il compito di far recuperare i valori dall’esterno della classe e di permettere la modifica delle variabili di classe. Notiamo che questi metodi devono essere dichiarati public, in modo che possono essere invocati dall’esterno.

 

Importare una classe

Quando dobbiamo usare una classe che non si trova nello stesso package dobbiamo importarla. Per farlo si scrive la parola chiave import più il nome del package seguito dal nome della classe. Ad esempio supponiamo di voler importare una classe Utente da un package chiamato model. Per utilizzarla dovremmo scrivere in alto, prima di ogni altro codice, il codice per importare:

import model.Utente;

se ci sono più classi da importare i vari comandi di import vanno scritte uno sotto l’altro.

Potrebbero interessarti anche...

Andrea Pastore 19/03/2020

12. Polimorfismo in Java

Alcuni linguaggi di programmazione consentono di decidere il comportamento dei metodi e costruttori in base al numero di variabili che vengono passate loro come parametro.  Supponiamo ad esempio di avere una classe Java chiamata Utente, con tre variabili (id, nome, cognome) e due costruttori: il primo prenderà in input solo una variabile id, il secondo prenderà in input una variabile id, una variabile nome e una variabile cognome.

I due costruttori in java saranno i seguenti

public Utente(int id) {

this.id = id;

this.nome = “”;

this.cognome = “”;

this.email = “”;

}

public Utente(int id, String nome, String cognome) {

this.id = id;

this.nome = nome;

this.cognome = cognome;

}

Quando noi invochiamo il costruttore a seconda del numero di parametri che inseriamo Java userà il costruttore che combacia con i parametri inseriti ed eseguirà il codice contenuto all’interno. Java darà un errore solo se non trova un costruttore con il numeor di parametri da noi specificato. Ad esempio se scriviamo:

	Utente utente =  new Utente(3,”Mario”,”Rossi”,”mario@gmail.com”);

Otterremo un errore, perché non abbiamo definito un costruttore che prende in input quattro parametri. Tutto quello che abbiamo detto per i costruttori è valido anche per i metodi (del resto il costruttore non è altro che un metodo speciale). Prendiamo in esame il seguente metodo che stampa tutti i dati dell’utente:

public void stampaDati() {

System.out.println(this.id + “ ” + this.nome + “ ” + this.cognome + “ ” +this.email);

}

Come per i costruttori possiamo creare un altro metodo che ha lo stesso nome, ma prende in input un parametro per stabilire come devono essere stampati questi elementi. Stabiliamo due tipologie di stampa: essenziale che stampa solo nome e cognome, completa che stampa tutto. Se il parametro passato in input non corrisponde a nessuna di queste due cose allora questo metodo invoca a sua volta il metodo stampaDati senza parametri, che stamperà i dati senza formattazione.

	public void stampaDati(String tipoVisualizzazione) {

if(tipoVisualizzazione.equals("essenziale")) {

System.out.println("Nome:"+this.nome+"; Cognome:"+this.cognome);

}

else if (tipoVisualizzazione.equals("completa")){

System.out.println("Id: "+this.id + "; Nome:"+this.nome+"; Cognome:"+this.cognome+"; Email:"+this.email);

}

else {

stampaDati();

}

}

Ereditarietà

L’ereditarietà è una caratteristica di molti linguaggi di programmazione moderni che consente di ottimizzare il codice scritto ed evitare ripetizioni. Ci sono casi in cui dobbiamo creare classi molto simili tra loro. Per questo molti linguaggi di programmazione consentono di usare il meccanismo dell’ereditarietà. Supponiamo di avere bisogno di questa due classi:

  • Utente
    id, nome, cognome, email,telefono
  • Fornitore
    id, nome, cognome, email,telefono,partitaIva

notiamo che Fornitore è quasi uguale alla classe Utente, quindi non ha senso riscriverla. Quando creiamo la classe Fornitore che estenderà la classe Utente. Scriviamo le due classi per renderci conto del codice e delle differenze tra le due. Qui sotto vediamo la classe utente:

public class Utente {

private int id ;

private String nome;

private String cognome;

private String email;

private String telefono;



public Utente(int id, String nome, String cognome, String email, String telefono) {

this.id = id;

this.nome = nome;

this.cognome = cognome;

this.email = email;

this.telefono = telefono;

}

// Getter & setter

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

… tutti gli altri getter e setter

}

Vediamo ora la classe Fornitore. Come abbiamo capito Java ci consente di evitare di ricopiare la classe, basta dichiarare, al momento della creazione, che la nuova classe estende la classe Utente. Questo avviene con la parola chiave extends.

public class Fornitore extends Utente {

private String partitaIva;

public Fornitore(String partitaIva, int id, String nome, String cognome, String email, String telefono) {

super(id, nome, cognome, email, telefono, via, citta, cap);

this.partitaIva = partitaIva;

}

public String getPartitaIva() {

return partitaIva;

}

public void setPartitaIva(String partitaIva) {

this.partitaIva = partitaIva;

}

}


Notiamo alcune differenze con la classe Utente scritta sopra: innanzitutto vediamo che nel costruttore c’è la parola chiave super, seguita da una serie di parametri. Si tratta dei parametri del costruttore della classe Utente, che viene richiamato con il comando super. Ogni volta che noi estendiamo una classe il costruttore della nuova classe deve richiamare il costruttore della classe che estende. Fornitore estende la classe Utente, quindi il suo costruttore prende in input anche i parametri che richiede il costruttore di Utente, che invocherà con il metodo super().

Nota: quando una classe estende un’altra classe, la classe estesa è definita superclasse

 

Le interfacce in Java

Vediamo ora un altro concetto fondamentale della programmazione: le interfacce, che  servono per standardizzare il codice.

 

Interfacce

Le interfacce vengono utilizzate per definire un tipo di dato e una classe, che però non viene implementato. Vengono definite solo le firme dei metodi che poi saranno implementate in altre classi. Vediamo un esempio di interfaccia:

public interface Libro {

public void stampaLibro();

public void setTitolo(String titolo);

public String getTitolo();

public void setNumeroPagine(int numeroPagine);

public int getNumeroPagine();

}

Per usare una determinata interfaccia dopo il nome della classe dobbiamo usare la parola chiave implements seguita dal nome dell'interfaccia. Appena facciamo questo il nostro ide ci segnalerà che i metodi dell'interfaccia non sono ancora implementati, clicchiamo su add unimplementated methods (potrebbe cambiare a seconda dell’ide usato) e il nostro ide li aggiungerà per noi, dobbiamo solo completarli con il codice all'interno.

In sostanza le interfacce consentono di standardizzare il codice: dueprogrammatori si accordano sui metodi e su come vengono chiamati, e poiai fini dell'interoperabilità delle classi non è importante come vieneimplementato. Nel tempo le implementazioni possono cambiare, ma rispettandole interfacce non ci saranno problemi di incompatibilità

 

Il paradigma MVC

La programmazione ad oggetti è utilizzata nel paradigma MVC (MODEL, VIEW, CONTROLLER), che consente una separazione delle logiche. Vediamo i tre elementi nel dettaglio:

  • model
    sono le classi cosi come le abbiamo viste. Quando creiamo una classe infatti definiamo degli oggetti ma non li usiamo in quell’istante
  • view
    le viste rappresentano la parte del progetto che mostra i dati visivamente
  • controller
    sono i file che consentono di rendere permanenti le operazioni sugli oggetti: creazione di nuovi elementi, modifica di elementi esistenti, eliminazione di elementi e anche recupero dei dati di un elemento. I controller nascondono le logiche sottostanti alle altre classi: potremmo voler salvare i dati in un file oppure nel database, per le classi esterne viene chiamato lo stesso metodo. Facendo passare tutte le operazioni di salvataggio, modifica, caricamento per il controller avremo solo una classe che gestisce queste operazioni, che si traduce in un vantaggio per la manutenibilità del codice, in quanto per apportare modifiche alla gestione degli oggetti dovremmo modificare solo la classe relativa.
Leggi tutto

Andrea Pastore 19/03/2020

5. Il costrutto if

Il costrutto if serve per far prendere decisioni ai nostri programmi. Inizia con la parola chiave if seguita dalle parentesi tonde. Se la condizione è verificata viene eseguito il codice contenuto all’interno delle parentesi graffe. Possiamo dare a Java anche istruzioni su cosa fare nel caso in cui la condizione non sia verificata, scrivendo dopo l’if un else.

		if(i<5) {

Eseguo un certo codice

}

else {

Eseguo un altro codice

}

La condizione che abbiamo posto è che i sia minore di 5, il simbolo < è definito operatore. Più avanti vedremo molti altri operatori utilizzabili.

La parola chiave else non è obbligatoria, possiamo scegliere di eseguire un codice solo se una condizione è verificata e non fare nulla in caso contrario. Vediamo alcuni esempi, supponiamo di avere una variabile int i = 5:

		if(i<5) {

System.out.println("i minore di 5");

}

Questo primo codice controlla che i sia minore di 5, se questa condizione è vera allora viene eseguito il codice al suo interno (in questo caso System.out.println("i minore di 5"); ). Noi abbiamo supposto che i abbia valore 5, quindi in questo caso Java rileverebbe che la condizione non è vera e non stamperebbe nulla, terminando il programma senza dare informazioni. Vediamo il codice sottostante:

		if(i<5) {

System.out.println("i minore di 5");

}

else {

System.out.println("i non è minore di 5");

}

in questo codice oltre all’if c’è un else: quindi Java rileverà ugualmente che la condizione nell’if non è vera, ed eseguirà il codice presente nell’else, in questo caso la stampa del messaggio “i non è minore di 5”.

 

Le diverse condizioni possibili nell’if

All’interno dell’if possiamo porre diverse condizioni: ad esempio possiamo chiederci se un numero è maggiore, minore, uguale oppure diverso da un altro numero. Per i primi due casi si usano gli operatori maggiore e minore, mentre per verificare l’uguaglianza si usa l’operatore ==. Abbiamo bisogno di due uguali perché abbiamo visto che un singolo uguale serve per inizializzare una variabile (l’assegnamento).

		if(i==5) {

System.out.println("i uguale a 5");

}

In questo caso Java eseguirà il codice all’interno dell’if se e solo se i è uguale a 5.

Per verificare che un numero sia diverso da un altro si usa l’operatore !=

		if(i!=5) {

System.out.println("i diverso da 5");

}

In questo caso Java eseguirà il codice all’interno dell’if se e solo se i è diverso da 5. Se dobbiamo verificare che un numero sia minore oppure uguale ad un altro, oppure maggiore o uguale ad un altro, possiamo usare l’operatore <= oppure >=. Ad esempio in questo caso:

		if(i<=5) {

System.out.println("i è minore o uguale a 5");

}

Java eseguirà il codice all’interno dell’if solo se i è minore o uguale di 5 (quindi a differenza del semplice simbolo < i può valere anche 5 e la condizione è sempre rispettata)

 

Condizioni più elaborate con gli operatori AND e OR

Finora abbiamo visto che la condizione all’interno dell’if verificano il valore di una sola variabile, non è sempre cosi. Possiamo trovarci a dover verificare più variabili con lo stesso if, ad esempio potremmo dover verificare che un determinato utente sia maggiorenne e si chiami Mario.

Potremmo avere ancora un altro caso: potremmo dover verificare che solo una delle due condizioni sia vera, per rifarci all’esempio di prima potremmo dover verificare che l’utente si chiami Mario oppure che sia maggiorenne.

È possibile fare queste due richieste con gli operatori AND e OR, rappresentati in Java dai simboli && per l’AND e || per l’OR. L’operatore AND restituisce true quando entrambe le condizioni sono vere, l’operatore OR restituisce true se almeno una delle due condizioni è vera.

Facciamo un esempio con il primo caso, ovvero scriviamo il codice per verificare che un utente sia maggiorenne e si chiami e si chiami Mario. Per verificare questa condizione dobbiamo usare l’operatore AND. Assumiamo di avere una variabile età e una variabile nome, il codice sarà:

	if(età>=18 && nome.equals(“Mario”)) {

eseguo il codice

}

Vediamo ora l’altra possibilità, ovvero quando ci accontentiamo che solo una delle due condizioni sia vera. In questo caso dobbiamo usare l’operatore OR:

	if(età>=18 || nome.equals(“Mario”)) {

eseguo il codice

}

Nel caso in cui volessimo ignorare eventuali differenze tra maiuscole e minuscole nella variabile nome Java mette a disposizione il metodo equalsIgnoreCase:

	if(età>=18 && nome.equalsIgnoreCase (“Mario”)) {

eseguo il codice

}

oppure nel caso in cui dobbiamo verificare che solo una delle due condizioni sia vera

	if(età>=18 || nome.equalsIgnoreCase (“Mario”)) {

eseguo il codice

}
Leggi tutto

Andrea Pastore 19/03/2020

17. L’operatore Lambda

L’operatore lambda è stato introdotto in Java 8 ed è rappresentato dal simbolo -> e  consente di velocizzare alcune operazioni. Supponiamo di avere il seguente codice:

		ArrayList<String> stringhe = new ArrayList<>();

stringhe.add("Andrea");

stringhe.add("Antonio");

stringhe.add("Alessandro");

stringhe.add("Alfonso");

supponiamo ora di voler stampare tutti i nomi presenti in questo ArrayList: se volessimo usare un metodo tradizionale potremmo scrivere

		for(int i = 0; i<4; i++) {

System.out.println(stringhe.get(i));

}

vediamo ora come fare la stessa cosa con l’operatore lambda:

		stringhe.forEach((String s) -> System.out.println(s));

come vediamo è molto più sintetico: sul nostro array invochiamo il metodo forEach che applica l’espressione lambda a tutto l’array.

Con gli operatori lambda possiamo anche applicare dei filtri. Ad esempio supponiamo di voler stampare solo le stringhe che hanno valore Andrea. In modo tradizionale sarebbe:

		for(int i = 0; i<4; i++) {

if(stringhe.equals("Andrea")) {

System.out.println(stringhe.get(i));

}

}

vediamo invece con l'operatore lambda:

		stringhe.stream().filter(s->s.equals("Andrea")).forEach((s) -> 				System.out.println(s));

Abbiamo applicato il metodo stream per prendere tutti gli oggetti e poi il metodo filter per inserire il filtro che ci interessava, infine abbiamo usato di nuovo il metodo forEach per stampare.

Leggi tutto

Cerca...