Scarica - Ripetizioni Materie Scientifiche

2015
Ripetizioni
Materie
Scientifiche
Prof. Ing. Per. Ind.
Alessandro Bianco
[RIPETIZIONI MATERIE
SCIENTIFICHE]
Dispensa introduttiva al linguaggio Java
Ripetizioni Materie Scientifiche
Indice
Dalla programmazione strutturale a OOP……………………………………………………..pag. 2
Cos’è il JDK……………………………………………………………………………………pag. 7
Il primo programma in Java……………………………………………………………………pag. 12
IDE e strumenti di sviluppo avanzati…………………………………………………………..pag. 17
Variabili e dichiarazioni……………………………………………………………………….pag. 25
Tipi primitivi di Java e valori………………………………………………………………….pag. 30
Classi wrapper…………………………………………………………………………………pag. 34
Boxing, unboxing e autoboxing……………………………………………………………….pag. 36
Operatori e casting……………………………………………………………………………..pag. 39
If e switch: costrutti condizionali in Java……………………………………………………...pag. 44
Ciclo for e while, costrutti iterativi in Java……………………………………………………pag. 49
Break e Continue………………………………………………………………………………pag. 53
I Metodi in Java………………………………………………………………………………..pag. 55
Overload di metodi e variable arguments……………………………………………………...pag. 60
Metodi statici (static) e metodi di istanza……………………………………………………...pag. 63
Array in Java…………………………………………………………………………………...pag. 66
Stringhe in Java………………………………………………………………………………...pag. 70
Enum, gestire le enumerazioni………………………………………………………………...pag. 74
Principi di OOP………………………………………………………………………………...pag. 81
Classi, oggetti e costruttori…………………………………………………………………….pag. 87
Ereditarietà in Java…………………………………………………………………………….pag. 92
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 1
Ripetizioni Materie Scientifiche
Dalla programmazione strutturale a OOP
Immaginiamo di dover scrivere un programma per gestire conti bancari dovremo provvedere, per
ciascuno dei conti gestiti, a prenderci carico di rappresentare lo stato (un nome, owner ed un
importo, amount, per semplicità) e scrivere alcune procedure per modificarlo (funzioni, subroutine),
oltre ad una probabilmente per crearlo. Pensiamo ad operazioni come versamento e prelievo alle
quali passare ogni volta tra gli argomenti la struttura conto su cui operare.
Con l’approccio OO nel medesimo programma potremo invece pensare di avere un tipo di oggetto,
ad esempio chiamato Conto che contenga insieme:


lo stato (che chiameremo fields, i campi dell’oggetto);
le procedure per la sua gestione (che chiameremo methods, metodi).
In questo modo possiamo modellare nella medesima sezione del codice (che chiameremo classe),
sia lo stato che il “behavior”, il comportamento delle entità che utilizziamo.
L’integrazione dello stato e del behavior è comunemente chiamata encapsulation (incapsulamento)
ed è la base di quella caratteristica della programmazione OO che viene chiamata information
hiding che consiste nel mantenere interno a precise sezioni del codice l’accesso allo stato delle
entità utilizzate.
È interessante osservare, che OOP è una metodologia ed uno stile di progettazione del software che,
tranne in casi particolari, può essere impiegato indipendentemente dal linguaggio ma che risulta
estremamente più facile da adottare se il linguaggio (come Java, C++ e altri) ne fornisce le primitive
(come le classi in Java).
Classi e oggetti in Java
Nell’approccio OO in Java non solo si definiscono gli oggetti su cui si intende lavorare ma li si
organizzano in categorie: una classe è esattamente la definizione delle proprietà e dei metodi che
avrà ogni elemento della categoria.
La distinzione tra oggetto e classe è enfatizzata dalle seguenti definizioni:
Classe è una collezione di uno o più oggetti contenenti un insieme uniforme di attributi e servizi,
insieme ad una descrizione circa come creare nuovi elementi della classe stessa (Edward Yourdan);
Un oggetto è dotato di stato, behavior ed identità; la struttura ed il comportamento di oggetti simili
sono definiti nelle loro classi comuni; i termini istanza ed oggetto sono intercambiabili (Grady
Booch).
Definire una classe in Java:
Una classe in java si definisce con la keyword class:
/**
* Classe per rappresentare un Conto
*/
public class Conto {
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 2
Ripetizioni Materie Scientifiche
private double amount;
private String owner;
// costruttore
public Conto(String owner, double initialAmount) {
this.owner = owner;
this.amount = initialAmount;
}
public void versamento(double qty) {
amount += qty;
}
public boolean prelievo(double qty) {
if(amount < qty)
return false;
amount -= qty;
return true;
}
/* Getters */
public double getAmount() {
return amount;
}
public String getOwner() {
return owner;
}
}
dove si può osservare che:




il corpo (dichiarazioni di variabili e metodi) della classe è racchiuso tra parentesi graffe;
le linee sono terminate da punto-e-virgola (;);
le linee che iniziano per doppio slash ( //) sono commenti così come tutto quello che è
racchiuso tra ‘/*‘ e ‘*/‘;
il doppio asterisco all’inizio di un commento (/**) ha un significato speciale in relazione al
comando javadoc che sarà introdotto in seguito e serve per la generazione di
documentazione a partire dai commenti nel codice.
Qualificatori di visibilità
I qualificatori di fronte alle dichiarazioni di tipi o metodi ( public e private) servono per
determinare la visibilità degli elementi cui sono applicati:
Qualificatore Descrizione
Significa che sarà visibile (accessibile se si tratta di un field, chiamabile se si tratta di
private
un metodo) solo all’interno della classe che lo contiene.
significa invece che sarà accessibile anche dall’esterno della classe stessa.
public
Esistono altri 2 possibilità circa la visibilità:
Qualificatore Descrizione
accessibile solo dalle classi derivate, ne capiremo il significato in seguito
protected
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 3
Ripetizioni Materie Scientifiche
default
Non è una keyword: si ha la visibilità di default se nessuna delle precedenti viene
specificata e implica visibilità per tutte le classi che si trovano all’interno del
medesimo package (qualche dettaglio più avanti).
Costruttore
Di particolare rilevanza è il metodo contrassegnato con il commento // costruttore: è diverso
dagli altri metodi (versamento e prelievo) in quanto non dichiara un valore di ritorno e serve
(come richiesto dalla definizione di classe di Yourdan) per creare nuove istanze di elementi di
quella classe. Il costruttore delle classi deve essere utilizzato con la keyword new, la scrittura:
Conto myConto = new Conto("Francesca", 1.0e8);
Significa che vogliamo costruire (allocare in memoria) una nuova istanza di un oggetto della
categoria Conto ed inizializzarlo con owner “Francesca” e amount 1 miliardo.
Qualora non avessimo specificato nessun costruttore nella definizione della classe ne sarebbe stato
creato uno di default senza argomenti. Si possono aggiungere quanti costruttori si vogliono a patto
che abbiano tutti diversa signature (firma, ovvero tipo e numero degli argomenti). I costruttori
devono avere nome uguale al nome della classe.
this
All’interno del costruttore si può osservare anche l’uso della keyword this che in ciascuna classe
rappresenta sempre l’istanza corrente.
Si osservi come nel costruttore si usi this nella prima riga per disambiguare la variabile owner:
senza this l’assegnazione non avrebbe effetti (ed il compilatore la segnalarebbe anche con uno
warning) in quanto significherebbe che si assegna il contenuto della variabile owner ad owner
stessa, mentre con la qualificazione della variabile significa che stiamo assegnando il contenuto
della variabile owner, che è stata passata come argomento al metodo, al field owner della classe
Conto (o meglio dell’istanza corrente dell’elemento della classe Conto che stiamo costruendo).
Notazione ‘.’
Una volta costruito l’oggetto conto sarà possibile utilizzare i suoi metodi con la notazione:
conto.versamento(10);
conto.prelievo(100);
L’accesso ai field sarebbe possibile con la medesima notation (e.g. conto.amount = 5) ma è
proibito per scelta della nostra classe che definisce i field come private e quindi il compilatore ce
lo segnalerebbe come errore.
Ereditarietà: estendere una classe
L’utilità della strutturazione OO diventa palese, a mio parere, se si pensa a questo punto che
potrebbe nascere la necessità di estendere l’immaginario programma per la gestione di conti con
una banalizzazione di un conto in cui alcune somme vengano vincolate (e.g. nel tempo: sono nel
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 4
Ripetizioni Materie Scientifiche
conto ma disponibili solo dopo una certa data); in tal caso potremmo “estendere” direttamente la
classe Conto:
public class ContoVincolato extends Conto {
private double importoVincolato;
private Date fineVincolo;
public ContoVincolato(String
super(o,a);
importoVincolato=0;
}
o, double a) {
public boolean vincola(double qty, Date fineVincolo) {
if(importoVincolato != 0 || getAmount() < qty)
return false;
else {
prelievo(qty);
importoVincolato = qty;
this.fineVincolo = fineVincolo;
return true;
}
}
/**
* svincola l'importo vincolato se è passata la data di vincolo
*/
public boolean svincola() {
if(importoVincolato > 0) {
Date adesso = new Date();
if( adesso.after(fineVincolo) ) {
this.versamento(importoVincolato);
this.importoVincolato = 0;
return true;
}
}
return false;
}
//overloaded getAmount
public double getAmount(Date quando) {
if(importoVincolato == 0 || quando.before(fineVincolo))
return getAmount();
return getAmount() + importoVincolato;
}
public double getAmount() {
svincola();
return super.getAmount();
}
}
Questo codice serve a mostrare come estendere (cioè aggiungere funzionalità) alla classe Conto
senza doverne riscrivere il codice. In questo caso si è aggiunta la funzionalità di vincolare un
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 5
Ripetizioni Materie Scientifiche
importo fino ad una certa data e inserito un metodo che renderà di nuovo disponibile l’importo
vincolato se chiamato dopo la data di scadenza del vincolo.
In casi come questi si dice che la classe ContoVincolato deriva da Conto da cui eredita i field
conto ed owner ed i metodi versamento e prelievo (che infatti sono usati all’interno di
ContoVincolato).
La keyword super usata nel costruttore di ContoVincolato serve per riferirsi alla classe padre i.e.
quella da cui si deriva. In casi come questo nei quali la classe padre ha un costruttore con parametri
la classe figlia (derivata) ha l’obbligo di chiamare esplicitamente il costruttore del padre che
invece è implicitamente chiamato nel caso che sia presente un costruttore vuoto (senza argomenti).
Il metodo getAmount(Date arg) è invece un esempio di metodo ‘overloaded‘ cioè un metodo
definito più volte (in questo caso una volta nelle classe padre ed una volta nella classe derivata, ma
non è un obbligo), ma con argomenti diversi; il compilatore capirà quale chiamare sulla base degli
argomenti che gli vengono passati.
Infine il metodo getAmount() è un esempio di ridefinizione di un metodo da parte della classe
derivata; quando chiamato su una istanza di un oggetto di tipo ContoVincolato il metodo
getAmount non eseguirà quello definito in Conto ma prima chiamerà la funzione svincola e solo
dopo chiamerà la getAmount di Conto; si noti l’uso della keyword super in questo caso necessaria
per evitare un loop infinito di chiamate ricorsive.
Package
Un ultimo concetto interessante in Java per quanto riguarda le classi è quello dei package
Ogni classe appartiene ad un package (dichiarato all’inizio del file in cui è definita una classe con la
keyword package appunto) e serve a definire a qualche gruppo di classi essa appartiene.
Un package è del tutto simile ad un ‘path’ in un filesystem solo che al posto degli slash ( /) le
componenti sono separate da punti (.). Nelle prossime lezioni ci sarà occasione di approfondire
questo ed altri concetti legati alla struttura delle classi in Java ed alla programmazione OO in
generale.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 6
Ripetizioni Materie Scientifiche
Cos’è il JDK
Preparare il proprio computer per sviluppare con Java è davvero un gioco da ragazzi in quanto
scaricando un unico file abbiamo subito tutti i programmi che ci servono per iniziare.
Gli strumenti principali che il Java Development Kit (JDK) ci mette a disposizione sono:
composto dalla macchina virtuale propriamente detta (JVM), le librerie standard (che compongono
la cosidetta Java core API) ed il comando java che serve per far partire la JVM ed eseguire i
programmi.
Strumento
Descrizione
È il compilatore, il cui compito è quello di trasformare il codice sorgente
Java nel bytecode che sarà poi eseguito dalla macchine virtuale java
javac
(JVM)
JRE (Java Runtime
L’ambiente di runtime
Environment)
Oltre a questi contiene molti tool per lo sviluppo, di seguito introduciamo solamente i più
comunemente usati e ci riserviamo di parlare degli altri via via che serviranno:
Strumento
Descrizione
Java Archiver, che utilizzeremo a partire dalla prossima sezione e serve a realizzare
jar
archivi di classi.
Utility che serve per generare la documentazione (in HTML) di codice java a partire
da commenti inseriti nei sorgenti stessi. La documentazione generata da javadoc è
javadoc
quella che quasi ogni progetto Java mette a disposizione e ne è un esempio quella
ufficiale della java api.
Java Class File Disassembler, una tool per invertire il processo di compilazione, cioè
uno strumento che dato un file che contiene la versione compilata (il bytecode) di una
javap
classe java recupera i nomi ed i tipi dei field ed i metodi della stessa.
Tool utilizzata per permettere l’utilizzo di codice scritto in C (detto nativo) da java.
javah
appletviewer Viewer per applet che consente di eseguirle senza l’ausilio di uno web browser.
Java debugger.
jdb
Oracle, che dal 2010 (anno in cui ha acquistato Sun) è proprietaria del marchio Java, supporta il
Java Development Kit su molteplici architetture e sistemi operativi: tutte le versioni di Windows da
Vista a 8, le versioni di Windows Server a partire dalla 2008; Mac OS X Mountain Lion e
Mavericks; Linux Oracle, RedHat, Suse, Ubuntu oltre a Solaris e Ubuntu su processori ARM.
Se utilizzate uno di questi sistemi operativi vi basterà aprire con il vostro browser preferito l’URL:
http://www.oracle.com/technetwork/java/javase/downloads/index.html
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 7
Ripetizioni Materie Scientifiche
e premere il pulsante “DOWNLOAD” per scaricare la release corrente ( Java Platform (JDK) 8, al
momento in cui viene scritta la guida).
Sarete automaticamente indirizzati alla pagina dei download e dovrete a questo punto accettare i
termini della licenza prima di poter procedere al download.
Nota: Se state usando Linux o BSD e i termini della licenza Oracle non vi convincono potete
provare ad usare OpenJDK
Su Windows si dovrà effettuare il download di uno dei 2 file eseguibili jdk-8-windows-i586.exe
e jdk-8-windows-x64.exe (entrambi di oltre 150 MB) a seconda che si usi un sistema a 32 o a 64
bit mentre su Linux si puo’ scegliere tra gli archivi tar compressi installabili su tutte le distribuzioni
e gli rpm che invece sono indicati per Fedora, SUSE etc.
La procedura di installazione è totalmente automatica e sarà sufficiente accettare i default del
sistema di installazione. Ad esempio l’installazione su Windows prevede pochi semplici passi: la
selezione delle componenti da installare e la scelta della path del JRE.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 8
Ripetizioni Materie Scientifiche
Dopo qualche secondo di attesa avrete installato tutti i tool necessari per sviluppare in Java.
Il wizard, una volta completata l’istallazione ci suggerisce di visitare il sito della piattaforma Java,
standard edition dove potrete trovare molti documenti e tutorial. Meglio tenere questo indirizzo tra i
preferiti per avere un riferimento in futuro.
Impostare le variabili d’ambiente (Windows / Linux )
Il compilatore (javac) per default non è nella path e quindi per ‘provarlo’ dovrete andare a cercarlo
nella direcroty di installazione, ad esempio:
C:\Program Files\Java\jdk1.8.0\bin\
Po poterlo utilizzare senza doverci preoccupare di includere tutto il percorso possiamo includerlo
nelle variabili di ambiente del sistema (PATH). Vediamo come fare nei casi più comuni.
Impostare PATH su Windows
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 9
Ripetizioni Materie Scientifiche
Per le ultime versioni di Windows i passaggi sono piuttosto simili. Si parte dal pannello di controllo
(quello desktop nel caso di Windows 8) e poi cerchiamo l’icona Sistema. Nella maschera che appare
clicchiamo su Impostazioni di sistema avanzate (sulla sinistra). Infine nella nuova maschera
troviamo il pulsante “Variabili d’ambiente”. Che ci permetterà di accedere al pannello di modifica
delle variabili di sistema.
Qui non rimane che cercare la variabile Path e aggiungere il percorso relativo allaa nostra
installazione (copiamolo tutto fino a [...]bin\).
Impostare PATH su Ubuntu
Su Ubuntu il procedimento è ancora più semplice (e vale anche per altre distribuzioni che utilizzano
bash o derivati). È sufficiente modificare il file .bashrc e aggiungere la riga:
export PATH=$PATH:{{percorso di Java}}
ad esempio:
export PATH=$PATH:/opt/jdk1.8.0/bin
Verificare l’installazione
Per verificare che l’istallazione abbia avuto successo potete provare ad eseguire la macchina
virtuale, ad esempio aprendo l’interprete dei comandi (o il terminale) e provando a lanciare il
comando:
java -version
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 10
Ripetizioni Materie Scientifiche
Il risultato dovrebbe essere simile a quello in figura:
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 11
Ripetizioni Materie Scientifiche
Il primo programma in Java
In questa sezione scriveremo il primo programma in Java, “from scratch” e lo compileremo con gli
strumenti base messi a disposizione dal JDK. Quindi per seguire questo tutorial è previsto che
abbiate installato il compilatore Java ed il JRE, scaricabili da qui la cui installazione è stata descritta
nella lezione precedente.
Va premesso che nella vita di uno sviluppatore, non accade praticamente mai di scrivere un
programma Java interamente a mano: tutti usiamo sempre un IDE (Eclipse ad esempio, che
vedremo più avanti), che provvede i tool necessari ad una più semplice gestione dei file e alla
manutenzione dei progetti.
È comunque molto utile e istruttivo imparare a organizzare tutto “a mano”, passo dopo passo, anche
commettendo volutamente i tipici errori in cui si può incorrere la prima volta.
Il primo programma che scriveremo è breve ma già permette di osservare molte cose:
package my.first.project;
public class Primo {
public static void main(String[] args) {
System.err.println("ciao mondo");
}
}
Iniziamo copiando il codice in un file in un qualsiasi editor di testi. Salviamo e chiamiamo
l’esempio PrimoProgramma.java, poi eseguiamo il compilatore:
javac PrimoProgramma.java
Il risultato, spiacevole, sarà qualcosa di simile a quello seguante:
PrimoProgramma.java:3: error: class Primo is public, should be declared in a
file named Primo.java
public class Primo {
^
1 error
Il compilatore ci avverte che abbiamo commesso il primo errore (ahimè il primo di una lunga serie).
I nomi delle classi in Java, convenzioni
In Java infatti ogni classe pubblica ( public class) deve essere contenuta in un file il cui nome sia
identico al nome della classe stessa: ad esempio Classe dovrà stare nel file Classe.java e Primo
dovrà quindi stare nel file Primo.java.
In generale si usa assegnare ad ogni classe il relativo file per convenzione, anche quando non è
obbligatorio (se togliamo la keyword public e lasciamo solo class possiamo compilare il
programmino senza errori). Si ritiene infatti che questa pratica aiuti ad una buona organizzazione
del codice.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 12
Ripetizioni Materie Scientifiche
Prima di procedere a rinominare il file ed a fare la prova successiva vale la pena di soffermarsi alla
scelta del nome della classe (che noi abbiamo chiamato Primo).
In Java le classi possono avere i nomi più disparati: primo, PRIMO, PrImo123, primo_, _primo
sarebbero stati tutti nomi validi (documentazione ufficiale) ma esiste una convenzione che prevede
che i nomi delle classi inizino con un carattere maiuscolo, continuino con caratteri minuscoli e, se
composti da più parole, siano capitalizzate le prime lettere di tutte le componenti (la scrittura di
parole composte capitalizzando tutte le prime lettere è comunemente detta CamelCase).
Quindi se volessimo creare una classe che si chiami “prima classe del tutorial” la convenzione (non
la sintassi) ci indicherebbe di chiamarla PrimaClasseDelTutorial (nei nomi delle classi la sintassi
prevede che non si possano usare gli spazi).
Rinominiamo a questo punto PrimoProgramma.java in Primo.java e tentiamo ancora la
compilazione:
javac Primo.java
Questa volta non otterremo nessun errore e, accanto al file Primo.java, troveremo un secondo file
chiamato Primo.class. Il compilatore javac genererà dei files .class ogni volta che lo
utilizzaremo su dei file .java.
Il file Primo.class contiene il bytecode del nostro programma java (non provate a leggerlo in
quanto non c’è molto al momento da capirci, è un file binario con un sacco di caratteri
incomprensibili per noi) che possiamo pensare di eseguire, o più precisamente di chiedere alla
macchina virtuale java (JVM) di eseguire:
java Primo
Qui il nome “Primo” che passiamo come argomento dell’eseguibile java non si riferisce al nome
del file .class, ma è proprio il nome della classe da eseguire.
Rispettare il namespace
L’esecuzione anche questa volta darà un risultato inatteso, che potrebbe essere un semplice:
Errore: impossibile trovare o caricare la classe principale Primo
oppure un più articolato messaggio di errore:
Exception in thread "main" java.lang.NoClassDefFoundError: Primo (wrong name: my
/first/project/Primo)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:792)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:14
2)
...
In ogni caso l’errore è dovuto al fatto che la JVM cerca la nostra classe nella directory
my/first/project/; cosa che a una prima osservazione sembrerebbe strana ma che è giustificata
dal fatto che nel nostro programma la prima linea dice che la classe Primo appartiene al package
“my.first.project”.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 13
Ripetizioni Materie Scientifiche
Quindi in fase di esecuzione la JVM si aspetta di trovare la classe in un directory che è formata dal
nome del package dove i punti (‘.‘) sono sostituiti da slash (‘/‘).
Vale la pena di osservare che il risultato inatteso (errore di esecuzione) è segnalato dalla JVM sotto
forma di una Exception (eccezione) che, come impareremo nelle prossime lezioni, sarà sempre il
modo in cui la JVM ci comunica eventuali errori di run-time (durante l’esecuzione di un
programma).
L’eccezione in questo caso è semplice da decifrare semplicemente leggendo la prima riga.
La scritta “wrong name: my/first/project/Primo” ci fa infatti capire che
NoClassDefFoundError (sostanzialmente “Non ho trovato la definizione della classe”)
l’errore
Creiamo quindi la directory, spostiamoci entrambi i file (il file .java non sarebbe necessario
spostarlo ma la sua collocazione naturale è in quella directory) e riproviamo:
mkdir -p my/first/project
mv Primo.* my/first/project/
e finalmente eseguendo (va usato il nome completo, fully qualified):
java my.first.project.Primo
otteniamo:
ciao mondo
Package e JAR
I package in Java sono il modo più naturale di raggruppare le classi in gruppi (moduli, unità,
gruppi, categorie) in modo che il nostro codice sia più leggibile e possiamo pensare che ogni
componente di un package name rappresenti una directory sul filesystem.
Come per i nomi le classi, Java lascia molta libertà allo sviluppatore nella scelta delle componenti
di un package name. Tuttavia anche in questo caso una convenzione comunemente utilizzata è
quella di utilizzare solo caratteri minuscoli ed eventualmente numeri.
Poiché il compilatore genererà sempre almeno un file .class da ogni file .java (da un singolo file
.java possono essere generati anche più file .class), i programmi compilati in java potrebbero
diventare rapidamente scomodi da gestire in quanto composti da intere directory di file.
Per questo motivo insieme al compilatore ed alla JVM viene fornito anche un altri eseguibile il cui
nome è jar (java archiver) il cui scopo è esattamente quello di prendere una intera directory di class
files e trasformarla in un unico file (detto java archive) più facile da maneggiare e gestire. Ad
esempio:
jar cf primoprogramma.jar my
creerà il file primoprogramma.jar che contiene tutto il nostro albero di directory. In buona
sostanza un archivio jar è la versione compressa della directory ed il formato di compression è
esattamente lo zip; per “spacchettare” un jar si può addirittura usare qualsiasi programma in grado
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 14
Ripetizioni Materie Scientifiche
di decomprimere gli zip; anche per crearlo potreste farlo ma in tal caso dovreste creare a mano il
file MANIFEST.MF che trovate nella directory META-INF dell”archivio e che jar ha provveduto
automaticamente a creare.
Per controllare il contenuto del file .jar possiamo eseguire:
jar tvf primoprogramma.jar
Classpath e jar
Naturalmente quando vorremo utilizzare le classi compilate nel jar non avremo bisogno di
decompimerlo ma potremo chiedere direttamente alla JVM di utilizzare come ‘classpath’ il file jar:
java -cp primoprogramma.jar my.first.project.Primo
il classpath è sostanzialmente una sorta di filesystem virtuale dentro nel quale la JVM cerca le
classi. Se il nostro programma fosse composto di più classi archiviate in più files jar avremmo
potuto passarli tutti alla JVM concatenandoli:
java -cp primoprogramma.jar:secondo.jar my.first.project.Primo
NOTA: su Windows i file .jar devono essere separati da punto e virgola (‘;‘) e non da due punti
(‘:‘) che invece funziona su Unix e OSX.
Il main
Come ultima nota circa il nostro programmino di 6 righe (parentesi comprese) va osservato che il
contenuto della classe è formato da un metodo chiamato main e dichiarato public e static (dei
qualificatori parliamo meglio nella lezione sulla programmazione orientata agli oggetti).
Il fatto che il metodo si chiami main non è assolutamente un caso: main è precisamente il nome che
deve avere il metodo che vogliamo far eseguire per primo alla JVM quando viene lanciata. Non
solo: main dovrà avere anche la stessa firma (signature, ovvero gli argomenti ed il valore di ritorno)
che abbiamo utilizzato in Primo.
Il fatto che il metodo statico main ritorni int significa che sarà possibile restituire un intero al
sistema operativo come risultato dell’esecuzione di un programma (questo valore di ritorno è
solitamente considerato un modo per segnalare un eventuale errore se è diverso da zero) mentre
l’argomento args di tipo String[] (array di stringhe) sta a significare che quel metodo potrà ricevere
(dalla JVM e dal sistema operativo che la esegue) un numero arbitrario di argomenti di tipo stringa;
se modificate il programma come segue potrete sperimentare questa caratteristica:
package my.first.project;
public class Primo {
public static void main(String[] args) {
System.err.println("ciao " + args[0] + " !" );
}
}
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 15
Ripetizioni Materie Scientifiche
Compiliamo l’esempio ed eseguiamolo il comando:
java my.first.project.Primo " Java Developer"
In questa ultima versione potete anche osservare come sia semplice concatenare stringhe in java
utilizzando l’operatore ‘+‘, che tra numeri effettua la somma mentre tra le stringhe è (overloaded)
usato con il significato di concatenare.
Tanto per non stare con le mani in mano prima di iniziare la prossima lezione potete provare ad
osservare cosa succede se:


eseguite il programma specificando gli argomenti senza le virgolette
eseguite completamente senza argomenti
Cercando di spiegarvi anche il motivo dei risultati che ottenete.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 16
Ripetizioni Materie Scientifiche
IDE e strumenti di sviluppo avanzati
Nella sezione precedente abbiamo visto come poter realizzare un primissimo progetto scritto in
Java, come compilarlo e come costruire il file jar, possiamo dire tutto interamente “fatto a mano” e
con gli unici e soli strumenti fornitici dal JDK.
Tuttavia se dobbiamo utilizzare “daily” questo strumento per progetti medio/grandi, visto l’enorme
bagaglio di librerie che a corredo della JRE, le moltissime librerie di terze parti che esistono e che è
possibile importare nel progetto e viste le dipendenze che esistono fra i diversi progetti, è
abbastanza importante avere a nostra disposizione strumenti avanzati per lo sviluppo, che ci
supporti e ci semplifichi la vita.
Perciò in questo articolo facciamo una breve rassegna degli ambienti di sviluppo (IDE – Integrated
Development Environment) più famosi e più utilizzati nel mondo Java:
NetBeans
Eclipse
IntelliJ Idea
Anche la generazione dei un file jar ed in generale la gestione complessiva dei progetti non è
pensabile come operazione da fare manualmente senza l’ausilio di altri strumenti che tengano in
considerazione tutte le dipendenze, legami e configurazioni presenti fra i diversi elementi che
compongono un progetto Java.
I sistemi per l’automazione del processo di build ci vengono in aiuto in questo aspetto del ciclo di
sviluppo delle applicazioni; in questa categoria prenderemo in considerazione:
Ant
Maven
IDE – Integrated Development Environment
L’IDE per uno sviluppatore è un amico fidato con cui passa la maggior parte del tempo. Beh, forse
è per questo che sembra che gli sviluppatori si dividano in tifoserie agguerrite a difesa del proprio
ambiente di sviluppo preferito.
Cercando in rete qualche confronto tra Eclipse, NetBeans e anche IntelliJ Idea si trovano vere e
proprie battaglie di opinione. Ma forse la domanda stessa “quale è l’IDE migliore?” è mal posta:
non esiste un IDE migliore. Si tratta di applicazioni di grande complessità, estendibili, con centinaia
(letteralmente centinaia) di plugin e moduli, tutte hanno le features indispensabili, quelle utili ed
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 17
Ripetizioni Materie Scientifiche
anche quelle delle quali non sentirete mai la necessità, ma ciascuna le “offre” secondo le sue
modalità e punti di vista, quindi è una questione di gusto ed abitudine scegliere l’una o l’altra.
Io uso Eclipse (per abitudine) e tutte le volte che nelle prossime lezioni ci sarà la necessità di
utilizzare l’IDE (generazione di codice, validazione, gestione dei build e del deploy) lo faremo con
Eclipse ma potete star sicuri che si possono effettuare le stesse operazioni anche con NetBeans e
Idea.
Per scegliere davvero il consiglio è quello di provarli (a più riprese ed a livelli diversi di esperienza
con Java, perché le cose possono cambiare) e di non “innamorarsi”: ricordiamoci che sono solo IDE
e usare l’uno o l’altro è solo una questione di abitudine.
NetBeans
Nato come progetto universitario negli anni 90 e poi acquistato da Sun, che decise nel 2000 di
renderlo un progetto open source rilasciando tutti i sorgenti alla comunità, NetBeans è da
considerarsi l’IDE “ufficiale” per lo sviluppo con Java essendo ad oggi supportato direttamente da
Oracle.
Per alcuni anni è stato considerato una sorta di progetto all’inseguimento del più “featured” Eclipse,
ma con l’impegno diretto di Oracle è oggi un competitor di tutto rilievo e del tutto intercambiabile
con gli altri IDE.
NetBeans è il primo IDE ad avere supporto completo per le nuove features di Java 8, Java
Enterprise 7 e HTML5.
Per scaricarlo è sufficiente raggiungere la pagina di download di NetBeans e selezionare la versione
desiderata. In prima istanza sarà sufficiente la “Java SE” ma ne esistono altre versioni per lo
sviluppo in C/C++, HTML5/PHP, Java Enterprise, ed una che comprende tutte le altre. Per
completare l’installazione basta eseguire il file scaricato.
Appena aperto, l’IDE vi mostrerà una scheda informativa con l’accesso a documentazione e
tutorial; se volete iniziare a programmare chiudete la scheda informativa usando il pulsantino in alto
a sinistra.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 18
Ripetizioni Materie Scientifiche
Per creare un progetto Java con Netbeans è sufficiente selezionare il menu New > Project. Il
wizard ci guida poi fino alla creazione della prima classe nel progetto ed inserisce per noi anche il
metodo main.
Come si vede in figura l’IDE ci aiuta nella scrittura del codice segnalandoci gli errori (il marker
rosso alll’inizio della riga ancora non completa), assistendoci nella scelta dei nomi dei metodi (la
dropdown list contiene tutti i metodi dell’oggetto System.out che matchano con la parte scritta) e
dandoci immediatamente anche la documentazione (quella generata con javadoc).
Infine per compilare ed eseguire il progetto non serve tenere a mente dove si trova il suo main, la
struttura delle classi e il classpath, NetBeans lo fa per noi e basta utilizzare il pulsante Play (quello
con la freccia verde in alto) per eseguire e vedere l’output senza lasciare l’ambiente di sviluppo.
Eclipse
Eclipse è certamente l’ambiente di sviluppo più noto e usato in ambito Java (ma anche per molti
altri linguaggi). Nasce agli inizi del 2000 per un accordo tra molte grandi società (Borland, IBM,
QNX Software Systems, Red Hat, SuSE e molti altri) che concordarono nella creazione di una
fondazione (la Eclipse Foundation appunto) per promuovere lo sviluppo e la crescita di un IDE
originariamente sviluppata da IBM (ed il cui investimento in termini di tecnologia e sviluppo era a
quei tempi stimato in qualcosa come 40 milioni di dollari).
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 19
Ripetizioni Materie Scientifiche
Oggi la Eclipse Foundation oltre all’IDE vero e proprio rappresenta una delle organizzazioni più
floride di progetti open source.
Installare Eclipse è semplice: basta scaricare l’archivio ed eseguirlo. Come per NetBeans non c’è
molto altro da fare e l’IDE è pronto. Una volta lanciato, ci viene chiesto di selezionare uno
workspace, una directory di riferimento che Eclipse utilizza per organizzare i progetti, ciascuno con
una sua directory specifica.
La pagina di benvenuto di Eclipse è simile a quella di NetBeans e mostra l’immancabile scheda
introduttiva con i link alla documentazione ed i tutorial.
La creazione di un progetto è guidata da un semplice wizard (lanciato scegliendo, new > java
project) .
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 20
Ripetizioni Materie Scientifiche
Anche in questo caso è possibile con un solo pulsante Play (la freccia verde nella barra in alto)
eseguire un programma Java e visualizzarne l’output senza lasciare l’IDE.
Eclipse è diventato di uso comune anche perché google offre i plugin per sviluppare applicazioni
Android, e Google App Engine e GWT.
IntelliJ IDEA
Probabilmente il più giovane tra gli IDE che stiamo esaminando ma di certo il più aggressivo: la sua
pagina Web riporta nel title “The Best Java and Polyglot IDE”.
A differenza delle altre soluzioni proposte, Idea è un prodotto commerciale di JetBrains che ne offre
una versione community liberamente scaricabile ed una versione commerciale (“ultimate”).
Oltre a tutte le features che caratterizzano anche le altre IDE, Idea offre un egregio supporto per la
gestione di progetti basati su maven ed ha i suoi maggiori fan tra gli sviluppatori “enterprise”, dove
la flessibilità e la potenza degli strumenti di sviluppo viene messa a dura prova.
Build System
Anche per quanto riguarda i build system (maven in realtà non è semplicemente un build system,
ma un project management & comprehension tool) citiamo qui quelli più diffusi e ne vediamo le
caratteristiche e le funzionalità di base, lo scopo di questa guida non è quello di approfondire in
dettaglio questi argomenti, ma è utile conoscere l’esistenza dei tool più importanti per la gestione
dei progetti Java.
Apache Ant
Apache Ant (più comunemente Ant) è un tool di sviluppo per l’automazione del processo di
building, o semplicemente build system. È un progetto Apache, rilasciato Open Source sotto licenza
Apache Software License.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 21
Ripetizioni Materie Scientifiche
Per fare un paragone, è simile a Make (famoso tool per la complilazione di progetti C/C++). Anche
se implementato completamente in Java ed appositamente progettato per la piattaforma Java, Ant
può gestire qualunque tipo di progetto (anche C++).
La principale caratteristica di Ant è l’utilizzo di file XML per descrivere il processo di build,
tipicamente (per default) si utilizza un file chiamato build.xml.
Il build file contiene informazioni su come effettuare il build del progetto e per ogni progetto
possono essere presenti più target (azioni come creare directory, compilare i sorgenti, eseguire test,
…) e ciascun target può avere dipendenze con altri target.
Ecco un esempio di file build.xml per un semplice progetto Java “Hello World”, Vengono definiti
4 target (clean, clobber, compile e jar), ciascuno dei quali ha associata una descrizione.
<?xml version="1.0"?>
<project name="Hello" default="compile">
<target name="clean" description="remove intermediate files">
<delete dir="classes"/>
</target>
<target name="clobber" depends="clean"
files">
<delete file="hello.jar"/>
</target>
description="remove
all
artifact
<target name="compile" description="compile the Java source code to class
files">
<mkdir dir="classes"/>
<javac srcdir="." destdir="classes"/>
</target>
<target name="jar" depends="compile" description="create a Jar file for the
application">
<jar destfile="hello.jar">
<fileset dir="classes" includes="**/*.class"/>
<manifest>
<attribute name="Main-Class" value="HelloProgram"/>
</manifest>
</jar>
</target>
</project>
Il target jar come dipendenza compile, questo significa che prima che Ant inizi ad eseguire il
target jar dovrà aver prima eseguito correttamente il target compile.
All’interno di ogni target sono specificate le operazioni che Ant deve eseguire; queste sono spesso
realizzate con task che Ant ha già definiti (built-in).
Installare Ant
Se stiamo lavorando con uno degli IDE sopra descritti, l’installazione di Ant è quasi gratuita e gli
ambienti di sviluppo hanno già Ant incluso nella maggior parte delle loro distribuzioni. Per info e
dettagli:
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 22
Ripetizioni Materie Scientifiche



Ant su netBeans
Ant su Eclipse
Ant su IntelliJ Idea
In alternativa il primo passo è quello di scaricare Ant dal sito della Apache Foundation. Preleviamo
l’ultima versione del bynaryfile. A seconda del sistema operativo che abbiamo scarichiamo un
archivio tar oppure zip (nel caso di Windows).
Una volta scaricato e decompresso il file scegliamo il path “definitivo” per Ant sulla nostra
macchina. Infine andranno impostate le variabili d’ambiente JAVA_HOME e ANT_HOME e andrà
aggiunto ${ANT_HOME}/bin (Unix) o %ANT_HOME%/bin (Windows) nella PATH.
Per ulteriori info e dettagli su come installare e configurare Ant (anche in base al sistema operativo)
fate riferimento alla guida ufficiale.
Apache Maven
Apache Maven (o solo Maven) è un tool per l’automazione della fase di building di un progetto
usato principalmente e primariamente per progetti Java.
Maven mira principalmente a risolvere due aspetti:
1. descrivere come il programma/progetto deve essere costruito;
2. descrivere le sue dipendenze.
Come per Ant, la descrizione del processo di build, le sue dipendenze da moduli e componenti
esterne, l’ordine delle operazioni, le directory e i plugin necessari, è fatta attraverso un file XML.
Maven scarica automaticamente tutte le librerie Java ed i plugin necessari da uno o più repository
(come Maven2 Central Repository) e li salva in una cache locale.
I progetti Maven vengono configurati utilizzando un Project Object Model, che è salvato in un file
chiamato pom.xml. Di seguito un esempio minimalista:
<project>
<!-- model version is always 4.0.0 for Maven 2.x POMs -->
<modelVersion>4.0.0</modelVersion>
<!-- project coordinates, i.e. a group of values which uniquely identify
this project -->
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 23
Ripetizioni Materie Scientifiche
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0</version>
<!-- library dependencies -->
<dependencies>
<dependency>
<!-- coordinates of the required library -->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<!-- this dependency is only used for running and compiling tests ->
<scope>test</scope>
</dependency>
</dependencies>
</project>
Questo POM definisce un identificatore univoco per il progetto (coordinates) e le sue dipendenze
nel framework JUnit.
In ogni caso questo è abbastanza per costruire il progetto ed eseguire gli unit test associati (Maven
fornisce valori di default per la configurazione del progetto).
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 24
Ripetizioni Materie Scientifiche
Variabili e dichiarazioni
In questa sezione introdurremo le basi della sintassi del Java insieme a concetti utili per la
comprensione di alcuni aspetti rilevanti dell’esecuzione di un programma.
Molte delle parti sono probabilmente già nel background di chiunque abbia mai utilizzato un
qualsiasi linguaggio di programmazione ma nell’introduzione della sintassi qualche breve nota di
carattere generale si ritiene utile.
Variabili, identificatori e indirizzi
Formalmente potremmo definire una variabile in un linguaggio di programmazione come la coppia
composta da:


un nome simbolico (detto anche identificatore)
e un indirizzo di memoria destinato a contenere una determinata quantità, detta
comunemente valore della variabile.
Il nome simbolico della variabile serve nei programmi per far riferimento all’indirizzo di memoria
al fine di accedere e/o modificarne il valore durante l’esecuzione.
Dichiarare le variabili
In Java (che appartiene alla classe dei linguaggi tipati) ogni variabile ha associato anche un tipo
(come Integer, String, boolean, etc.) che definisce le caratteristiche che avranno i valori che la
variabile potrà assumere.
Ad esempio una variabile di tipo Integer potrà contenere il valore 42 ma non il testo
"quarantadue".
In generale in Java il tipo di una variabile può essere uno dei tipi predefinitti (primitivi) del
linguaggio oppure un tipo definito da noi come vedremo nelle prossime sezioni.
La sintassi per la dichiarazione di una variabile in Java è la seguente:
[public|protected|private] [static] [final] Tipo identificatore [= value];
dove le parti tra parentesi quadre ‘[]‘ sono opzionali ed il simbolo pipe ‘|‘ deve essere letto
“oppure” (il significato delle keywords verrà chiarito nel seguito).
Possiamo anche inizializzare la variabile quando è presente il simbolo ‘ =‘ al quale segue il valore
che dovrà assumere.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 25
Ripetizioni Materie Scientifiche
I nomi delle variabili
Sintatticamente l’identificatore (nome) di una variabile è una sequenza di lettere e cifre il cui primo
elemento deve essere una lettera oppure il carattere underscore (‘_‘) o ancora il carattere dollaro
(‘$‘).
Vige comunque la convenzione (non regola sintattica) che i nomi delle variabili inizino con una
lettera minuscola e, qualora formati da più parole concatenate, tutte le parole successive alla prima
siano capitalizzate e non vengano usati i simboli _ e $ nonostante siano ammessi. Ad esempio:
int nomeDellaVariabileIntera;
Per i nomi dei tipi, ovvero per le classi, la convenzione prevede invece che la prima lettera sia
maiuscola):
class LaMiaClasse
L’identificatore può essere una qualsiasi stringa ma esistono alcune parole riservare del linguaggio
che non possono essere utilizzate come identificatori:
abstract
assert
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
enum
extends
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
new
package
private
protected
public
return
short
static
strictfp
super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while
Tipi di variabili
In Java si distinguono tre tipi di variabili: variabili locali, variabili di istanza e variabili di classe.
Vediamo in dettaglio di che si tratta.
Variabili Locali
Si parla di variabili locali quando la dichiarazione avviene all’interno di un metodo.
Le variabili locali sono create quando un metodo viene chiamato e scompaiono (vengono cancellate
dalla memoria) quando il metodo termina.
Ogni variabile dichiarata all’interno del metodo può essere utilizzata solamente all’interno del
metodo stesso ed in Java le variabili locali non possono essere utilizzate prima della loro
inizializzazione.
Per esempio, se provassimo a compilare il seguente metodo:
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 26
Ripetizioni Materie Scientifiche
void add(long i) {
long j;
j = j + i;
}
il compilatore Java ci darebbe un messaggio di errore perchè la variabile j non è stata inizializzata
prima del suo uso e non è quindi possibile aggiungere un valore alla variabile fino a che a questa
non è stato assegnato un valore.
Il codice seguente sarebbe invece compilato senza alcun errore:
void add(long i) {
long j = 1;
j = j + i;
}
Nell’esecuzione di questo frammento di codice la macchina virtuale Java crea in memoria lo spazio
per registrare le variabili locali i e j, per poi cancellarle (liberando lo spazio in memoria) alla fine
dell’esecuzione del metodo add.
Scope di una variabile
Più in generale si definisce scope di una variabile l’area del codice nel quale un identificatore resta
associato ad un indirizzo di memoria (e quindi l’area di codice entro il quale una variabile mantiene
il suo valore).
In Java ogni blocco (cioè ogni gruppo di linee di codice racchiuso da parentesi graffe {}) definisce
uno scope e ogni variabile locale ha come scope l’area di codice che inizia dalla definizione della
variabile stessa e termina con il blocco corrente.
Variabili di istanza
Le variabili di istanza, anche note come field o campi, sono dichiarate all’interno di una classe ma
all’esterno di ogni metodo.
I field hanno come scope l’intero corpo della classe in cui sono dichiarati, compresi i metodi della
classe stessa. Quindi sono visibili all’interno di tutti i metodi della classe.
Può succedere che una variabile locale in un metodo (oppure il parametro di un metodo) abbia lo
stesso nome (identificatore) di una variabile di istanza. In questo caso ha la precedenza la variabile
più specifica, cioè la variabile locale o il parametro.
Vediamo un esempio:
public class Scope {
int var = 6;
public void primoMetodo(int var) {
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 27
Ripetizioni Materie Scientifiche
int i = var;
// in questo caso ha precedenza il parametro e
// i assume il valore che sarà passato come parametro al metodo
// ...
}
public void secondoMetodo() {
int var = 7;
int i = var;
// qui ha precedenza la variabile locale al metodo, quindi
// i ha il valore 7
// ...
}
public void terzoMetodo() {
int i = var;
// qui semplicemente assegnamo ad i il valore della
// variabile di istanza e i prende il valore 6
// ...
}
public void quartoMetodo(int var) {
int i = this.var;
// in questo caso i assume il valore 6 indipendentemente
// dal valore del parametro poiché abbiamo utilizzato la
// keyword 'this', indica di utilizzare la variabile 'var'
// che abbiamo definito come field e che appartiene
// all'istanza corrente della classe.
// ...
}
}
Un’istanza di una variabile (non statica) continua ad esistere nella memoria di un programma fino a
quando esiste l’oggetto che la contiene (ed un oggetto “rimane in vita” fino a quando ne esiste
almeno una referenza, quindi una variabile associata ad esso).
Variabili di classe (static)
Le variabili di classe infine, comunemente dette anche static field o campi statici, sono variabili di
istanza ma nella loro definizione viene usata la keyword ‘static’.
static int var = 6;
Una variabile di classe è una variabile visibile da tutte le istanze di quell’oggetto ed il suo valore
non cambia da istanza ad istanza, per questo appartiene trasversalmente a tutta la classe.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 28
Ripetizioni Materie Scientifiche
Più in dettaglio mentre per le variabili di istanza viene allocata una nuova locazione di memoria per
ogni istanza di una classe, per le variabili statiche esiste una unica locazione di memoria legata alla
classe e non associata ad ogni singola istanza.
Una variabile di classe, statica, vive (cioè mantiene occupata la memoria e continua a mantenere il
suo valore) fino al termine del programma.
Modificatori di visibilità: public, private, protected, default
Le variabili di istanza e di classe possono essere ulteriormente qualificate per mezzo delle keywords
public e private che ne determinano la visibilità all’esterno della classe in cui sono dichiarate.




Se utilizziamo la keyword private una variabile sarà visibile (accessibile, utilizzabile per far
riferimento al suo indirizzo di memoria e quindi al suo valore) solamente all’interno della
classe;
mentre se qualificheremo la variabile come public indicheremo al compilatore che la
variabile potrà essere utilizzata da qualsiasi parte del codice in cui ci sia una istanza della
classe (con la notazione idIstanzaClasse.nomeVariabile).
Protected significa che la variabile sarà accessibile da ogni altra classe che appartiene al
medesimo package della classe che contiene la variabile e da ogni classe che ne deriva (la
estende).
Se non specifichiamo un qualificatore di visibilità, la variabile sarà lasciata con la visibilità
di default che in Java signifca che sarà accessibile solo da tutte le classi nel medesimo
package.
Variabili final e static final
Infine si usa la keyword final per dichiarare una variabile che potrà essere inizializzata una sola
volta, sia nella fase di dichiarazione o attraverso una successiva assegnazione.
Al contrario delle costanti, il valore delle variabili final non è necesariamente noto a compile-time
ma il loro indirizzo di memoria può essere inizializzato una sola volta rendendone possibile
l’utilizzo in alcuni contesti in cui sarebbe impossibile utilizzare le normali variabili locali.
In Java si definiscono costanti le variabili che vengono qualificate contempraneamente come final
e static: è convenzione che i nomi delle variabili final siano in maiuscolo e se il nome è costituito
da più parole, queste vengano separate dal carattere underscore (‘ _‘);
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 29
Ripetizioni Materie Scientifiche
Tipi primitivi di Java e valori
Java è un linguaggio tipato, abbiamo già accennato a questo in precedenza, ciò significa che ogni
variabile prima di essere utilizzata deve essere dichiarata: dobbiamo quindi assegnarle un nome ed
un tipo.
Cos’è un tipo
Il tipo è l’insieme di caratteristiche che qualsiasi valore assunto da una variabile dovrà soddisfare.
Ad esempio: “essere un intero”, “essere una sequenza di carattteri Unicode”, etc.
I tipi primitivi in Java
Il tipo di una variabile può essere costruito dallo sviluppatore per composizione a partire da un set
predefinito di tipi detti comunemente tipi primitivi.
I tipi primitivi in Java sono 8 e ciascuno di essi è pensato per rappresentare un certo tipo di
informazione e utilizzando una quantià specifica di memoria.
Inoltre, parlando di dichiarazione delle variabili, abbiamo detto che le variabili “locali” devono
essere inizializzate per evitare errori di compilazione, questo non è vero per le variabili di istanza
per le quali, per ogni tipo primitivo, è specificato un valore di default.
Tipo
Q.tà di Memoria Informazione rappresentata
byte
8 bit
short
16 bit
int
32 bit
long
64 bit
float
32 bit
Variabile con segno (con rappresentazione “two’s
complement”, complemento a due) e rappresenta valori in un
range [-128 e 127] (estremi inclusi)
Numeri interi (con segno) in un range [-32,768, 32,767]
Numeri interi (per default con segno, signed) in un range [231, 231-1] . Con Java 8 è stata introdotta la possibilità di
utilizzare gli int per rappresentare quantità unsigned che
potranno avere range [0, 232-1] (grazie ad appositi metodi
statici introdotti nelle classi Integer e Long)
Numeri interi (per default con segno, signed) in un range [263, 263-1]. Come per gli interi in Java 8 esiste la possibilità
di utilizzarli come quantità unsigned con range (positivo) che
arriva fino a 264-1.
Numeri in virgola mobile in singola precisione secondo la
specifica IEEE 754, utilizzando la rappresentazione segno,
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Valore
di
default
0
0
0
0L
0.0f
Pag. 30
Ripetizioni Materie Scientifiche
mantissa esponente.
(-1)segno * mantissa * 2esponente
Nella versione a 32bit il range rappresentabile va calcolato
pensando ad un bit di segno, una mantissa a 23bit e un
esponente a 8bit con valori compresi tra -126 e 127.
double 64 bit
Inoltre lo standard prevede la rappresentazione di due valori
per zero (da destra e da sinistra) due per infinito (positivo e
negativo), e di valori NaN (not a number) da utilizzare ad
esempio come risultati di operazioni impossibili (es. divisioni
per zero).
Numeri in virgola mobile in doppia precisione secondo la
specifica IEEE 754. La precisione con cui vengono
0.0d
rappresentati i numeri aumenta in virtù dell’aumento del
numero di bit utilizzati.
non specificato,
ma sarebbe
boolean
sufficiente un
solo bit
serve a rappresentare solamente 2 valori: vero o falso ( true o false
false).
16 bit
È utilizzato per la memorizzazione di caratteri del charset
Unicode) nel range ['\u0000', '\uffff'] (in esadecimale) \u0000
o equivalentemente [0,65535].
char
Va aggiunto che ogni variabile di tipo oggetto (cioè di tipo non primitivo) viene per default
inizializzata con il valore speciale null.
Va notato che la stessa documentazione ufficiale di Java segnala che, benché i valori di default
siano garantiti per tutti i field non inizializzati é da considerarsi una cattiva pratica quella di non
inizializzare le variabili e quindi si dovrebbe cercare di evitarla.
Literals, la codifica dei valori numerici
I tipi primitivi sono elementi con cui poter costruire tutti gli altri oggetti da utilizzare nei
programmi, perciò sono da considerarsi dei tipi di dato speciali del linguaggio. Per questo c’è
l’esigenza utilizzare modalità specifiche per poter definire i valori nel codice.
I literals sono appunto le codifiche dei valori di questi tipi nel linguaggio. Vediamo quali sono.
I possibili valori del tipo boolean sono esprimibili con le keyword true e false.
Valori per i tipi int e long sono esprimibili come interi in base 10 utilizzando la comune notazione
posizionale:
int lifeUniverseAndEverything = 42;
oppure in rappresentazione esadecimale (Hex, in base 16) utilizzando il suffisso ‘0x‘ o binaria
(base 2, dalla versione 7 di java) utilizzando il suffisso ‘0b‘:
int lifeUniverseAndEverythingBis = 0x2A;
// esadecimale
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 31
Ripetizioni Materie Scientifiche
int lifeUniverseAndEverythingTer = 0b00101010;
// binario
Questi stessi literals possono essere utilizzati anche per esprimere valori di tipo byte e short mentre
per esprimere valori long si pospone alla rappresentazione la lettera ‘ L‘ (è valido anche il carattere
minuscolo ma sconsigliato a causa della sua scarsa leggibilità essendo confondibile con il numero
‘1‘):
long bigLUE = 4242424242L;
Valori di tipo non intero possono essere analogamente espressi separando la parte decimale con il
simbolo ‘.‘ (punto) e saranno considerati di tipo double a meno che non sia posposta la lettera ‘ F‘
(o ‘f‘).
I literals di tipo double possono essere terminati con la lettera ‘ D‘ (o ‘d‘) qualora per motivi di
leggibilità la si ritenga opportuna (ma non è obbligatoria).
È ammessa anche la cosiddetta notazione scientifica per i numeri in virgola mobile che consiste
nell’utilizzo della lettera E (o e) seguita da un numero che esprime la potenza di 10 da moltiplicare
al numero espresso prima di essa:
double mille
= 1000.0;
double milleSci
= 1.0e3;
float milleFloat = 1000.0f;
A partire dalla versione 7 di Java è possibile utilizzare il carattere underscore (‘_‘) in tutti i literal
numerici per aumentarne la leggibilità, ad esempio separando le migliaia:
float milleEasy = 1_000.0f;
il carattere ‘_‘ non ha alcun uso se non quello di facilitarne la lettura e può essere utilizzato
esclusivamente tra coppie di numeri (non come primo o ultimo carattere, non adiacente al ‘ .‘ o agli
altri caratteri ammessi nelle notazioni).
Character and String Literals
I valori di tipo carattere possono essere espressi per mezzo di caratteri Unicode (UTF-16) racchiusi
tra apici singoli che possono eventualmente essere espressi sotto forma di charcode utilizzando
“Unicode escape” :
char nCirconflesso
= 'ñ';
char nCorconflessoCode = '\u00F1';
Sono supportati anche alcune speciali rappresentazioni (dette escape sequences o sequenze di
escape):
Escape Carattere
backspace (indietro)
\b
tab
\t
line feed (fine linea)
\n
form feed (fine pagina / nuova pagina)
\f
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 32
Ripetizioni Materie Scientifiche
\r
\’
\”
\\
carriage return (ritorno carrello / a capo)
apice singolo
doppio apice
backslash (\)
Stringhe e numeri
In Java, accanto agli 8 tipi primitivi sono da considerarsi tipi di dato speciali (detti comunemente
Simple Data Objects) anche i tipi String e Number (e derivati) che fungono in qualche modo da
controparte dei dati primitivi dove ci sia l’esigenza di utilizzare un oggetto invece che direttamente
un tipo (la differenza risulterà più chiara nel corso delle prossime lezioni).
Basti sapere al momento che le variabili di tipo String sono sequenze di char che possono essere
inizializzate utilizzando le virgolette (doppi apici):
String author = "Douglas Noël Adams";
mentre i tipi Integer, Byte, Long, Float e Double (controparti dei medesimi tipi primitivi scritti
con la prima lettera minuscola) sono inizializzabili con i medesimi literals presentati per i
corrispondenti tipi nativi ma tutti quanti vengono per default inizializzati al literal null (letto nullo)
se non assegnamo loro esplicitamente un valore.
Il compilatore è quasi sempre in grado di convertire automaticamente i tipi primitivi nei rispettivi
simple object (operazione detta boxing ed unboxing), fanno eccezione solo alcuni casi particolari.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 33
Ripetizioni Materie Scientifiche
Classi wrapper
Come abbiamo già detto nella lezione precedente, in Java per ogni tipo primitivo esiste un
corrispondente Simple Data Object o, come si suol dire, una Classe Wrapper.
Dato un tipo primitivo (i cui nomi iniziano tutti rigorosamente con la prima lettera minuscola) si
ottiene il corrispondente Data Object sostanzialmente capitalizzando il nome come mostrato nella
tabella seguente:
Tipo primitivo Classe Wrapper
byte
Byte
short
Short
int
Integer
long
Long
float
Float
double
Double
char
Character
boolean
Boolean
Anche se ad un primo sguardo può sembrare che ci sia poca differenza tra un tipo primitivo e la sua
controparte ‘wrapped’ (anche e spesso detta ‘boxed’) tra le due c’è una fondamentale distinzione: i
tipi primitivi non sono oggetti e non hanno associata alcuna classe e quindi devono essere trattati in
modo diverso rispetto agli altri tipi (ad esempio non è possibile utilizzarli nelle collezioni, che
saranno argomento di future lezioni) e non possono avere metodi.
Per ovviare a questa distinzione Java mette dunque a disposizione delle classi preconfezionate per
contenere, “wrappare” i tipi primitivi. Possiamo infatti pensare ad una classe wrapper esattamente
come un involucro (wrap) che ha l’unico scopo di contenere un valore primitivo rendendolo da un
lato un oggetto e dall’altro “ornandolo” con metodi che altrimenti non avrebbero una loro naturale
collocazione.
Tutte le classi wrapper sono definite nel package java.lang e sono qualificate come final, perciò
non è possibile derivare da loro. Inoltre tutte queste classi sono immutabili, cioè non è possible
dopo la costruzione cambiarne il valore.
Mentre Byte e Character derivano direttamente da Object tutti i Data Object di tipo numerico
derivano da Number che a sua volta è un discendente diretto di Object.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 34
Ripetizioni Materie Scientifiche
Dai tipi semplici ai Data Object
Anche se Java 1.5 ha introdotto il concetto di ‘autoboxing/unboxing’, che approfondiremo in una
apposita lezione, passare da un tipo primitivo alla sua versione wrappata è in linea di principio
semplice ma laborioso:
int val = 44;
//dato un tipo primitivo si crea la classe wrapper
Integer value = new Integer(val);
//dalla classe wrapper è possibile "estrarre" il valore
int valueBack = value.intValue();
Metodi speciali per il parsing
Le classi wrapper sono in Java anche il posto in cui trovano posto gli utilissimi metodi che servono
per fare il parsing di stringe e convertirle in valori numerici, ad esempio:
String quarantatre = "43";
Integer q = new Integer(quarantatre);
Quando utilizziamo questi metodi per la conversione occorre gestire una eventuale generazione di
errori. Infatti non tutte le possibili sequenze di caratteri sono numeri, prendiamo ad esempio il
seguente snippet:
String quarantaquattro = "quarantaquattro";
Integer q = new Integer(quarantaquattro);
Il parser non sarebbe in grado di processare con successo la stringa e otterremmo un errore che la
JVM segnala attraverso una eccezione di tipo NumberFormatException (più avanti approfond
String
Tra i tipi wrapper potremo annoverare anche il tipo String che con i Simple Data Object condivide
moltissime caratteristiche (ad esempio l’immutabilità). Ma data l’importanza delle stringhe
riserveremo a loro un’intera lezione.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 35
Ripetizioni Materie Scientifiche
Boxing, unboxing e autoboxing
In questa lezione esaminiamo l’Autoboxing in Java: si tratta della caratteristica del linguaggio che,
a partire dalla versione 1.5, ci consente di lavorare con tipi primitivi e tipi wrapper in maniera
intercambiabile.
Boxing
In Java generalmente ci occupiamo di utilizzare e definire oggetti come istanze di una classe, quindi
con metodi e attributi. Ma per motivi pratici abbiamo spesso a che fare con tipi primitivi (int,
double, boolean, …) che non sono oggetti, ma “tipi semplici”.
Prima dell’introduzione dell’autoboxing programmando in Java ci trovavamo nella necessità di
convertire un tipo primitivo nella sua corrispondente classe wrapper. Lo spezzone di codice che
segue potrebbe non risultarvi nuovo:
Integer x = new Integer (10);
Double y = new Double (5.5);
Boolean z = Boolean.parseBoolean("true");
Queste operazioni sono note come operazioni di boxing, cioè “inscatolamento” del tipo primitivo
nel relativo tipo wrapper al fine di utilizzare un oggetto e tutte le sue proprietà (ad esempio porre un
intero in una lista o operazioni che hanno necessità di maneggiare oggetti).
L’autoboxing
Veniamo alla novità introdotta da Java 1.5 con un esempio pratico:
Integer x = 10;
Double y = 5.5f;
Boolean z = true;
Number n = 0.0f;
Attraverso autoboxing gli oggetti scritti nello spezzone di codice vengono automaticamente creati
con i valori di riferimento dettati, senza generare errori. Questo permette di scrivere codice più
leggibile e maneggevole.
Chiaramente alla funzione di boxing è associata l’operazione di unboxing che trae gli stessi
vantaggi della precedente:
/** Esempio di operazione di unboxing */
int x = -1;
Integer y = x;
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 36
Ripetizioni Materie Scientifiche
Il linguaggio si arricchisce, permettendo allo sviluppatore di non preoccuparsi delle operazioni di
conversione (boxing e uboxing, appunto) lasciandole al compilatore del bytecode che si occuperà di
gestirle per noi (autoboxing).
A basso livello la situazione non è affatto cambiata, la macchina virtuale che esegue il bytecode non
ha avuto cambiamenti; ciò che cambia è a livello di compilazione. Infatti le operazioni di
conversione a livello di bytecode sono quelle che avremmo svolto manualmente, solo che ci viene
in aiuto il compilatore preoccupandosi di effettuarle lui in automatico per noi.
Il comportamento dell’operatore di uguaglianza
Ovviamente ciò significa che le regole osservate in ambiente pre Java 1.5 continuano a valere. Dal
punto di vista della sintassi comunque la cosa produce notevoli vantaggi in quanto ora è possibile
associare gli operatori aritmetici e le strutture condizionali ai tipi wrapper (ricordandoci sempre che
verrà fatto unboxing automatico):
/** Esempio di utilizzo dei costrutti come operatori aritmetici
* e statement condizionali
*/
Integer x = 0;
Boolean verify = true;
while (verify)
{
x++;
if (x > 10)
verify = false;
}
Il codice scritto mostra come sia possibile utilizzare i costrutti sia con operatori aritmetici sia in
statement condizionali (if, while, for, …). Unico caso a cui fare attenzione è il caso
dell’uguaglianza tra due istanze di wrapper che puntano al medesimo valore:
/** Esempio di uguaglianza tra istanze di wrapper */
Integer x = 1000;
Integer y = 1000;
x==y ??
La condizione precedente dovrebbe risultare falsa, anche se i valori sono uguali. In pratica di fronte
all’operatore di uguaglianza, il compilatore si comporta normalmente, facendo comparazione tra
due istanze di oggetto senza effettuare unboxing. L’operazione, tradotta sarebbe:
Integer x = new Integer (1000);
Integer y = new Integer (1000);
x == y; // false!
Qui forse è più intuitivo capire perché il risultato è falso. Tutto ciò risulta verificato a meno di
alcune situazioni dove la macchina virtuale utilizza delle ottimizzazioni. Nei seguenti casi la JVM
crea delle istanze immutabili che vengono riutilizzate (per motivi di performance):
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 37
Ripetizioni Materie Scientifiche




Valori boolean true e false;
Valori del tipo byte;
Primo byte del tipo int (valori tra -127 e 127);
Primo byte del tipo char;
Ad esempio, comparare due Integer che hanno come valore 100 ci darebbe true (fate qualche
tentativo).
Impatto sull’overload dei metodi
Altro comportamento a cui dobbiamo porre attenzione è nel caso dell’overload di metodi, dove c’è
la possibilità di confusione in fase di compilazione:
/** Esempio di overload di metodi */
public void metodoA(Integer x);
public void metodoA(double y);
public void usaA()
{
int d = 0;
metodoA(d);
}
In questo caso il compilatore non effettua alcuna operazione di unboxing, proprio perché non c’è
modo di sapere dinamicamente come comportarsi. In generale, di fronte a situazioni di ambiguità,
per mantenere anche la compatibilità con le precedenti versioni, il comportamento è quello che ci
sarebbe con la versione di Java 1.4. In questo specifico caso la chiamata sarebbe fatta al metodo che
accetta come tipo il double.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 38
Ripetizioni Materie Scientifiche
Operatori e casting
Gli operatori in Java sono simili a quelli che si trovano in altri linguaggi di programmazione: il
core di Java prevede per i tipi primitivi operatori algebrici, logici, etc. In questa lezione esaminiamo
questi operatori di base, ma oltre a questi possiamo definire, per ogni tipo, metodi che ne
implementino di nuovi.
Iniziamo con l’operatore “punto” (.) che serve per l’accesso a campi e metodi di classi e oggetti.
Per accedere al campo simpleField dell’oggetto myObject scriviamo semplicemente:
myObject.simpleField
Se myObject espone anche un metodo myMethod(int value), possiamo involarlo scrivendo:
myObject.myMethod(100)
Casting
Prima di proseguire con gli operatori bisogna ricordare che Java è un linguaggio “fortemente
tipato”, perciò i tipi sono fondamentali e c’è uno stretto controllo sull’utilizzo dei tipi: ad esempio
non è possibile assegnare un carattere ad un intero, cosa possibile nel linguaggio C, ma è anche
impossibile assegnare un double ad un float, senza un esplicito casting.
Il casting consiste nel forzare un valore di un tipo ad assumere un tipo diverso: se scriviamo 5 ad
esempio abbiamo un numero intero, ma se ne facciamo un cast a float indenderemo la versione di
5 “reale”, per farlo basta scrivere:
(float) 5;
In questo modo possiamo ottenere i famosi assegnamenti di interi a reali e di reali double a reali
float, prendiamo ad esempio queste variabili:
double v1 = 10.0;
float v2;
int v3 = 5;
Gli assegnamenti che seguono sono errati:
v2 = v1; // non si può assegnare un double a un float
v1 = v3; // non si può assegnare un intero a un double
Per rendere possibili questi assegnamenti, quindi “leciti” per Java, ci dobbiamo servire dei cast:
v2 = (float) v1;
v1 = (double) v3;
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 39
Ripetizioni Materie Scientifiche
Operatori artitmetici
Come dicevamo abbiamo a disposizione i classici operatori aritmetici (+, -, *, / e %), ma è
importante sottolineare che anche le operazioni sono tipate, quindi se sommiamo due interi
otteniamo un valore intero, così per tutte le operazioni, in questo modo possiamo sempre prevedere
il tipo del risultato di una certa espressione.
A questo proposito è importante ricordare che la divisione tra interi, a differenza di altri linguaggi,
ritorna un valore intero. Quindi se vogliamo ottenere il numero reale derivato dalla divisione dei
due numeri interi x e y, bisogna prima trasformarli in reali:
float risultato= (float) x
/ (float) y;
Operatori abbreviati
Abbiamo ad un certo punto usato un assegnamento particolare, ovvero +=, questa è una
abbreviazione, serve ad incrementare il valore di una variabile senza doverla riscrivere. Se ad
esempio vogliamo aggiungere alla variabile a il valore 5, sommandolo al suo valore attuale
scriveremmo:
a = a + 5;
oppure, in modo abbreviato:
a += 5;
Vale lo stesso per ogni operatore binario (es. -, *, /).
Java inoltre offre altri quattro operatori che sono delle abbreviazioni, due di incremento di variabili
e due di decremento. Sia X una variabile, possiamo scrivere:
X++; // valuta X, poi incrementa X di 1
X--; // valuta X, poi decrementa X di 1
++X; // incrementa X di 1, poi valuta X
--X; // decrementa X di 1, poi valuta X
L’espressione X++ è un comando di assegnamento, ma anche una espressione che restituisce un
risultato. Il comportamento cambia a seconda che i simboli di incremento o decremento precedano
o seguano la variabile:


se l’operatore segue la variabile, l’espressione restituisce il valore attuale della variabile
prima di modificarlo;
se l’operatore precede la variabile, l’espressione restituisce il valore della variabile già
modificato.
Ad esempio:
X = 10;
Y = X++;
// risultato: X=11 e Y=10
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 40
Ripetizioni Materie Scientifiche
Ecco un esempio più complesso da poter provare:
class incdec {
public static void main(String [] a)
{
int X,Y,Z,W,V;
X=10;
System.out.println("X="+X);
Y=X++;
System.out.println("Y=X++: ho X="+X+",Y="+Y);
Z=++Y;
System.out.println("Z=++Y: ho Z="+Z+",Y="+Y);
W=Z--;
System.out.println("W=Z--: ho W="+W+",Z="+Z);
V=--W;
System.out.println("V=--W: ho V="+V+",W="+W);
}
}
Operatori su stringhe
Per le stringhe esiste un operatore di concatenamento:
String a = "Pietro " ;
String b = a + "Castellu";
String b += "cci"
Il risultato sarà "Pietro Castellucci". L’operatore + appende ad una stringa anche caratteri e
numeri, ad esempio possiamo costruire una stringa scrivendo:
System.out.println("Numero complesso:"+ parteReale + " +i" + parteImmaginaria +
" ha modulo " + modulo);
Sembra strano questo, anche in considerazione del fatto che abbiamo detto che le espressioni Java
sono ben tipate a differenza del C, questo avviene però perché il sistema effettua delle conversioni
implicite di tipi primitivi e oggetti in stringhe. Ad esempio definendo un qualsiasi oggetto, se ne
definisco un metodo toString(), il sistema che si troverà a lavorare con stringhe e con quest’oggetto
ne invocherà automaticamente il metodo toString().
Operatori di confronto
Altri operatori sono quelli di confronto, ovvero:
Operatore Descrizione
>
maggiore di
espressione Cosa restituisce
true se x è strettamente maggiore di y, false
x>y
altrimenti
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 41
Ripetizioni Materie Scientifiche
>=
<
<=
==
!=
maggiore o uguale x>=y
di
x<y
minore di
minore o uguale di x<=y
x==y
uguale
x!=y
diverso
true
se x è maggiore o uguale di y, false altrimenti
true
se x è strettamente minore di y, false altrimenti
true se x è minore o uguale di y, false altrimenti
true se x è uguale a y, false altrimenti
true se x è diverso da y, false altrimenti
Operatori logici
Vi sono gli operatori logici che ci aiutano a definire espressioni booleane , che in altre parole
mettono in relazione il verificarsi di più condizioni. Vediamo, attraverso questa tabella come
possiamo mettere in relazione due valori (o espressioni) x e y di tipo booleano:
Operatore Descrizione espressione Cosa restituisce
true se x e y sono entrambe vere, false altrimenti (almeno
x&&y
and
&&
una delle due false)
true se almeno una tra x e y è vera (o entrambe), false
x||y
or
||
altrimenti (entrambe false)
!x
not
!
true se x è falsa e false se x è vera.
Operatori Bitwise
Inoltre vi sono gli operatori binari orientati ai bit, che effettuano le operazioni logiche
confrontando, i bit degli operandi:
Operatore Descrizione
espressione Cosa restituisce
il valore determinato dall’operazione di and tra i bit di x e
x&y
&
and ‘bit a bit’
|
x|y
or ‘bit a bit’
xor
(o
or
esclusivo) ‘bit a x^y
bit’
^
y
>>
<<
shift destro (o
sinistro)
con x>>y
segno
>>>
shift
destro x>>>y
senza segno
~
complemento
~x
il valore determinato dall’operazione di or tra i bit di x e y
il valore determinato dall’operazione di xor tra i bit di x e
y (lo xor ritorna true (o il bit 1) solo se uno degli
operandi è vero e l’altro è falso)
sposta i bit della variabile x verso destra (o sinistra) di y
posizioni, preservando il bit di segno. Lo slittamento a
sinistra prevede l’inserimento di bit 0 in coda, e non
presenta particolari problemi. Con lo slittamento a destra
le cose cambiano perché coinvolge l’ultimo bit che è
speciale, in quanto rappresenta il segno
sposta i bit della variabile x verso destra di y posizioni,
senza considerare il bit di segno (vengono aggiunti sempre
0 a sinistra)
inverte tutti i bit della variabile x (gli 0 diventano 1 e
viceversa)
Queste operazioni binarie sono molto utili nella costruzione di maschere.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 42
Ripetizioni Materie Scientifiche
Operatore condizionale
L’ultimo operatore da vedere è l’operatore condizionale, che permette di definire espressioni con
due differenti risultati a seconda del valore assunto da una espressione booleana. Ecco la sintassi:
(espressione boolana) ? expVero : expFalso;
a seconda che il valore dell’espressione risulti vero o falso, viene eseguita la prima espressione
dopo il punto interrogativo (?) o quella dopo i due punti (:).
Si consideri ad esempio l’espressione seguente:
int ValoreAssoluto = (a<0) ? -a : a;
ValoreAssoluto
assumerà il valore -a se a è negativo oppure a se è positivo o nullo.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 43
Ripetizioni Materie Scientifiche
If e switch: costrutti condizionali in Java
Possiamo pensare all’esecuzione del codice Java come alla lettura di un libro, che avviene dall’alto
verso il basso. I costrutti condizionali sono delle espressioni che consentono di alterare l’usuale
modo di esecuzione di un programma e di eseguire una certa porzione di codice in base ad una
“scelta”.
I costrutti condizionali sono quindi semplicemente il modo di tradurre sotto forma di codice
algoritmi simili al seguente:
SE condizione1
codice da eseguire se condizione1 è soddisfatta
SE INVECE condizione2
codice da eseguire se condizione2 è soddisfatta
...
ALTRIMENTI
codice
da
sono soddisfatte
eseguire
se
tutte
le
precedenti
condizioni
non
In Java esistono sostanzialmente 2 costrutti condizionali, if-else (o if-then-else)e switch-case, in
questa lezione li esamineremo entrambi.
Il costrutto if in Java
Iniziamo da if-else. A volte si tende a chiamare questo costrutto condizionale if-then-else anche se
la keyword then non esiste. Lo esamineremo iniziando con il tradurre in Java l’algoritmo che
abbiamo scritto in precedenza, in questo modo:
if(condizione1) {
// ...
} else if (condizione2) {
// ...
} else {
//...
}
Fatto ciò possiamo esplorare le caratteristiche sintattiche di questo costrutto, grazie a qualche
osservazione.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 44
Ripetizioni Materie Scientifiche
Le condizioni sono espressioni di tipo boolean
Iniziamo con il dire che tutte le “condizioni” devono essere espressioni di tipo boolean (quindi
risultare in true o false).
Ecco qualche esempio:
int a = ...;
int b = ...;
// OK! Sintassi corretta!
if(a == b) {
...
}
// NO! Sintassi errata: 'a' è un 'int' e non un 'boolean'
if(a) {
}
// mentre è valida la scrittura
boolean c = ...;
if(c) {
...
}
Meglio specificare questo particolare soprattutto per chi è abituato a linguaggi come C++ o
JavaScript in cui espressioni di diversi tipi possono verificare le condizioni degli if.
Precedenza negli if multipli
Gli if sono valutati in successione e sarà eseguito solamente il primo per il quale la condizione
risultarà vera:
boolean c1 = true;
boolean c2 = true;
boolean c3 = false;
if(c1) {
// il blocco viene eseguito
} else if(c2) {
// il blocco non viene eseguito anche se 'c2' è 'true'
}
if(c3) {
// il blocco non viene eseguito perché 'c3' è 'false'
} else if(c2) {
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 45
Ripetizioni Materie Scientifiche
// il blocco viene eseguito, poiché 'c2' è 'true' mentre 'c3' è 'false'
}
‘else’ e ‘else if’ non sono obbligatori
Ci può essere un numero arbitrario di else if (il nostro “SE INVECE“), anche nessuno.
Inoltre ci può essere un solo else e non è obbligatorio che ci sia
Istruzioni e blocchi di istruzioni
Al verificarsi di una condizione il costrutto if esegue l’istruzione oppure il blocco di istruzioni che
segue. Per questo le parentesi graffe possono essere omesse nel caso che il blocco da eseguire sia
composto da una sola istruzione.
I seguenti due snippet sono equivalenti:
if(condizione)
{
System.out.println("Condizione verificata");
}
if(condizione)
System.out.println("Condizione verificata");
All’interno di un blocco si possono nidificare altri costrutti condizionali, in effetti anche if è
un’istuzione. Quindi possiamo avere casi come questo:
if(condizioneUno) {
if(condizioneDue)
System.out.println("Entrambe verificate");
else
System.out.println("Verificata solo condizioneUno");
}
In questo caso e in generale quando ci sono più condizioni annidate, può essere una buona prassi
decidere di utilizzare sempre le parentesi graffe, per non creare problemi di leggibilità e
interpretazione del codice.
switch-case
Pur essendo possibile usare if-else per costruire strutture condizionli arbitarie, Java come molti altri
linguaggi (il C ad esempio) ammette anche una secondo costrutto condizionale: switch-case.
Vediamo subito un esempio che poi commenteremo esplorando le caratteristiche peculiari di questo
costrutto in Java:
switch(c) {
case value1:
...
break;
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 46
Ripetizioni Materie Scientifiche
case value2:
...
break;
// eventuali altri case
case valueN:
...
default:
}
Tipi e valori
Prima di descrivere il funzionamento dello switch-case, soffermiamoci a sottolineare che il
parametro dello switch (che abbiamo qui indicato con c) deve essere una espressione (o variabile)
di tipo byte, short, char, int (o dei rispettivi tipi boxed) oppure Enum (Parleremo più avanti dei
tipi Enum). Dalla versione 7 di Java è contemplato anche il tipo StringTypes).
Questo significa che i valori nelle clausole case (value1, value2, …, valueN) devono essere del
medesimo tipo del parametro (‘c‘)
Il funzionamento dello switch-case
La semantica del costrutto switch-case è leggermente più complicata di quella di if-then-else e per
comprenderla rapidamente è probabilmente comodo pensare di leggere l’esempio precedente come
segue:
“dato un valore per c, procedi ad eseguire le istruzioni a partire dal case contrassegnato dallo
stesso valore”.
La parte su cui fare attenzione della frase precedente è “a partire da” in quanto nello switch-case
l’esecuzione letteralmente salta alla riga di codice etichettata con il valore corrispondente al valore
dell’argomento di switch e da quella posizione continua eseguendo tutte le istruzioni senza tener
conto di eventuali case.
Ad esempio:
int c = ...;
switch (c) {
case 1:
System.out.print("1 ");
case 2:
System.out.print("2 ");
break;
case 3:
System.out.println("3 ");
case 4:
System.out.println("4 ");
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 47
Ripetizioni Materie Scientifiche
default:
System.out.println("4+");
}
Facciamo una tabella in cui mettiamo i diversi risultati stampati sulla console al cambiare del valore
di c:
Valore di ‘c’ Stampa sulla console
3
1
3 4 4+
1 2
Nel secondo caso il comando break ne terminerà l’esecuzione (e quindi se c == 2 sarà stampato
solamente “2″).
break
Il comando break tecnicamente parlando non fa parte del costrutto switch-case ma è spesso
utilizzato in connessione ad esso e come abbiamo visto è opzionale.
Vale la pena di osservare che l’istruzione break serve genericamente a terminare l’esecuzione di
un blocco di programma (più precisamente ogni blocco associato a un ciclo iterativo o uno switch)
ed anche se il costrutto switch-case è di gran lunga il contesto in cui lo si vede utilizzato, è
sintatticamente corretto utilizzarla anche altrove.
default
La clausola default è opzionale e serve a determinare una porzione di codice che sarà comunque
eseguita quando non viene verificata nessuna clausola case
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 48
Ripetizioni Materie Scientifiche
Ciclo for e while, costrutti iterativi in Java
Se i costrutti condizionali visti nella precedente sezione rispondono all’esigenza di eseguire diverse
parti di un codice subordinatamente ad una determinata condizione, i costrutti iterativi ci
consentono di eseguire ripetutamente un determinato blocco di codice (che, al solito, potrà
essere una singola linea oppure un intero blocco racchiuso tra parentesi graffe ‘{}’).
In Java i costrutti iterativi sono sostanzialmente 3, comunemente denominati in base alle keywords
che li contraddistinguono: while, do-while e for.
while
Il ciclo while esegue una istruzione o un blocco di codice finché rimane verificata una certa
condizione. In italiano diremmo: “fino a quando la condizione è vera esegui il blocco” ecco un
esempio:
while(condizione) {
// ...
}
dove condizione deve essere una variabile o una espressione di tipo booleano. In questo caso
quindi esprimiamo la volontà di eseguire tutte le istruzioni del blocco ripetutamente fino a quando
condizione ha valore true.
Interessante capire cosa succede la prima volta che si accede al ciclo. Se condizione è false
quando l’esecuzione arriva per la prima volta allo statement while il blocco di codice non sarà
eseguito neanche una volta.
do-while
il ciclo do-while è simile al while tanto da poter essere considerato una sua variante, ed infatti il
frammento di codice:
do {
// ...
} while(condizione);
analogamente a quello sopra presentato, causa l’esecuzione del blocco tra graffe fino a quando la
condizione è vera ma con la importante differenza che in questo caso condizione viene presa in
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 49
Ripetizioni Materie Scientifiche
considerazione alla fine del blocco. Quindi l’esecuzione del blocco di istruzioni viene effettuata
almeno una volta, anche se la condizione risulta da subito false.
Continuando il parallelo con la lingua italiana questo codice andrebbe letto: “esegui il blocco di
codice e, poi, se condizione è vera fallo di nuovo, altrimenti smetti”.
for
Il ciclo for è un costrutto tra i più conosciuti, comune praticamente a tutti i linguaggi e, pur
servendo come i precedenti ad eseguire ripetutamente un blocco, fornisce una semplice sintassi per
accomodare:



una espressione di inizializzazione eseguita solo una volta prima di iniziare il ciclo;
una espressione di ‘aggiornamento’ (tipicamente un incremento) da eseguire al termine di
ogni esecuzione del blocco;
una condizione di terminazione (o uscita) dall’esecuzione iterativa.
Con un codice simile al seguente:
for(inizializzazione; condizione; incremento) {
// ...
}
si ottiene un programma che esegue esattamente una volta inizializzazione, esegue poi il blocco,
quindi effettua l’incremento, valuta condizione e, se questa risulta vera (true, come al solito
condizione deve essere di tipo boolean), esegue di nuovo blocco, alla fine del quale ripete il test e
così via.
È semplice convincersi con un esempio che un ciclo for ed uno while sono facilmente
intercambiabili:
int i=0;
while(i < 10) {
// ...
i++;
}
è identico a:
for(int i=0; i<10; i++) {
// ...
}
dove si vede anche la comune pratica di definire le variabili di iterazione ( i nell’esempio)
direttamente all’interno della sezione di inzializzazione rendendole locali al blocco (ed alle sezioni
di incremento e terminazione).
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 50
Ripetizioni Materie Scientifiche
Cicli infiniti
Si osserva che essendo inizializzazione, incremento e condizione opzionali non è strano
trovare casi in cui vengano omesse fino all’estremo:
for(;;) {
// ...
}
letto anche “for-ever” o ciclo infinito, poiché la condizione di terminazione è omessa e per default
considerata come true (il medesimo comportamento si otterrebbe naturalmente con “ while(true)
{}“).
for each
Una importante variante del ciclo for è quella che potremmo definire for-each (detta anche for-in)
che serve nel caso particolare (ma comune) in cui si voglia eseguire un determinato blocco di codice
per ogni elemento di una data collezione (o array).
Pur dovendo rimandare una completa descrizione di questa variante a quando parleremo di
Collection e array possiamo comunque mostrarne la sintassi:
for( Type item : itemCollection ) {
// ...
}
che, continuando i paralleli con la lingua italiana si legge come:
“prendi uno ad uno gli elementi della collezione itemCollection, assegna ciascuno di essi alla
variabile item ed esegui per ciascun elemento il blocco (che potrà quindi usare item al suo
interno)”.
for-each, le Collection e i tipi generics
Anche se parleremo più avanti di “Collection” e “generics”, in questa fase ci basta sapere che si
tratta di collezioni di oggetti alle quali si applicano i cosiddetti iteratori per effettuare una scansione
di tutti gli elementi.
Ecco un esempio pratico di una tipica routine di iterazione degli elementi di una Collection che
utilizzi i tipi generics:
Queue<String> queue = new LinkedList<String>();
for(Iterator<String> it = queue.iterator(); it.hasNext(); ) {
String tmp = it.next();
// ... qui fa qualcosa
}
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 51
Ripetizioni Materie Scientifiche
Senza l’ausilio dei tipi generics la cosa diventa ancora più ardua, poiché bisogna effettuare il cast
(su it.next()) con il rischio di un’eccezione a runtime. In realtà, seppure con la miglioria dei tipi
generics, questa iterazione non è pulita in quanto ci obbliga a utilizzare una struttura dati (Iterator)
che di fatto non utilizziamo.
Come abbiamo visto invece, il ciclo for-each permette una definizione automatica di tutto ciò in un
solo comando integrato:
for(String tmp:queue) {
//...
}
L’utilizzo della struttura nel secondo esempio verrà tradotto con la codifica definita nel primo,
facendoci però perdere qualsiasi riferimento all’iteratore che lavorerà dietro le quinte. Si tratta
quindi di una semplificazione che sicuramente dà dei benefici in termine di migliore codifica ma
assolutamente non intacca le prestazioni né in positivo né in negativo.
public class ForeachTest {
public static void main(String[] args) {
Collection<string> coll = new ArrayList<String>();
// utilizziamo l'array degli argomenti con for-each
// e popoliamo la collezione
for(String tmp:args){
coll.add(tmp);
}
// stampiamo la collezione
for(String tmp:coll) {
System.out.println(tmp);
}
}
}
</string>
Per semplicità lavoriamo con un array di String (l’array degli argomenti, args, del main) e una
Collection<String> dove ci metteremo il contenuto del primo array. Come si vede dal codice nel
primo caso facciamo una semplice iterazione, utilizzando l’oggetto tmp (che ha ciclo di vita
all’interno del for). Nel secondo iteriamo sulla collezione stampando il risultato.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 52
Ripetizioni Materie Scientifiche
Break e Continue
Break e continue sono comunemente detti costrutti di branching ed in Java servono a controllare
l’esecuzione di un blocco di codice in un modo sostanzialmente opposto a come operano i costrutti
iterativi e condizionali.
Questi ultimi infatti, focalizzano sull’esecuzione (eventuelmente ripetuta) di un intero blocco di
codice, mentre break e continue servono per terminare (in un senso che sarà chiarito sotto)
l’esecuzione di un blocco di codice in un ciclo o uno statement switch.
break
Lo statement break (già visto nella sua forma più semplice e comune quando abbiamo parlato di
switch-case) serve per terminare l’esecuzione di uno o più blocchi di codice. In altre parole serve
per “saltare fuori” da costrutti iterativi o switch-case.
La forma completa di break, detta labeled per la presenza di una etichetta, è la seguente:
etichetta:
loop-or-switch {
// blocco prima del break
loop-or-switch-2 {
// ...
break etichetta;
}
// blocco dopo il break
// (senza etichetta il programma salterebbe qui!)
}
// fine del blocco etichettato (il programma salta qui!)
dove:
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 53
Ripetizioni Materie Scientifiche

etichetta
è un identificatore (come definito nella sezione sulle variabili) e serve per
assegnare un nome allo statement in modo che possa essere utilizzato per specificare quale
blocco si vuole terminare al momento del break;
loop-or-switch può essere un costrutto tra for, while, do, switch

Quando in un programma viene trovato un break etichettato l’esecuzione del codice letteralmente
salta alla fine del blocco contrassegnato dall’etichetta (quindi se ci sono più blocchi innestati salta
alla fine del livello identificato dall’etichetta).
Se l’etichetta è omessa, per default, il programma salta alla fine del blocco corrente.
continue
Similmente a break lo statement continue serve per saltare alla fine di un blocco ma in questo caso
non viene terminato il ciclo ma solamente interrotta l’iterazione corrente e l’esecuzione salta
immediatamente alla valutazione della condizione di terminazione. Eventualmente poi si procederà
ad una nuova iterazione.
Naturalmente non avrebbe senso utilizzare continue con lo statement switch ed infatti è
sintatticamente proibito.
La peculiarità di continue è forse più facilmente comprensibile per mezzo dei seguenti esempi:
int sum = 0;
for(int i = 1; i < 10; i++) {
if(i%2 == 0) {
break; // il blocco che viene interrotto con break
// è quello "corrente" nel senso
// "del ciclo corrente" (il for)
}
sum++;
}
System.err.println(sum);
Questo snippet produce come output “ 1“, quindi le istruzioni dopo il break sono state eseguite
solamente una volta mentre in questo esempio in cui sostituiamo break con continue:
int sum = 0;
for(int i = 1; i < 10; i++) {
if(i % 2 == 0) {
continue;
}
sum++;
}
System.err.println(sum);
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 54
Ripetizioni Materie Scientifiche
otteniamo come output “5“. Questa volta le istruzioni dopo continue sono state eseguite per tutti i
numeri tra 1 e 10 (escluso) che non sono divisibili per 2 (1,3,5,7,9 appunto 5 volte).
Anche per continue esiste una versione labeled del tutto simile a quella vista per brerak che
consente di saltare alla fine della corrente iterazione del ciclo contrassegnato dall’etichetta.
Nonostante esistano contesti in cui break e continue sono molto comodi (per alcuni si veda anche
qui) si osserva che la leggibilità del codice viene di solito molto ridotta dal loro uso (sicuramente
fatta eccezione per l’istruzione switch in cui break è spesso chiaramente indispensabile) e quindi
prima di cimentarsi in strutture complicate da leggere (e quindi da documentare e soprattutto
mantenere) si invita a riflettere se non esista un approccio più chiaro.
I Metodi in Java
In questa lezione esaminiamo più da vicino i metodi, una parte fondamentale della programmazione
Java. Vedremo cosa sono, come è possibile definirli, identificarne la “firma” e come utilizzarli.
Cos’è un metodo
Tutti i linguaggi di programmazione forniscono la possibilità di definire, sotto un solo nome, interi
gruppi di istruzioni o insiemi di linee di codice o blocchi di espressioni (statements), che dir si
voglia. In questo modo possiamo riutilizzare un blocco di codice in molte parti del programma,
semplicemente richiamando il nome con cui l’abbiamo definito, senza dover riscrivere tutto il
blocco ogni volta.
Questi aggregati, a seconda del linguaggio, si chiamano funzioni, procedure, subroutines o
sottoprogrammi. In Java utilizziamo la logica definita dai blocchi istruzioni per rappresentare il
comportamento di classi di oggetti e questi blocchi di codice prendono il nome di metodi.
Definire un metodo in Java
La sintassi per la definizione di un metodo è molto simile a quella per la definizione di una variabile
con la quale mutua peraltro numerose caratteristiche:
[public|protected|private] [static] [final] Tipo
identificatore([Tipo1 parametro1, Tipo2 parametro2, ..., TipoN parametroN])
[throws Eccezione1, Eccezzione2, ...] {
// blocco di codice appartenente al metodo
return varTipo;
}
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 55
Ripetizioni Materie Scientifiche
Come di consuetidine tutte le parti tra parentesi quadre sono da considerarsi opzionali e, come
vedremo tra poco lo statement return è opzionale nel caso in cui Tipo (il tipo di fronte
all’identificatore) sia void.
Nella definizione di un metodo, l’identificatore è il nome assegnato al blocco di codice e che
dovrà essere utilizzato per chiamare (eseguire) il metodo; sintatticamente si applicano esattamente
le medesime considerazioni fatte per le variabili e come per esse vige la convenzione di far iniziare
i nomi dei metodi con un carattere minuscolo e proseguire con il consueto camelcase.
I parametri
La sezione [Tipo1 parametro1, ... TipoN parametroN] è detto insieme dei parametri
(formali) del metodo in cui si dichiara il tipo ed il nome simbolico delle variabili che il blocco di
codice dovrà ricevere dal programma chiamante per poter svolgere il proprio compito.
Ad esempio, per scrivere un metodo che calcoli l’area di un parallelelogramma dovremo scrivere
del codice che, noti i valori della lunghezza della base e dell’altezza esegua l’operazione base *
altezza.
La dichiarazione:
public double areaParallelogramma(double base, double altezza) {
// ...
}
ha lo scopo di avvertire il compilatore che nel corpo del metodo (tra le parentesi graffe come al
solito) i nomi base e altezza fanno riferimento ai valori di tipo double che saranno passati al
metodo quando lo utilizzeremo. Per inciso i valori che inseriamo al momento della chiamata del
metodo sono detti parametri attuali.
Quindi possiamo scrivere il metodo in questo modo:
public double areaParallelogramma(double base, double altezza) {
double a = base * altezza;
return a;
}
Richiamare il metodo
Per chiamare il nostro metodo all’interno della classe in cui lo stiamo definendo (fuori dalla classe
di definizione occorre una sintassi differente, che vedremo in seguito), sarà sufficiente scrivere
qualcosa del genere:
double areaCalcolata;
areaCalcolata = areaParallelogramma(7.0, 6.0);
Una volta eseguito questo codice la variabile areaCalcolata avrà valore 42.0.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 56
Ripetizioni Materie Scientifiche
Possiamo pensare che la macchina virtuale durante l’esecuzione del codice della chiamata al
metodo salti letteralmente all’inizio della dichiarazione del metodo, inizializzi le variabili (formali)
base ed altezza con i valori (attuali) 7.0 e 6.0, esegua quindi il corpo del metodo fino a quando
non incontri la keyword return e a quel punto prenda il valore dell’argomento di return e lo utilizzi
per assegnare il valore alla variabile areaCalcolata.
Return e il valore di ritorno del metodo
Il valore specificato accanto a return deve essere del medesimo tipo specificato nella dichiarazione
del metodo ma deve essere omesso se il metodo è stato dichiarato come void.
Traducibile come “vuoto” dichiarare un metodo void significa dire che il metodo non ritornerà
alcun valore ed in tal caso la keyword return può essere anche omessa.
Lo statement return merita una certa attenzione anche perché, pur essendo strettamente legato ai
metodi, potremmo accomunarlo a break e continue introdotti in precedenza. Infatti anche
l’esecuzione del return provoca un salto nell’esecuzione, in questo caso un salto fuori dal metodo.
Questa caratteristica risulta più chiara se immaginiamo di voler modificare il calcolo dell’area in
modo che il prodotto venga eseguito solo se i parametri sono diversi da 0:
public double areaParallelogramma(double base, double altezza) {
if(base == 0.0 || altezza == 0.0)
return 0.0; // questo return causa l'uscita dal metodo
double a = base * altezza;
return a;
}
Se uno dei due parametri è 0, viene eseguito il primo return e il controllo passa al codice
chiamante. Quindi il calcolo del prodotto viene effettuato solo nel caso in cui entrambi i parametri
siano diversi da 0.
Throws, sollevare o rilanciare le eccezioni
Della keyword throws e dei tipi che la seguono parleremo in una opportuna sezione sulle eccezioni
ma l’idea generale è che Java ci consente di sollevare delle eccezioni circa le operazioni di un
metodo e queste eccezioni possono essere poi utilizzate dal nostro codice per gestire le condizioni
di errore.
Per fissare le idee possiamo immaginare, sempre nel caso dell’area, di voler gestire il fatto che sia
base che altezza sono da considerarsi lunghezze e quindi non ha senso che possano essere
specificate come quantità negative (ma d’altro canto il tipo double non è abbaastanza espressivo
per vincolare le nostre variabili ad essere positive); scriveremmo in tal caso qualcosa del tipo:
public double areaParallelogramma(double base, double altezza)
throws IllegalArgumentException {
if(base < 0)
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 57
Ripetizioni Materie Scientifiche
throw new IllegalArgumentException("base negativa");
if(altezza < 0)
throw new IllegalArgumentException("altezza negativa");
if(base == 0.0 || altezza == 0.0)
return 0.0;
double a = base * altezza;
return a;
}
Senza dilungarci troppo, anche throw termina l’esecuzione del blocco corrente come return ma
senza che debba essere specificato un valore di ritorno.
Signature, la firma dei metodi
È importante notare che in Java un metodo è univocamente determinato (oltre che dalla classe a cui
appartiene naturalmente) dal suo identificatore e dalla lista dei tipi dei parametri che riceve in
input. Tutto questo definisce la signature del metodo (la firma) il che significa che siamo liberi di
ridefinire il medesimo metodo più volte a patto che ogni definizione abbia una lista di parametri
diversa (tipi dei parametri, i nomi non contano, non conta nemmeno il tipo di ritorno).
In questo modo possiamo sovraccaricare un identificatore di metodo con diverse definizioni ed
effettuare il cosiddetto overloading. Capita spesso quindi che un metodo abbia diversi overload.
Sarebbe quindi del tutto lecito definire, accanto al metodo che prende in input il valore della base e
dell’altezza definire, ad esempio, un metodo areaParallelogramma che prenda in input le lunghezze
di due lati incidenti e l’angolo tra di loro:
public double areaParallelogramma(double c1, double c2, double alfa)
throws IllegalArgumentException {
if(c1 < 0)
throw new IllegalArgumentException("c1 negativa");
if(c2 < 0)
throw new IllegalArgumentException("c2 negativa");
if(c1 == 0.0 || c2 == 0.0)
return 0.0;
double a = c1 * c2 * Math.sin(alpha);
return a;
}
sarà il compilatore a scegliere al momento della chiamata quale metodo utilizzare sulla base del tipo
(e del numero) degli argomenti attuali passati.
// chiama il primo
areaParallelogramma(5.0, 4.0);
// chiama il secondo
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 58
Ripetizioni Materie Scientifiche
areaParallelogramma(5.0, 4.0, 0.5);
// errore in fase di compilazione:
// non esiste un metodo che prende solo un argomento
areaParallelogramma(5.0);
Modificatori di visibilità
Resta a questo punto da parlare solamente dei qualificatori del metodo i primi (public, private e
protected) hanno il medesimo significato che avevano per le variabili e detetminano chi (quale
parte del codice) possa vedere (utilizzare) in metodo:




public significa che sarà visibile dovunque,
private solo alla classe,
protected solo dalle classi che stanno nel medesimo package di quella in cui è definito il
metodo e dalle classi da essa derivate,
infine default (come al solito identificato dalla omissione del qualificatore) implicherà
visibilità alle classi nel medesimo package.
Metodi final
Il qualificatore final serve, nel caso dei metodi, per rendere un metodo non ridefinibile dalle
sottoclassi (avremo modo di vederne esempi in futuro): se un metodo viene contrassegnato come
final le sottoclassi lo potranno utilizzare e lo erediteranno (quindi lo avranno disponibile) ma non
potranno modificarlo (o come si dice comunemente: non potranno fare override del metodo).
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 59
Ripetizioni Materie Scientifiche
Overload di metodi e variable arguments
Come abbiamo detto parlando di signature dei metodi, tra le caratteristiche note di Java possiamo
citare quella dell’overloading di metodi, che ci consente di “sovraccaricare” un metodo di una
classe con diverse varianti, in base ai parametri passati come riferimento. Dal punto di vista
dell’utilizzo della classe ciò ci consente di definire in maniera dinamica il metodo che meglio si
adatta alla nostra esigenza.
Poniamo come esempio la classe Prodotto che gestisce un prodotto e le relative caratteristiche. Un
prodotto può contenere diverse note allegate, una serie di caratteristiche che lo descrivono. Il codice
di una semplice classe con relativo costruttore sarebbe qualcosa del genere.
/**
* Esempio di una classe con un costruttore
*/
package it.html;
public class Prodotto
{
private int id;
// ...
public Prodotto(int id, String desc)
{
// ...
}
public Prodotto(int id, String desc1, String desc2)
{
// ...
}
public Prodotto(int id, String desc1, String desc2, String desc3)
{
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 60
Ripetizioni Materie Scientifiche
// ...
}
}
In questo caso l’overload dei metodi ci permette di avere una serie di costruttori in base a quante
informazioni vogliamo passare. Se pensiamo di proseguire con l’introduzione di nuovi parametri
intuiamo la limitazione di dover inserire un numero crescente di dichiarazioni.
Varargs
A partire dalla versione 1.5 di Java 1.5, abbiamo però il cosiddetto varargs (Variable Arguments),
un meccanismo per definire un numero di argomenti indefinito.
Utilizzando il modificatore … (puntini sospensivi o ellipsis) è possibile definire la presenza di un
numero variabile di argomenti. Vediamo subito la cosa applicata al nostro caso:
/**
* Esempio di utilizzo di varargs
*/
package it.html;
public class Prodotto
{
private int id;
// ...
public Prodotto(int id, String... desc)
{
// ...
}
}
Il metodo si aspetta 0 o N parametri di tipo String da utilizzare in dipendenza della logica del
metodo (o del costruttore, come in questo caso). Dal punto di vista della macchina virtuale è come
definire un metodo che si aspetta una array di String:
public Prodotto(int id, String[] desc) {
// ...
}
È molto importante ricordare questo per capire il funzionamento e utilizzare i parametri all’interno
del metodo. A questo punto ci si potrebbe chiede a che giova utilizzare i varargs: il vantaggio sta
nel poter passare parametri ai medodi in modo più sintetico:
// chiamata con varargs
metodoX(1,"uno","due","tre");
// chiamata senza varargs
metodoX(1,new String["uno","due","tre"]);
Il codice risulta evidentemente più elegante, nonché chiaro, nel primo esempio, mentre del secondo
non capiamo bene il perché sia necessaria una presenza di un array. Ecco un semplice programma
per testare il funzionamento del costrutto:
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 61
Ripetizioni Materie Scientifiche
/**
* Test del varargs
*/
public class VarargsTest {
public void testVarargs(String... list) {
System.out.println("Metodo 1");
for (String tmp:list)
{
System.out.println("#" + tmp);
}
}
public static void main(String[] args)
{
System.out.println("Test varargs");
VarargsTest va=new VarargsTest();
va.testVarargs("do","re","mi","fa","sol","la","si");
va.testVarargs("1","2","3","4");
}
}
Come si vede dal corpo del metodo utilizziamo il costrutto for-each e di fatto utilizziamo il
parametro list come un array.
Per verificare che alla fine la struttura dati utilizzata è proprio l’array, inseriamo e mandiamo in
esecuzione la seguente riga di codice:
va.testVarargs(new String[]{"do","re","mi","fa","sol","la","si"});
Il risultato sarà lo stesso di prima. Unica cosa a cui bisogna fare attenzione è il caso in cui
l’overload possa causare ambiguità. Proviamo ad aggiungere il seguente metodo alla classe:
/**
* Test di ambiguità sul varargs
*/
public void testVarargs(String arg1,String... list) {
// ...
}
Il compilatore procederà correttamente ma ci darà un errore al momento di utilizzare il metodo, in
quanto si trova nell’impossibilità di segliere il metodo corretto (non avendo indicazione su quale
scegliere). Infatti, in questo caso, non possiamo sapere se la prima stringa fa parte della lista di
parametri o è un parametro a sé stante.
In queste situazioni, utilizzare un array potrebbe essere un modo per risolvere.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 62
Ripetizioni Materie Scientifiche
Metodi statici (static) e metodi di istanza
Tra tutti i qualificatori utilizzabili nella dichiarazione di un metodo quello che più di ogni altro ne
modifica il funzionamento (nel senso che chiariremo tra poco) è static.
Per comprendere l’uso della keyword static in Java bisogna ricordare innanzi tutto che, come
detto, ogni metodo appartiene ad una classe ed una classe è, in qualche modo, un “pacchetto” di dati
e metodi.
Sappiamo che da una classe possiamo ottenere molteplici istanze e per ciascuna istanza si hanno
variabili dai nomi identici ma dai valori distinti (forse “che puntano a locazioni di memoria diverse”
sarebbe una definizione più chiara). Se poi vogliamo che una variabile sia la medesima per tutte le
istanze di una classe sappiamo che la dobbiamo invece definire come static.
Per i metodi avviene sostanzialmente la medesima cosa: possiamo pensare che dei metodi definiti in
una classe ne esista normalmente (cioè se non si specifica static) una “copia” per ogni istanza
della classe, mentre dei metodi statici ne esista una sola copia associata alla classe stessa. Per
scendere più in dettaglio:


i metodi non statici sono associati ad ogni singola istanza di una classe e perciò il loro
contesto di esecuzione (quindi l’insieme delle variabili cui possono accedere) è relativo
all’istanza stessa: possono accedere e modificare le variabili dell’istanza e modificarne lo
stato;
in contapposizione i metodi statici non sono associati ad una istanza ma solo ad una classe.
Quindi non potranno interagire con le variabili di istanza, ma solamente con quelle statiche.
Questa distinzione tra metodi statici e metodi di istanza si riflette anche in una diversa sintassi che
si deve utilizzare per eseguire i 2 tipi di metodi:
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 63
Ripetizioni Materie Scientifiche
Tipo di metodo
Sintassi
NomeClasse.nomeMetodo(...)
Statico
Non statico (di istanza) nomeIstanza.nomeMetodo(...)
Per NomeClasse si intende il nome di una classe e non di una istanza (come si potrebbe intuire
anche dalla convenzione per cui le istanze non iniziano mai con una lettera maiuscola).
Per la precisione anche i metodi statici possono essere richiamati utilizzando una istanza invece che
il nome della classe, ma questa è considerata una cattiva pratica e segnalata dal compilatore con un
warning.
Cerchiamo di chiarire ulteriormente la differenza tra i diversi tipi di metodi utilizzando il codice
seguente in cui si presenta la struttura di base di una micro-libreria per la rappresentazione di figure
piane (solo i parallelogrammi sono definiti naturalmente).
Innanzi tutto definiamo una classe di metodi di servizio utilizzabili dovunque e non legati ad una
specifica istanza, quindi tutti statici:
public class Geometry2DUtils {
public static final double PiGreco = 3.141592;
public static double areaParallelogramma(double base, double altezza) { }
public static double areaParallelogramma(double c1, double c2, double alpha) {
}
// ...
public static double areaCerchio(double r) {
// anche se static potrà usare la variabile statica PiGreco
return PiGreco * r * r;
}
}
continuiamo poi con una classe che rappresenti i parallelogrammi
public class Parallelogramma {
private double base, altezza;
public Parallelogramma(double base, double altezza) {
// inzializza le variabili di istanza
this.base = base;
this.altezza = altezza;
}
// questo metodo area non ha bisogno dei
// parametri in quanto utilizza le variabili della classe
public double area() {
// utilizza il metodo (pubblico e statico) nella classe di utility
// chiamandolo usando il nome della classe (poi il compilatore
// sceglie quello giusto sulla base dei parametri)
return Geometry2DUtils.areaParallelogramma(base, altezza);
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 64
Ripetizioni Materie Scientifiche
}
// metodi per l'accesso (getters) delle variabili private della classe,
// garantiscono, tra l'altro, che i field possano essere utilizzati ma non
// modificati
public double getBase()
{ return base; }
public double getAltezza() { return altezza; }
}
A questo punto potremo utilizzare le nostre classi in un immaginario metodo main (static)
public class Programma {
public static void main(String[] args) {
// creiamo una istanza della classe Parallelogramma
// e la assegnamo alla variabile p (che ha il tipo opportuno)
Parallelogramma p = new Parallelogramma(4.0, 8.0);
// creiamo una seconda istanza di Parallelogramma che avrà
// metodi e variabili con i medesimi nomi di p ma distinti
// quanto a valore e contesto di esecuzione
Parallelogramma p1 = new Parallelogramma(11.0, 18.0);
double pArea = p.area(); // assegna a pArea 4*8
double p1Area = p1.area(); // assegna a p1Area 11*18
// il medesimo risulato di pArea si avrebbe naturalmente con
double
spArea
=
Geometry2DUtils.areaParallelogramma(p.getBase(),
p.getAltezza());
// ma
effattuata.
con
una
minore
chiarezza
circa
l'oggetto
dell'operazione
//...
}
}
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 65
Ripetizioni Materie Scientifiche
Array in Java
che permette di gestire una sequenza di lunghezza fissa di elementi tutti del medesimo tipo. Il
numero di elementi in un array, detto lunghezza dell’array, deve essere dichiarato al momento della
sua allocazione e non può essere cambiato.
Dichiarare un array in Java
La sintassi per la dichiarazione di una variabile di tipo array è la seguente:
Tipo[] nome;
nella quale si deve osservare l’uso delle parentesi quadre ‘ []‘ tra il tipo ed il nome della variabile
(anche se la documentazione riporta sempre questa forma della dichiarazione è ammissibile anche
postporre le parentesi quadre dopo l’identificatore:
Tipo nome[];
Tipo può
essere sia un tipo primitivo di java, sia il nome di una classe.
Allocare l’array
Per default le variabili di tipo array sono inizializzate con il valore null. Quindi, prima di poterle
usare, dovremo inizializzarle allocando per esse la memoria per mezzo della consueta keyword
‘new’:
nome = new Tipo[n];
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 66
Ripetizioni Materie Scientifiche
che riserva (o ‘allocà) la memoria necessaria per contenere n elementi di tipo Tipo.
Tutti gli elementi dell’array sono inizializzati con il valore di default previsto dal tipo che abbiamo
indicato. Ad esempio, se Tipo è una classe tutti gli elementi verranno inizializzati a null e quindi
andranno a loro volta allocati.
Usare gli array
Una volta creato l’array, possiamo accedere ai singoli elementi indicandone la posizione (detta
indice) all’interno contentitore, grazie all’operatore ‘ []‘ (parentesi quadre). Ad esempio:
nome[3]; // indica il quarto elemento della collezione
Bisogna ricordare che gli elementi sono numerati a partire da zero, quindi nome[0] farà riferimento
al primo elemento, nome[1] al secondo e così via fino all’ultimo indicato da nome[lunghezza-1].
Come detto sopra la lunghezza di un array è fissata in fase di creazione e si può in qualsiasi
momento accedere al suo valore utilizzando la proprietà length che è presente in ogni array java
con la sintassi: nome.length.
Gli array sono spesso utilizzati per mantenere informazioni statiche all’interno di un programma; ad
esempio per determinare quanti giorni abbia un mese dato il suo numero potremmo pensare di
scrivere un metodo del tipo:
int giorniMese(int numeroMese) {
return numeroGiorniPerMese[numeroMese -1];
}
che per funzionare ha bisogno di un array che contenga, mese per mese, il numero di giorni. Quindi:
int[] numeroGiorniPerMese
dell'array;
=
new int[12];
//
dichiarazione
e
allocazione
giorniPerMese[0] = 31; // gennaio, primo mese, posizione 0
giorniPerMese[1] = 28; // febbraio (non funziona per i bisestili ma non ci
interessa)
// etc ...
giorniPerMese[12] = 31; // dicembre
Inizializzare array con liste (literals)
L’inizializzazione degli elementi dell’array è prolissa, poco leggibile e decisamente scomoda da
collocare nel codice (soprattuto se vogliamo dichiarare numeroGiorniPerMese come static,
provate per fare pratica !!). Ma Java ammette una sintassi per allocare ed inizializzare gli array in
modo più diretto:
int [] numeroGiorniPerMese = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
Questa modalità prevede che i valori degli elementi dell’array possano essere elencati in una lista
racchiusa tra parentesi graffe e separati da virgole. Naturalmente tutti i valori della lista devono
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 67
Ripetizioni Materie Scientifiche
essere del tipo specificato per l’array (o ad esso assegnabile, ma questa affermazione risulterà
probabilmente più chiara quando avremo preso confidenza con con il concetto di derivazione di una
classe da un’altra).
Errori con gli array
Quando utilizziamo gli array è nostra responsabilità non tentare di accedere ad elementi esterni al
range definito. Ad esempio:
int l = 5;
int [] a = new int[l];
a[9] = 10;
Quando mandiamo in esecuzione di questo pezzo di codice, esso genera un errore di runtime (non
di compilazione): la JVM, quando chiediamo di accedere al decimo elemento dell’array, emette una
eccezione di tipo ArrayIndexOutOfBoundsException.
Array multidimensionali
Java permette anche l’utilizzo di array di array (detti anche array multimensionali) di profondità
arbitraria (o con numero di dimensioni arbitrario) e la sintassi per la loro dichiarazione ed
allocazione si ottiene semplicemente ripetendo le parentesi quadre tante volte quante il numero di
dimensioni, per esempio:
int [][][] arrayConDimensione3 = new int[4][5][6];
Analogamente a quanto detto per gli array unidimensionali, Java prevede una sintassi speciale per
l’inizializzazione degli array multidimensionali:
float[][] mat
{
{
{
};
= new
1, 2,
4, 5,
7, 8,
int[][] = {
3 },
6 },
9, 10, 11 }
L’ultima riga mostra anche come sia possiblie creare array multidimensionali ‘ragged‘, cioè nei
quali i sotto-array non abbiano tutti la medesima lunghezza.
È possibile creare array ragged anche senza inizializzarli immediatamente, ad esempio con la
sintassi che segue:
int [][] ar = new int[10][];
for(int i =0; i< 10; i++) {
ar[i] = new int[(i%3) +1];
}
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 68
Ripetizioni Materie Scientifiche
che risulta chiara se si pone attenzione al fatto che dato un array n-dimensionale possiamo sempre
ottenerne una sezione k dimensionale (con k < n) semplicemente specificando le parentesi quadre
solo per le n-k dimensioni che vogliamo fissare ed omettendole per quelle che si vogliono lasciare
libere:
int [][][] cubo = new int[10][10][10];
int[][] quadrato1 = cubo[0];
int[][] quadrato2 = cubo[1];
int[] segmento = quadrato1[0];
int i = segmento[4];
int j = quadrato2[3][5];
int k = cubo[3][5][7];
java.lang.Arrays
Gli array sono un costrutto classico di praticamente ogni linguaggio di programmazione e,
nonostante il limite notevole di non poter cambiare dimensione (size) dopo la creazione (e qualche
complicatezza sintattica degli array multidimensionali), il loro utilizzo è estremamente comune in
molti ambiti. Perciò Java mette a disposizione la classe java.lang.Arrays con numerosi algoritmi
per operare sugli array:



ricerca;
sorting (ordinamento);
copia;
e altri strumenti per la manipolazione, tutti sotto forma di metodi statici.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 69
Ripetizioni Materie Scientifiche
Stringhe in Java
Testi, messaggi e codici sono solo alcune delle applicazioni che hanno le stringhe in
programmazione. In Java esse sono rappresentate come sequenze di caratteri unicode (UTF-16) e
possiamo crearle e manipolarle grazie alla classe String, messa a disposizione nel core di Java
(java.lang.String).
Vediamo quindi come dichiarare le stringhe ed effettuare su di esse le operazioni più classiche.
Definire una stringa in Java
Il modo più semplice e diretto per creare un oggetto di tipo String è assegnare alla variabile un
insieme di caratteri racchiusi fra virgolette:
String titolo = "Lezione sulle stringhe";
questo è possibile in quanto il compilatore crea una variabile di tipo String ogni volta che incontra
una sequenza racchiusa fra doppi apici; nell’esempio la stringa "Lezione sulle stringhe" viene
trasformata in un oggetto String e assegnato alla variabile titolo. Questa forma di inizializzazione
è detta string literal
Oltre alla modalità “literal”, poiché si tratta comunque di oggetti, le variabili di tipo String possono
essere inizializzate anche utilizzando la keyword new e un costruttore come si vede nell’esempio
seguente:
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 70
Ripetizioni Materie Scientifiche
public void initString() {
// Inizializzazione con una new
String titolo = new String("Titolo dell'opera");
// Inizializzazione che fa uso di un array di caratteri
char[] arraySottotitolo = {'s','o','t','t','o','t','i','t','o','l','o','!'};
String sottotitolo = new String(arraySottotitolo);
}
Dalla versione 8 di Java, String conta ben 13 costruttori (oltre a 2 deprecati ed una decina di metodi
statici che in qualche modo creano istanze di tipo stringa a partire da altri tipi di variabili).
Length, la lunghezza di una stringa
La classe String espone anche numerosi metodi per l’accesso alle proprietà della stringa sulla quale
stiamo lavorando; uno di questi è il metodo length(), che ritorna il numero di caratteri contenuti
nell’oggetto. La sua signature è:
int length()
Per esempio le seguenti linee di codice:
public void printLength() {
String descrizione = "Articolo sulle stringhe ...";
int length = descrizione.length();
System.out.println("Lunghezza: "+length);
}
stampano come risultato:
Lunghezza: 27
In questo esempio è interessante notare anche come il compilatore interpreti automaticamente
l’espressione "Lunghezza: "+length, creando un oggetto di tipo String ottenuto concatenando la
stringa che troviamo in prima posizione e la stringa ottenuta dalla rappresentazione decimale del
valore di length (variabile di tipo int).
In sostanza viene svolta per noi una conversione di tipo senza la quale avremmo dovuto scrivere
qualcosa di questo genere:
"Lunghezza: " + String.valueOf(length);
Il metodo (statico) valueOf di String, restituisce la rappresentazione testuale del parametro di tipo
int che riceve in ingresso.
Stringa = array di caratteri
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 71
Ripetizioni Materie Scientifiche
Possiamo pensare a una stringa esattamente come a un array di caratteri, questo significa che
possiamo considerare i singoli caratteri come elementi di array. Consideriamo questa stringa:
String str = "Ciao HTML.it";
Il carattere 'C' è alla posizione 0, il carattere 'i' è alla posizione 1, … il carattere 't' finale è alla
posizione 11, che coincide con str.length()-1.
Per accedere ai singoli caratteri non possiamo usare l’operatore ‘[]‘ come negli array, ma
possiamo sfruttare il metodo charAt:
char charAt(int index);
utf-16
Se vogliamo utilizzare le stringhe come array dovremo porre la massima attenzione alla
rappresentazione unicode/utf-16 che prevede che i cosiddetti “supplementary characters” occupino
2 posizioni nell’array; magari questo non ha molta rilevanza con il nostro sistema locale ma le cose
potrebbero diventare complicate.
Concatenare le stringhe
L’operazione di concatenazione di stringhe può essere effettuata in modi diversi. La classe String
fornisce il metodo concat per la concatenazione di stringhe la cui signature è:
String concat(String str);
Quindi:
String str1 = new String("Nome ");
String str2 = new String("Cognome ");
String str3 = str1.concat(str2);
assegna a str3 una nuova stringa formata da str1 con str2 aggiunto alla fine; insomma "Nome
Cognome".
Avremmo potuto ottenere la stessa cosa in modopiù semplice utilizzando l’operatore ‘ +‘:
String str1 = "Nome";
String str2 = "Cognome";
String str3 = str1+str2;
Oppure avremmo potuto costruire la stringa concatenata direttamente tramite literals:
String str3 = "Nome"+"Cognome";
Substring, estrarre una sottostringa
Per prelevare e manipolare solo una porzione di una stringa possiamo utilizzare il metodo
substring, presente in 2 forme (overloaded):
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 72
Ripetizioni Materie Scientifiche
String substring(int beginIndex);
String substring(int beginIndex, int endIndex);
La prima ritorna una stringa (sottostringa di quella di partenza) a partire dall’indice specificato fino
alla fine della stringa; la seconda invece, ritorna una stringa che è anch’essa sottostringa di quella di
partenza, ma che parte dall’indice beginIndex e termina in endIndex. Per esempio:
String
String
String
String
titolo = "I promessi Sposi";
a = titolo.substring(2);
// a vale "promessi Sposi"
b = titolo.substring(12); // b vale "Sposi"
c = titolo.substring(2,9); // c vale "promessi"
Nota: sia l’operazione di concatenamento sia quella di estrazione di una sottostringa (e tutti i
metodi che operano sulle stringhe per la verità), sono caratterizzati dal fatto di non modificare la
stringa su cui vengono applicate ma di ritornarne una nuova. Ad esempio titolo.substring(12)
non modifica titolo ma ritorna una nuova variabile di tipo String che contiene la sottostringa
"Sposi";
Stringhe, oggetti “immutabili”
Anche se cercassimo con attenzione non troveremmo come fare l’operazione di ‘estrazione’
direttamente su una stringa: in Java le stringhe sono oggetti immutabili, cioè il loro valore non
può essere cambiato dopo la loro creazione (come gli array non possono cambiare lunghezza per
fare un parallelo).
L’immutabilità dell’oggetto String deve sempre essere tenuta presente ogni volta le si manipolano,
non è infatti infrequente cadere in errori come questo:
String messaggio = "Ciao XX";
messaggio.replace("XX", "Mondo");
System.out.println(messaggio);
nel quale semplicemente il risultato dell’operazione di sostituzione è non utilizzato. Possiamo
comunque assegnare il nuovo oggetto literal allo stesso riferimento:
messaggio = messaggio("XX", "Mondo");
Ma questo significa abbandonare l’oggetto precedente. In altre parole avremo nella memoria il
nuovo oggetto "Ciao Mondo" puntato dalla variabile messaggio e l’oggetto "Ciao XX"
abbandonato a se stesso senza riferimenti.
Per modificare il contenuto di una stringa di caratteri è consigliabile utilizzare le classi
StringBuffer o StringBuilder che, al contrario di String, possono essere modificati senza lasciare
oggetti inutilizzati e secondo i casi possono risultare quindi assai piu’ preformanti (e comodi).
I metodi per manipolare le stringhe
Oltre al replace, la classe String mette a disposizione molti altri metodi per manipolare le
stringhe, esaminiamone alcuni:
Metodo
Descrizione
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 73
Ripetizioni Materie Scientifiche
boolean contains(CharSequence s)
boolean equals(Object anObj)
boolean isEmpty()
String[] split(String regex)
String trim()
ritorna true se e solo se la stringa contiene la sequenza di
caratteri specificati dal parametro s
confronta la stringa con l’oggetto obj specificato
ritorna true se e solo se la lunghezza della stringa è 0
suddivide la stringa intorno ad ogni occorrenza con
l’espressione regex e ritorna array con tutte le
sottostringhe
ritorna una copia della stringa di partenza eliminando tutti
gli spazi bianchi all’inizio e alla fine della stringa
Enum, gestire le enumerazioni
A partire dalla versione 5, in Java è stato introdotto uno speciale tipo chiamato Enum o
Enumerated Type che, almeno in prima approssimazione, può essere semplicemente pensato come
un modo per vincolare una variabile a poter assumere solo un determinato (dallo sviluppatore) set di
valori.
Se immaginiamo di scrivere un programma che gestisca un calendario probabilmente avremo
bisogno di un modo per identificare i giorni della settimana; nulla vieta di definire, ad esempio, la
variabile giornoDellaSettimana come int (byte basterebbe ma siamo a fare un esempio) e tenere a
mente che 0 corrisponde a Lunedì, 1 a Martedì e così via.
Così facendo giornoDellaSettimana potrà certamente avere tutti i valori desiderati:
int giornoDellaSettimana = 4; // VEN per la nostra convenzione
e con quache costante (static final) potremmo anche rendere leggibile il codice:
public static final int LUN = 0;
// ...
public static final int VEN = 4;
int giornoDellaSettimana = VEN;
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 74
Ripetizioni Materie Scientifiche
ma purtroppo, per il compilatore, giornoDellaSettimana è e resta una variabile int e quindi non
potrà mai verificare che non ci sia mai nel nostro codice una linea in cui viene assegnato il valore 9
(o -4 essendo gli interi signed), facendoci perdere una delle più preziose comodità di un linguaggio
staticamente tipato come Java: il type checking al momento della compilazione.
D’altro canto, se invece di cercare di usare (male) un int, oppure un altro qualsiasi tipo non pensato
per rappresentare un giorno della settimana, definiamo:
public enum Giorno {
LUNEDI,
MARTEDI,
MERCOLEDI,
GIOVEDI,
VENERDI,
SABATO,
DOMENICA // opzionalmente può terminare con ";"
}
e poi
Giorno giornoDellaSettimana;
avremo una variabile che potrà contenere solamente un valore appartenente al set specificato nella
definizione dell’enum Giorno e contemporaneamente avremo a disposizione anche i nomi simbolici
(le costanti di prima) da usare nella scrittura del programma:
giornoDellaSettimana = Giorno.VENERDI;
Ricordando la lezione sui costrutti condizionali, è interessante poter usare enumerazioni come
quelle definite dal tipo Giorno anche con lo statement switch.
/** EnumTest.java*/
public class EnumTest {
public enum Giorno { LUNEDI, MARTEDI, MERCOLEDI, GIOVEDI, VENERDI, SABATO,
DOMENICA };
public static void main(String[] args) {
// scegliamo un valore
Giorno giornoDellaSettimana = Giorno.GIOVEDI;
// definiamo una logica
switch(giornoDellaSettimana){
case LUNEDI:
System.out.println("Oggi
break;
case MARTEDI:
System.out.println("Oggi
break;
case MERCOLEDI:
System.out.println("Oggi
break;
case GIOVEDI:
System.out.println("Oggi
break;
è Lunedì");
è Martedì");
è Mercoledì");
è Giovedì");
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 75
Ripetizioni Materie Scientifiche
case VENERDI:
System.out.println("Oggi è Venerdì");
break;
case SABATO:
System.out.println("Oggi è Sabato");
break;
case DOMENICA:
System.out.println("Oggi è Domenica");
break;
}
}
}
Il risultato sarà:
Oggi è Giovedì
Le caratteristiche della classe enum
Tecnicamente parlando in Java una enum è una classe come le altre ma che “implicitamente” (cioè
senza che lo scriviamo noi) estende sempre la classe java.lang.Enum, cosa che ha l’unico
inconveniente di rendere impossibile di scrivere enum che derivino da altri tipi.
Il trattamento speciale che Java riserva agli enum riserva anche qualche interessante sorpresa: il
compilatore per ogni classe enum sintetizza per noi un metodo statico (values) che ritorna un array
di tutti i possibili valori che potranno assumere le variabili cha varanno come tipo l’enum, quindi
nel nostro esempio il frammento di codice:
for( Giorno d : Giorno.values() ) {
System.err.println(d);
}
stamperebbe tutti i nomi dei giorni della settimana (anche i nomi vengono convertiti
automaticamente in stringhe, con il medesimo case).
Analogamente il compilatore ci offre la possibilità di convertire stringhe in valori del nostro
enum:
Giorno g = Giorno.valueOf("SABATO");
assegnerà alla variabile g il valore Giorno.SABATO (attenzione, la stinga deve avere il medesimo
case e non può contenere spazi, infatti l’esecuzione (non la compilazione) di:
Giorno g = Giorno.valueOf("Sabato");
genererebbe un errore di tipo “java.lang.IllegalArgumentException”.
In generale infine, il fatto che le enum siano a tutti gli effetti classi, apre la possibilità di aggiungere
dentro di essi metodi e field e, e questo è più sorprendente, anche costruttori.
Esaminimo un pò di sintassi. Se la definizione dell’enum contiene metodi e/o field:
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 76
Ripetizioni Materie Scientifiche




la lista dei field deve essere terminata da ‘ ;‘ (che altrimenti è ammesso ma non
obbligatorio).
Gli eventuali costruttori devono essere privati (o package private) e non possono essere
chiamati esplicitamente, sono unicamente a disposizione del compilatore.
La lista dei valori ammissibili nel caso in cui siano definiti dei costruttori non deve essere
considerata, come abbiamo fatto fino ad ora, come una lista di etichette ma come una forma
compatta per istruire il compilatore a costruire determinate istanze della classe ed assegnare
loro un nome simbolico.
Non ci sono restrizioni circa i metodi e i field che possono essere inclusi nel corpo di un
enum.
Enumerazioni: creare oggetti specifici
Oltre alla sintassi, che tutto sommato non presenta particolari aspetti interessanti, circa la versatilità
delle enum vale la pena di provare ad immaginarne un contesto d’uso.
Immaginiamo di dover gestire in un programa gli elementi chimici (fare riferimento ad esempio alla
tavola periodica degli elementi) potremmo probabilmente costruire un enum (grosso, sono oltre
100) che li contenga tutti.
Poiché il numero di elementi è finito e prefissato, un enum potrebbe essere una scelta opportuna ma
ci dovremmo di sicuro preoccupare di associare ad ogni elemento alcune informazioni aggiuntive,
diciamo numero atomico e massa atomica per fissare le idee, e predisporre opportuni metodi per
accederli, l’enum dovrebbe quindi contenere qualcosa del tipo:
private int numeroAtomico;
private double massaAtomica;
private String simbolo;
public int getNumeroAtomico() {
return numeroAtomico;
}
// ... altri getter/setter
e un costruttore per permettere (costringere sarebbe il verbo giusto) l’inizializzazione dei field,
quindi:
private Elemento(String simbolo, int numeroAtomico, double massaAtomica) {
this.simbolo = simbolo;
this.numeroAtomico = numeroAtomico;
this.massaAtomica = massaAtomica;
}
Se a questo punto tentassimo di definire i valori dell’enum come abbiamo fatto per i giorni
public enum Elemento {
IDROGENO,
ELIO,
// ... ;
}
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 77
Ripetizioni Materie Scientifiche
Otterremmo un errore di compilazione che ci avviserebbe che il costruttore default (quello senza
argomenti) della classe Elementi non esiste. Infatti il costruttore lo abbiamo definito noi e prevede
che vangano specificati alcuni parametri e così siamo obbligati a costruire i nostri valori nell’enum,
con il costruttore che abbiamo fornito, la definizione giusta sarà quindi:
/** Elemento.java */
public enum Elemento {
IDROGENO("H", 1, 1.008),
ELIO("He", 2, 4.003),
// ... altri elementi
LITIO("Li", 3, 6.491);
private int numeroAtomico;
private double massaAtomica;
private String simbolo;
public int getNumeroAtomico() {
return numeroAtomico;
}
public String getSimbolo() {
return simbolo;
}
private Elemento(String simbolo, int numeroAtomico, double massaAtomica)
{
this.simbolo = simbolo;
this.numeroAtomico = numeroAtomico;
this.massaAtomica = massaAtomica;
}
}
Copiamo questa definizione in un file Elemento.java e la utilizziamo per una prova su strada
all’interno di un “main”:
/** main.java */
public class main {
public static void main(String[] args) {
for( Elemento e : Elemento.values() ) {
System.out.printf("%s\t|\t%d|\t%s\n",
e.getNumeroAtomico(), e);
}
}
}
e.getSimbolo(),
Lanciamo il programma e otteniamo:
H
He
Li
|
|
|
1|
2|
3|
IDROGENO
ELIO
LITIO
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 78
Ripetizioni Materie Scientifiche
Le variabili di tipo Elemento per costruzione, saranno sempre elementi validi e con le proprietà che
ci interessano sempre inizializzate e disponibili.
Enumerazioni: sintesi delle caratteristiche
Enum risolve quindi un’esigenza reale, quella di definire un insieme di valori predefiniti, senza
ricorrere alla mediazione di costanti intere, con la possibilità di avere una classe. Come abbiamo
visto anche le enumerazioni possono essere utilizzate nei cicli for-each (for-in) e nei costrutti
switch. Il metodo toString(), di default, è uguale al nome assegnato alla variabile, ma vedremo
come poterlo modificare.
Ecco una breve lista delle caratteristiche delle enumerazioni che ci aiuta a comprenderne la logica
per utilizzarle al meglio:





Una enumerazione è una classe, in particolare l’estensione della classe java.lang.Enum,
quindi come tale ha tutte le attenzioni sul controllo dei tipi in fase di compilazione.
I tipi definiti in una enumerazione sono istanze di classe, non tipi interi.
I valori di una enumerazione sono public final static, quindi immutabili.
Il metodo == è sovrascritto, quindi può essere usato in maniera intercambiabile al metodo
equals.
Esiste la coppia di metodi valueOf()/toString() che possono essere sovrascritti.
Enum, utilizzo avanzato
Possiamo finire approfondendo alcuni aspetti avanzati che possono aiutarci nella produzione di
codice. Lo facciamo con un esempio di enumerazione che rappresenti un player di tracce audio
prese da un archivio. Vogliamo che il player sappia riconoscere tre formati diversi (mp3, pcm e
dolby digital) e che ognuno di essi concretamente sia riprodotto in base alle proprie specifiche.
Creiamo quindi il tipo enum Audio e il relativo file (Audio.java) e lo definiamo con la stessa
sintassi che utilizziamo per una classe (alla fine sempre di una classe si tratta), quindi: variabili di
istanza, costruttori e metodi. In particolare definiamo un metodo astratto reproduce (questa parte
si capirà meglio dopo aver parlato della programmazione orientata agli oggetti).
/** Audio.java*/
public enum Audio {
// Lasciamo lo spazio per gli elementi dell'enumerazione
private final String channel;
private final int bitrate;
Audio(String channel,int bitrate) {
this.channel=channel;
this.bitrate=bitrate;
}
Audio(String channel) {
this.channel=channel;
bitrate = -1;
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 79
Ripetizioni Materie Scientifiche
}
public abstract String reproduce(String archive);
// getter e setter
public String getChannel() {
return channel;
}
public int getBitrate() {
return bitrate;
}
}
Per realizzare quanto detto sopra, ogni tipo concreto creato (ogni definizione) dovrà implentare
concretamente il metodo reproduce ed eventualmente potrà definire una sua propria logica nel suo
corpo di classe:
MP3("mp3",128) {
@Override
public String reproduce(String archive) {
return archive+" > file MP3";
}
},
PCM("PCM"){
@Override
public String reproduce(String archive) {
return archive+" > file PCM";
}
},
DD("Dolby Digital",256){
@Override
public String reproduce(String archive){
return archive+" > file Dolby Digital";
}
@Override
public String toString(){
//Override del metodo toString()
return "Dolby";
}
};
// qui seguono le definizioni della classe
private final String channel;
private final int bitrate;
Audio(String channel,int bitrate) {
// Etc...
Gli elementi, così definiti, li inseriamo nella prima parte della dichiarazione dell’enum.
Esaminando gli elementi vediamo che abbiamo creato ogni singolo tipo concreto sfruttando i
costruttori (l’uno o l’altro, badate bene), sempre però identificando il tipo con un identificativo
(MP3, PCM, DD). Ognuno dei metodi implementa concretamente il player scelto (qui simulato con
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 80
Ripetizioni Materie Scientifiche
il ritorno di una stringa particolare), inoltre l’ultimo player effettua l’override del metodo
toString().
Vediamo un main di esempio e l’utilizzo dell’enumerazione appena creata:
public class main {
public static void main(final String[] args) {
for( Audio a : Audio.values() ) {
System.out.printf("%s\t| %d\t|
a.getChannel(), a.reproduce("myFile"));
}
}
}
%s\t
|
%s\n",
a,
a.getBitrate(),
Ecco il risultato:
MP3
| 128 | mp3
| myFile > file MP3
PCM
| -1 | PCM
| myFile > file PCM
Dolby | 256 | Dolby Digital | myFile > file Dolby Digital
Qui c’è da notare che, per default, il toString() del tipo enum è l’identificativo assegnato ( MP3,
PCM) mentre, laddove abbiamo effettuato l’override, ovviamente, è il valore che noi abbiamo
assegnato (Dolby e non DD).
Principi di OOP
Nelle sezioni precedenti, esaminando alcune caratteristiche di Java, abbiamo incontrato alcuni
concetti di OOP ed alcuni dei suoi elementi cardine come, ad esempio, la definizione di classe e la
distinzione tra classe e istanza.
Tuttavia per avere la nozione di programmazione orientata agli oggetti comprende qualcosa in più,
che prescinde dal linguaggio di programmazione specifico e riguarda in generale lo schema mentale
da assumere nell’affrontare la modellazione, l’analisi e l’implementazione della soluzione di un
problema.
«The fact that you know Java doesn’t mean that you have the ability to transform that knowledge
into
well-designed
object
oriented
systems.»
Developing Applications with Java and UML – Paul R. Reed
Questa citazione ci ricorda che la padronanza della sintassi di un linguaggio di programmazione
orientato agli oggetti, anche fosse perfetta, non basta per organizzare e strutturare sistemi e
programmi che rispecchino il paradigma OOP. La programmazione orientata agli oggetti infatti ha
una sua complessità che richiede sia la conoscenza di nozioni specifiche (alcune delle quali
squisitamente teoriche), sia un opportuno metodo ed una disciplina.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 81
Ripetizioni Materie Scientifiche
In questa e nelle prossime lezioni esaminiamo alcuni concetti fondamentali della OOP in Java,
passando per la notazione UML e arrivando a parlare di OOD (Object Oriented Design).
Design a oggetti vs Structured-design
Già dalla fine degli anni ’90, quando gran parte della teoria OO è stata messa a punto, è emerso
come analisi e design orientati agli oggetti fossero fondamentalmente differenti dalla tradizionale
metodologia del design strutturato.
Il focus sugli oggetti ha bisogno di un diverso approccio alla decomposizione dei problemi e non
deve sorprendere quindi che produca architetture software molto dissimili da quelle realizzate per
mezzo dello structured-design:


Con l’approccio strutturale ci si concentra sulla scomposizione di algoritmi in procedure.
Nell’approccio “a oggetti” ci si focalizza sull’interazione di elementi (oggetti) che
comunicano (scambiano messaggi) tra loro.
La OOP consente la descrizione di dinamiche complesse e la realizzazione di “procedure” più
facilmente implementabili (e mantenibili) favorendo le interazioni tra oggetti sostanzialmente
semplici.
La complessità dei problemi non deve essere rappresentata spesso da oggetti complessi ma da
interazioni complesse tra oggetti semplici.
Cerchiamo di comprendere quindi cos’è la programmazione orientata agli oggetti e …cosa non è,
seguendo quanto più possibile la visione di G. Booch (“OBJECT-ORIENTED ANALYSIS AND
DESIGN”) che ne è stato uno dei padri, ma tenendo anche presente la seguente affermazione di T.
Rentsch (“A Generalized Object Model” ACM SIGPLAN NOTICES – v17, n9):
"My guess is that object-oriented programming will be in the 1980s what structured programming
was in the 1970s. Everyone will be in favor of it. Every manufacturer will promote his products as
supporting it. Every manager will pay lip service to it. Every programmer will practice it
(differently). And no one will know just what it is".
Booch commentava che la medesima affermazione era ancora vera alla fine degli anni novanta e la
si applica ancora abbastanza bene.
OOP, OOD & OOA
In modo del tutto analogo a quanto successo per il design strutturale (sviluppatosi in modo da
riuscire a costruire sistemi complessi utilizzando gli algoritmi come loro elemento), l’objectoriented design si è evoluto in modo da dare supporto agli sviluppatori che utilizzano le classi e gli
oggetti come loro elemento base nella fase di sviluppo di un progetto.
Per la verità il modello ad oggetti è di importanza capitale nella descrizione di numerose dinamiche
anche non legate allo sviluppo del software e ha quindi subito influenze da diversi ambiti. Perciò il
concetto di “modello ad oggetti” finale unifica visioni provenienti da mondi diversi:
dall’informatica, alla strutturazione dei database, alla costruzione e design di interfacce fino ad
arrivare all’architettura dei computer.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 82
Ripetizioni Materie Scientifiche
OOP: Object Oriented Programming
Si definisce Object Oriented Programming (o semplicemente OOP) un metodo di
implementazione in cui i programmi sono organizzati attraverso un insieme di oggetti, ognuno dei
quali è un’istanza di una classe, e queste classi sono tutte parte di una gerarchia di entità unite fra di
loro da una relazione di ereditarietà.
Nella precedente definizione ci sono tre parti particolarmente importanti:
1. OOP utilizza un insieme di oggetti (non algoritmi, e gli oggetti sono la parte fondamentale
della costruzione logica);
2. ogni oggetto è istanza di una classe;
3. ogni classe è legata alle altre attraverso una relazione detta eredità.
Se in un programma manca anche solo una di queste caratteristiche, non lo si può definire objectoriented.
Tra i molti che hanno cercato di dare, con esiti alterni, una definizione delle caratteristiche che un
linguaggo deve avere per essere esso stesso definito object oriented, è inreressante la prospettiva di
Cardelli e Wegner (On Understanding Types, Data Abstraction, and Polymorphism – Computing
Surveys, Vol 17 n. 4, pp 471-522) per i quali un linguaggio può essere definito come orientato agli
oggetti se e solo se soddisfa le seguenti caratteristiche:
1. Supporta l’astrazione dei dati in una forma che permetta di parlare di strutture per le quali è
definito un insieme di operazioni che possono ad essa essere applicate ed è al contempo in
grado di garantire l’isolamento dello stato delle variabili interne alla struttura.
2. Gli oggetti hanno dei tipi (detti classi) associati che ne definiscono i possibili
comportamenti.
3. I tipi possono essere messi in una relazione di interdipendenza, detta eredità che permette
ad ogni tipo di poter ereditare attributi da altri tipi che in questo caso sono detti “superclassi” Questa caratteristica di un linguaggio di programmazione apparentemente fumosa
risulta chiara se si pensa che significa che in un linguaggio OO deve essere possibile
esprimere la frase “è un” come relazione fra le classi.
Per esempio la “Gibson Les Paul” è una chitarra elettrica, che è una chitarra, che è uno strumento
musicale e, nell’immaginario caso in cui si stia pensando ad un sistema di acquisti online, è un
oggetto di un dato peso e con un imballo di una data dimensione.
La relazione di eredità tra classi tanto importante da far concludere che se un linguaggio di
programmazione consente di creare tipi (indi classi) ma non supporta il concetto di eredità, non lo si
può definire object oriented.
OOD: Object-Oriented Design
Possiamo definire l’object-oriented design come una metodologia di progettazione che comprende
il processo di decomposizione ad oggetti e una notazione per rappresentare modelli logica e fisica
nonché gli aspetti statici e dinamici del sistema in fase di progettazione.
C’è da osservare che la definizione di OOD prevede una decomposizione delle problematiche in
oggetti (in contrapposizione al design procedurale che mira a decomporre le problematiche in
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 83
Ripetizioni Materie Scientifiche
algoritmi) e che già nella sua definizione prevede l’adozione di una notazione (astratta, cioè slegata
dal particolare linguaggio di programmazione che verrà eventualmente usato alla fine per
l’implementazione del design) atta a descrivere le interazioni tra gli oggetti ed a comunicare e
modellare le peculiarità del sistema in esame.
Questa notazione astratta è oggi universalmente riconosciuta essere il linguaggio di modellazione
detto UML: Unified Modelling Language del quale cercheremo di dare rudimenti pratici nelle
prossime sezioni.
OOA: Object-Oriented Analysis
Object-oriented analysis è l’analisi di un problema e la costruzione di modelli del mondo reale con
una visione orientata agli oggetti: in altre parole è una metodologia di analisi che esamina le
necessità di un problema dal punto di vista delle classi e degli oggetti.
Quale relazione lega OOA, OOD e OOP?
Fondamentalmente, i prodotti dell’analisi orientata agli oggetti servono come modelli da cui si
possa avviare una progettazione orientata agli oggetti; i prodotti di progettazione orientata agli
oggetti possono poi essere utilizzati come modelli per l’attuazione completa di un sistema
utilizzando metodi di programmazione orientati agli oggetti.
Anche se in teoria questo approccio che prevede OOA seguita da OOD e OOP (con successive
iterazioni e revisioni) sembra una colossale (e burocratica) complicazione ed un inutile
allungamento dei tempio di sviluppo, l’esperienza conferma che seguendo buone pratiche OO il
software prodotto risulta migliore e soprattutto mantenibile e modificabile.
Caratteristiche dello “stile” object oriented
Si individuano solitamente nello stile Object Oriented quattro elementi caratterizzanti:




Astrazione;
Encapsulation (Incapsulamento);
Gerarchia;
Modularità.
Ogni modello che manca di anche solo una di queste caratteristiche non può essere definito
“orientato agli oggetti”.
Astrazione, applicazione pratica alla progettazione
L’astrazione ci consente di evidenziare le caratteristiche fondamentali di un oggetto e di
classificarlo simile ad altri dello stesso tipo e distinto da tutti gli altri tipi e quindi ci consente di
tracciare confini concettuali ben definiti per descriverlo all’interno di un certo contesto di
osservazione che ci interessa.
Stabilire il giusto insieme di elementi di astrazione per un dato oggetto è il problema centrale nella
progettazione object-oriented. Cerchiamo di rendere più chiaro quanto appena detto con un
esempio.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 84
Ripetizioni Materie Scientifiche
Riprendiamo l’esempio della chitarra Gibson e consideriamo questo oggetto nel contesto della
realizzazione di un sistema per il trasporto di beni. Sarebbe probabilmente una buona astrazione
quella di considerare lo strumento musicale come appartenente alla categoria degli oggetti
trasportabili, con associato un peso ed un volume. Anche l’appartenenza alla categoria degli oggetti
fragili e di valore potrebbe essere corretta ma al fine della specifica prospettiva non avrebbe molto
senso. Si può classificarlo poi come oggetto ad uso dei gruppi musicali pop oppure in quello degli
strumenti a corda: entrambe le affermazioni sono vere ma non pertinenti alla astrazione consona al
problema in oggetto.
È importante osservare che l’astrazione va utilizzata come strumento che permette di focalizzare
l’attenzione su una visione esterna di un oggetto, in modo da separare quella che è
l’implementazione di un comportamento dal suo ruolo nella dinamica globale di un determinato
processo.
Sempre parlando della nostra chitarra nel contesto di un software per l’impacchettamento di item in
immaginari mezzi di trasporto, il fatto di averla classificata come “oggetto fragile” significherà che
dovrà essere possibile chiedere all’oggetto Chitarra quale sia il massimo peso che può sopportare
(quando impilata in un container) che servirà per schedulare l’ordine degli oggetti quando caricati.
Non conta come l’oggetto Chitarra calcolerà il massimo peso sostenibile (implementazione), quello
che è importante è che l’oggetto sia in grado di darci l’informazione (interazione).
Al contempo, se tra gli oggetti da trasportare si aggiungeranno (anche in un secondo momento) le
“scatole per uova” non sarà necessario al sistema di conoscere come il massimo peso sostenibile sia
calcolato nel caso delle uova ed in cosa questo differisca da quello per le chitarre (come avremmo
forse dovuto fare in un contesto procedurale se avessimo previsto una procedura per calcolare il
massimo peso sostenibile di un dato collo) ma basterà che anche le scatole per uova implementino
la medesima interfaccia delle chitarre, cioè quella prevista per gli oggetti fragili.
Incapsulamento
Come si capisce dall’esempio dei “colli fragili” c’è un secondo concetto che scaturisce
naturalmente dall’astrazione e che è rappresentato dal fatto che le implementazioni del calcolo del
massimo peso sostenibile per le chitarre e le uova vengono definite in opportune sezioni “private”
cioè pertinenti solamente a quegli specifici oggetti, incapsulati insomma in precise aree di codice.
I concetti di astrazione e di incapsulamento sono quindi complementari: l’astrazione focalizza
l’attenzione sul comportamento e le caratteristiche osservabili di un oggetto, mentre
l’incapsulamento si concentra sull’implementazione che riesce a riprodurre queste caratteristiche.
L’incapsulamento è molto spesso ottenuto attraverso l’aggregazione di informazioni, che è il
processo di nascondere tutte le informazioni private di un oggetto che non contribuiscono a definire
quelle che sono le sue caratteristiche essenziali nell’interazione con le altre entità del sistema in
esame.
Tipicamente la struttura di un oggetto viene mantenuta nascosta, così come l’implementazione dei
suoi metodi consentendoci di creare delle vere e proprie barriere fra i diversi tipi di astrazioni che
possono essere definite per un oggetto, senza creare alcun tipo di confusione e soprattutto senza
dover duplicare le informazioni che e diverse astrazioni condividono.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 85
Ripetizioni Materie Scientifiche
In definitiva si può definire l’incapsulamento come segue: l’incapsulamento è il processo di
suddivisione degli elementi di un’astrazione che ne costituiscono la struttura e il comportamento;
incapsulamento serve a separare l’interfaccia costruita per un certo tipo di astrazione e la sua
attuazione.
Britton e Parnas chiamano questi elementi incapsulati i "segreti" di un’astrazione.
Gerarchia
“Abstraction is a good thing, but in all except the most trivial applications, we may find many more
different abstractions than we can comprehend at one time.”
Nella maggior parte dei problemi che si affrontano non esiste una unica astrazione da tenere in
considerazione ma spesso ne esistono molteplici e la possibilità di organizzarle in gerarchie è di
vitale importanza per una modellazione efficace.
L’incapsulamneto ci aiuta nella gestione di questa complessità cercando di mantenere nascosta
all’utente finale la nostra astrazione e la modularità ci da il modo per gestire le relazioni logiche
dell’astrazione.
Tuttavia questo non sembra essere abbastanza, infatti molto spesso molte astrazioni formano
insieme una gerachia e identificando queste gerarchie riusciamo a semplificare enormemente il
problema.
Le gerarchie che comunemente si descrivono nella programmazione sono quelle che potremmo
definire strutturali (un determinato oggetto “è un” cioè “appartiene alla tal categoria”) oppure quelli
compontamentali “si comporta come un”.
Le chitarre nel sistema per il carico dei container è un item fragile ma magari ha senso dire che si
comporta anche come un oggetto vendibile e che quindi ha associato un metodo che ne ritorna il
prezzo utilizzabile per redigere il valore degli item che compongono un carico.
La distinzione tra “è un” e “si comporta come un” scompare quasi completamente in linguaggi che,
come il C++, supportano il concetto di ereditarietà multipla ma è invece importante da tenere a
mente in Java che forza una unica appartenenza strutturale.
Modularità
Il significato di modularità può essere visto come l’azione di partizionare un programma (o se
vogliamo un qualsiasi problema) in componenti che riescano a ridurne il grado di complessità.
Risulta infatti evidente nella modellazione di sistemi complessi che la sola astrazione in entità (e
gerarchie) e l’incapsulamento non bastano per renndere trattabili alcuni problemi ma è necessario
ricorrere anche alla modularizzazione, cioè alla individuazione di gruppi di entità (classi) affini per
funzionalità, area di utilizzo, comportamento e conseguente divisione del problema in sottoproblemi
più facilmente affrontabili.
Questa partizione in moduli consente anche di creare un certo numero elementi ben definiti e
separati all’interno del programma stesso (eventualmente anche compilati separatamente), ma che
sia possibile connettere fra di loro per orchestrare una soluzione globale al problema in esame.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 86
Ripetizioni Materie Scientifiche
Determinare come modularizzare un determinato problema è una operazione certamente iterativa ed
è caratterizzata dal medesimo livello di complessità della astrazione: quest’ ultima consiste nella
dereminazione delle entità che è necessario modellare nelal soluzione del problema in oggetto
mentre la modularizzazione consiste nel determinare gruppi ‘assimilabilì di oggetti da relegare in un
modulo da sviluppare separatamente.
Classi, oggetti e costruttori
Un programma (o un sistema) realizzato secondo l’approccio Object Oriented, semplice o
complesso che sia, basa il suo funzionamento sull’interazione tra oggetti: entità che possono
comunicare e modificare il proprio stato.
Per questo è fondamentale approfondire i concetti di classe, oggetto e costruttori, cosa che facciamo
in questa lezione, anche introducendo la rappresentazione UML, ovvero il liguaggio visuale nato
proprio per modellare sistemi orientati agli oggetti.
Gli oggetti
Nella vita reale siamo abituati a classificare e a vedere oggetti dalle caratteristiche tangibili e
riconoscibili, nel mondo OO invece, il concetto di oggetto si amplia e un oggetto può contenere
elementi concreti, ma anche entità come processi (pensiamo a quelli in una filiera manifatturiera) o
concetti teorici e astratti (un modello 3D nella modellazione solida, o cose intangibili come folla,
etc.).
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 87
Ripetizioni Materie Scientifiche
Gli oggetti di cui stiamo parlando non hanno caratteristiche fisicamente distinguibili ma sono
identificati dall’avere:


stato, l’insieme delle variabili interne che ne definiscono le caratteristiche in un certo istante
dell’esecuzione;
“comportamento“, le modalità di azione o di reazione di un oggetto, in termini di come il
suo stato cambia e come i messaggi passano.
Interazione tra oggetti
In OOP i problemi si affrontano concentrandosi sulle interazioni, perciò la spedizione di messaggi
risulta essere basilare per rappresentare l’evoluzione di un sistema. È utile quindi che sia ben
compreso e descrivibile in maniera chiara attraverso gli stumenti di modellazione.
L’interazione tra diversi oggetti avviene attraverso lo scambio di messaggi: un oggetto, detto
sender del messaggio agisce su un altro oggetto, detto recipient, spedendogli uno dei messaggi che
il ricevente è in grado di accettare.
In altre parole: l’oggetto sender chiama uno dei metodi “esposti” dall’oggetto ricevente.
In Java intendiamo per “oggetto” l’istanza particolare di una certa classe, e esso può possedere (o
esporre) alcuni metodi. Quindi un oggetto può ricevere un certo messaggio se possiede un metodo
che l’oggetto sender è in grado di chiamare (con la opportuna visibilità).
In UML lo scambio di messaggi è rappresentato con un diagramma (un semplice schema) chiamato
sequence simile a quello riportato in figura:
che va letta come: l’oggetto Sender spedisce il messaggio corrispondente al metodo methodXYZ
all’oggetto Recipient, il rettangolo vicino alla freccia (sotto Recipient) è comunemente detto
activation e rappresenta l’elaborazione necessaria a Recipient per operare sul suo stato e poter
reagire al messaggio ricevuto.
È naturalmente possibile che, come reazione al messaggio methodXYZ (ad esempio al fine di
reperire informazioni per una eventuale risposta al messaggio ricevuto) Recipient operi su altri
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 88
Ripetizioni Materie Scientifiche
oggetti generando una cascata di messaggi verso altre entità del sistema ( OtherObj nello schema
d’esempio).
Classi
Per ogni oggetto è necessario sapere qual è il set dei messaggi che è in grado di ricevere. Non
possiamo infatti inviare messaggi ad un oggetto se essi non sono ammessi nell’insieme delle azioni
previste.
Una classe è esattamente il “blueprint” (il prototipo) di un oggetto in cui vengono definiti tutti i
messaggi che ciascuna istanza sarà in grado di ricevere. Nella rappresentazione di una classe sarà
quindi importante evidenziare:


l’insieme dei metodi che la classe supporta (messaggi ricevibili)
l’insieme delle variabili di stato (attributi) che ne rappresentano lo stato.
Grazie all’UML possiamo utilizzare il Class Diagram come notazione e disegnare una classe in
modo simile allo schema che segue:
Lo schema mostra sia la notazione per la rappresentazione di una classe (con riportati attributi e
metodi) sia quella per rappresentare una istanza. Nel secondo caso i medodi non hanno ragione di
essere riportati (sono disponibili per il fatto che aa è istanza di ClasseXYZ). Sono riportati invece i
valori degli attributi che sono significativi per ogni singola istanza (mentre il loro valore non
avrebbe senso, tranne che per l’inizializzazione, se associato alla definizione di classe).
Aggiungere dettagli alla rappresentazione
Come l’analisi di ogni problema deve essere affrontata in maniera iterativa, allo stesso modo la
rappresentazione UML delle entità di un sistema va immaginato come un processo dinamico che
non pretende da subito di catturare ogni dettaglio ma procede da idee generali verso quelle più
specifiche e dettagliate.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 89
Ripetizioni Materie Scientifiche
Perciò gli schemi sopra riportati vanno condiderati solo di primo livello e potranno essere dettagliati
aggiungendo per ogni attributo il relativo tipo e valore iniziale:
typeAttribute: data_type = initial_valueClass
mentre per ogni messaggio (metodo) si dovrà procedere a dettagliare la firma (signature):
Metod ( arg: type, …, arg: type ) : return_type
Analogamente sia per gli attributi che per i metodi andrà specificata in una fase più avanzata di
analisi la visibilità che, nella notazione UML, consiste nell’anteporre al nome dei metodi e degli
attributi:
Segno Tipo di visibilità
public
+
private
protected
#
Java’s package visibility
~
mentre si usa sottolineare (o prefissare con $) i metodi e gli attirbuti static.
Un esempio
La ragione di cercare di rappresentare in UML ogni aspetto delle classi (e delle loro relazioni e
dinamiche, come avremo modo di vedere più avanti) deriva da un lato dal fatto che per avere una
buona manutenibilità di un codice occorre che ogni sua parte sia documentata ed anche i dettagli
siano quanto più possibile definiti prima che il codice venga scritto ma anche dal fatto che con gli
opportuni strumenti è possibile che l’implementazione venda generata automaticamente a partire
dalla descrizone formale salvando tempo di sviluppo a favore di quello di analisi e design.
A titolo di esempio riprendiamo schema della classe Conto, esaminato nella lezione introduttiva
sulla classi in Java ed il cui codice riportiamo anche di seguito.
/**
* Classe per rappresentare un Conto
*/
public class Conto {
private double amount;
private String owner;
// costruttore
public Conto(String owner, double initialAmount) {
this.owner = owner;
this.amount = initialAmount;
}
public void versamento(double qty) {
amount += qty;
}
public boolean prelievo(double qty) {
if(amount < qty)
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 90
Ripetizioni Materie Scientifiche
return false;
amount -= qty;
return true;
}
public double getAmount() {
return amount;
}
public String getOwner() {
return owner;
}
}
Nel quale è mostrato anche come sia possibile in UML qualificare elementi per mezzo di quelli che
sono comunemente chiamati strereotype (<< ... >>) e che servono per aggiungere significati alle
entità non descritti direttamente in UML e rappresentano un naturale sistema di estenzione per
l’UML stesso.
Costruttori
Il costruttore è una delle componenti di una classe che merita una trattazione accurata. Partiamo da
queste due eminenti definizioni:


“Una classe è una collezione di uno o più oggetti contenenti un insieme uniforme di attributi
e servizi, insieme ad una descrizione circa come creare nuovi elementi della classe stessa”
(E. Yourdan)
“An object has state, behavior, and identity” (G. Booch)
Il costruttore è quel metodo di una classe il cui compito è proprio quello di creare nuove istanze,
oltre ad essere il punto del programma in cui un nuovo elemento (quindi una nuova identità) viene
creato ed è reso disponibile per l’interazione con il resto del sistema.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 91
Ripetizioni Materie Scientifiche
Come nell’esempio Conto sopra riportato il costruttore è spesso usato per effettuare le
inizializzazioni dello stato delle nuove istanze. In Java possono esserci molteplici costruttori per una
medesima classe (ognuno con parametri di diversi) e ne esiste sempre almeno uno. Se infatti per
una data classe non viene specificato alcun costruttore, il compilatore ne sintetizza automaticamente
uno senza argomenti, detto costruttore default.
Ereditarietà in Java
Tra gli elementi più importanti che caratterizzano il paradigma di sviluppo OOP c’è la Gerarchia.
Tenendo a mente questo fatto non è difficile convincersi di quanto il concetto di ereditarietà sia
centrale nella programmazione Object Oriented.
Definizione di ereditarietà
Si dice che una classe A è una sottoclasse di B (e analogamente che B è una superclasse di A)
quando:


A eredita da B sia il suo stato che il suo behavior (comportamento)
e quindi un’istanza della classe A è utilizzabile in ogni parte del codice in cui sia possibile
utilizzare una istanza della classe B.
Questa ultima parte della definizione va sotto il nome di “Principio di sostituzione di Liskov” ed è
un invariante di importanza capitale che va tenuto presente ogni volta che si pensa a come
strutturare una gerarchia di classi.
Ereditatiertà strutturale (sub-typing)
In questa sezione analizziamo la più semplice forma di ereditarietà disponibile in Java (che
potremmo definire strutturale o sub-typing) ma vale la pena di tenere da subito presente che la
definizione di ereditarietà che abbiamo sopra dato è realizzabile in almeno 3 modi
fondamentalmente dissimili:



Il primo consiste nell’esprimere il fatto che un elemento della classe A “è anche” un
elemento della classe B (un cerchio è anche una figura piana);
il secondo consiste nel dire che gli elementi della classe A si comportano come elementi
della classe B (il cerchio ha un’area ed un bounding-rectangle proprio come tutte le figure
piane);
il terzo, infine, sintatticamente sensibilmente più complicato, quando si arriva al principio di
sostituzione, esprime la relazione di ereditarietà per incapsulamento ed è leggibile come gli
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 92
Ripetizioni Materie Scientifiche
elementi della classe A hanno un elemento della classe B che può quindi essere usato
quando ce ne fosse l’esigenza.
Nel seguito ci occuperemo esclusivamente della prima forma di ereditarietà (che va sotto il nome di
ereditarietà strutturale ed anche sub-typing) lasciando la seconda alla discussione sulle interfacce
mentre la terza verrà in qualche modo affrontata quando parleremo di OO Design e Patterns.
Extends, estendere (o derivare da) una classe in Java
In Java la relazione di derivazione viene resa con la keyword extends che deve essere usata nella
dichiarazione della classe:
class A extends B {
// ...
}
Implica che ogni istanza della classe A sarà anche di tipo B ed avrà a disposizione tutti i metodi
della classe B (potrà ricevere tutti i messaggi che può ricevere la classe B, usando la terminologia
delle precedenti sezioni) e nel suo stato saranno presenti tutte le variabili che si trovano nella super
classe B.
Nella notazione UML si esprime la relazione di ereditarietà fra A e B mediante una freccia che va
da A a B, come mostrato dalla seguente figura.
Esempio di ereditarietà in Java
Un esempio concreto di ereditarietà potrebbe essere il seguente, nel quale esempio intendiamo dare
una bozza di modello per un generico magazzino in cui parte dei colli sono per la vendita.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 93
Ripetizioni Materie Scientifiche
/**
* Collo è la classe "base"
*/
public class Collo {
// dati
private int x_size, y_size, z_size;
protected int weight;
// funzione getter di Weight
public int getWeight() { return weight; }
// Costruttore
public Collo(int w, int xs, int ys, int zs) {
this.weight
this.x_size
this.y_size
this.z_size
=
=
=
=
w;
xs;
ys;
zs;
}
public int getVolume() {
return x_size * y_size * z_size;
}
}
/**
* ColloInVendita è la classe "derivata"
*/
public class ColloInVendita extends Collo {
// dati (oltre quelli di Collo)
private int price;
// coefficienti da applicare alla vendita
private static final float A0 = 1;
private static final float B0 = 1.2;
private static final float C0 = 1.5;
public int getPrice() {
return price;
}
// Costruttore della classe derivata
public ColloInVendita(int w, int xs, int ys, int zs, int price) {
// richiama il costruttore della classe base
super(w, xs, ys, zs);
this.price = price;
}
public float getDeliveryCost() {
return A0*weight + B0*getVolume() + C0*price;
}
}
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 94
Ripetizioni Materie Scientifiche
È utile osservare come, nella dichiarazione di ColloInVendita, siano utilizzabili sia i metodi che i
field (a patto che siano public o protected) della super-classe Collo, che possono essere
eventualmente prefissati con la keyword super utile per eventuali disambiguazioni.
Una nota speciale merita il costruttore, infatti il costruttore della classe ColloInVendita (derivata)
deve essere in grado di costruire una istanza della classe Collo e quindi se per quest’ultima non è
previsto un costruttore ‘default’ (senza argomenti) la classe derivata lo dovrà chiamare
esplicitamente passandogli gli argomenti necessari con la sintassi super(…) che deve essere
obbligatoriamente il primo statment del costruttore della classe figlia.
Leggendo la dichiarazione della classe Collo, dove non compare la keyword extends, potrebbe
nascere la convinzione che questa classe non derivi da nessuna “super-class”; per la verità vale la
pena di osservare che in Java ogni classe deriva da almeno una classe genitrice che, se la
keyword extends viene omessa, è per default la classe java.lang.Object, dalla quale ogni
oggetto Java eredita, per esempio, i metodi hashCode(), getClass(), toString().
Strutturare la gerarchia delle classi
Scegliere le gerarchie di oggetti in una analisi OO non è operazione semplice e difficilmente priva
di ambiguità e dalla cui buona riuscita dipende l’efficacia dell’intera struttura ad oggetti che stiamo
costruendo.
Quando ci si accinge a modellare uno specifico problema con il paradigma ad oggetti è
probabilmente importante tenere presente che non esiste una gerarchia di classi giusta in assoluto,
ma conta lo specifico problema che si intende risolvere.
A tal proposito è interessante riflettere sul classico paradosso della modellazione di cerchi ed ellissi
riassunto dalla domanda “is a circle a kind of ellipse?” da cui prende il titolo anche una sezione
della C++ faq.
Domandandosi infatti se nel contesto della realizzazione di una gerarchia di classi per la
modellazione di figure piane sia opportuno ottenere la classe cerchio come derivata della classe
ellisse ci si trova a dover concludere che benchè matematicamente non ci possano essere dubbi che
la relazione è ben definita (un cerchio è di sicuro una ellisse con assi della medesima lunghezza),
dal punto di vista OO se la classe ellisse fornisce un metodo per cambiare asimmetricamente le
proprie dimensioni (ad esempio setSize(size_x, size_y)) la relazione di sub-typing non può
essere utilizzata in quanto ogni eventuale utilizzo di questo metodo richiederebbe che la classe
cerchio si trasformasse nella classe ellisse; cosa che, almeno in Java, non è possibile.
http://ripetizioni.doomby.com - https://www.facebook.com/Prof.Ing.Bianco
https://twitter.com/ProfBianco
Pag. 95