Introduzione a Java
Livio Tenze ltenze@units.it
March 23, 2021
Prima di iniziare
Cosa bisogna installare:
https://www.jetbrains.com/idea/download
Oracle JDK http://www.oracle.com/technetwork/java/
javase/downloads/index.html
Paradigmi di programmazione
Esistono due differenti modi per descrivere il mondo Come un sistema di processi (modello procedurale)
Tipicamente descritto con flow-chart Usa procedure, funzioni, strutture Cobol, Fortran, Basic, Pascal, C
Come un sistema di cose (modello a oggetti)
Tipicamente descritto come gerarchie e dipendenze tra classi Usa dichiarazioni di classi e di metodi
Simula, Smalltalk, Eiffel, C++, Java
Cosa ` e Java
Linguaggio definito dalla Sun Microsystems (1991), acquisito dalla Oracle
Si voleva estendere il linguaggio C++
La prima versione del linguaggio si chiam`o Oak, poi Java (1995)
Non doveva essere legato ad un processore o una piattaforma (bytecodes)
Permette lo sviluppo di applicazioni su piattaforme multiple, in reti eterogenee e distribuite (Internet)
Esistono pi`u compilatori Java e Virtual Machines Oracle
OpenJDK IBM
Blackdown (porting Linux di SunSoft), ora OpenJDK
Introduzione a Java
Java `e un linguaggio object-oriented, con alcune caratteristiche:
fortemente tipizzato: ogni espressione ha un tipo, che il compilatore usa per controllare la correttezza delle operazioni eseguite
tutti gli errori sono rilevati al momento in cui avvengono, non sono possibili errori non catturati (unchecked error)
non `e consentito l’accesso diretto alla memoria
gestione automatica della memoria a heap, con garbage collector: la deallocazione non `e gestita dal programmatore, ma `e il supporto a run time che si occupa di liberare la memoria non pi`u usata
Dynamic loading and linking: le classi vengono caricate solo al momento del loro effettivo utilizzo
Alcuni aspetti della JDK
JDK - Java development kit, arrivata alla versione 15
Il caricamento delle classi viene gestito mediante la variabile CLASSPATH: contiene una serie di stringhe suddivise da “;”
o “:” che rappresentano il nome completo di un archivio di classi (jar o zip) o di directory contenti file con estensione .class.
Compilatore java: javac
export CLASSPATH=/home/user/classes.zip javac pippo.java
E possibile usare l’opzione -classpath nella chiamata di javac` Debugger java (compilare con opzione -g): jdb
javac -g pippo.java jdb pippo
Alcuni aspetti della JDK
Interprete java: java
java pippo <arg1> <arg2> ...
Editor
Programma pippo.java
Compilatore
bytecode pippo.class
Loader
Carica il byte- code in memoria
Bytecode verifier Verifica che il bytecode non violi le re- strizioni di Java
Interprete
Legge il byte- code e lo esegue
Variabili
Una serie di primitive per poter creare strutture pi`u complesse.
Primitiva Dimensione Val. minimo Val. massimo
boolean 1-bit - -
char 16-bit Unicode 0 Unicode 216− 1
byte 8-bit -128 127
short 16-bit −215 215− 1
int 32-bit −231 231− 1
long 64-bit −263 263− 1
float 32-bit IEEE754 IEEE754
double 64-bit IEEE754 IEEE754
void - - -
Le variabili devono iniziare con una lettera, possono contenere numeri: identificatore var name;
Inizializzazione variabile
`E buona norma inizializzare la variabile una volta dichiarata:
identificatore var name = var value;
int p r i m o i n t e r o = 1 0 0 ;
Java, in ogni caso assegna ad ogni variabile un valore di default al momento della dichiarazione.
Tipo primitivo Valore assegnato
boolean false
char \u0000
byte 0
short 0
int 0
long 0L
float 0x0f
double 0.0
Variabili final
A differenza di molti altri linguaggi, Java non consente di definire costanti. Per far fronte alla mancanza `e possibile utilizzare il modificatore final. Una variabile dichiarata final si comporta come una costante, pertanto le deve essere assegnato il valore iniziale al momento della sua dichiarazione utilizzando l’operatore “=” di assegnazione.
f i n a l int p i p p o = 10;
Operatori
Operatori Funzioni
++ - - + - aritmetiche unarie e booleane
* / % aritmetiche
+ - addizione, sottrazione e concatenazione
<< >> >>> shift bit
< <= > >= instanceof comparazione (relazionali)
== != Uguaglianza e disuguaglianza (relazionali)
&ˆ| bit AND, XOR e OR
&& || ! logico AND, OR e NOT expr ? expr : expr Condizione a tre
Tutti gli operatori funzionano solamente con dati primitivi a parte gli operatori !=, == e = che hanno effetto anche se gli operandi sono rappresentati da oggetti. Inoltre la classe String utilizza gli
Esempio1
Esempio operatori appena visti, chiamate di java/javac.
Classi
Classe: descrizione astratta dei dati relativi a un concetto (attributi) e dei comportamenti tipici relativi
(metodi/funzioni)
Oggetto: istanza di una classe
Comunicazione attraverso messaggi (invocazione) Un oggetto `e una coppia (stato, funzioni)
Esempio
Automobile `e una classe (rappresenta il concetto generico di automobile)
Fiat Brava `e una classe (rappresenta il concetto di un tipo di automobile)
L’oggetto targato AX 266 WS `e un’istanza di Fiat Brava
Classi
Chiariamo
Oggetto: istanza (esemplare) di una classe Creazione di un oggetto: ISTANZIAZIONE
Due istanze della stessa classe NON sono lo stesso oggetto Due istanze diverse hanno la stessa INTERFACCIA
p u b l i c c l a s s A u t o m o b i l e {
p r i v a t e int l i t r i S e r b a t o i o = 50; /* a t t r i b u t i */
/* m e t o d i */
p u b l i c v o i d a v v i a t i (){ /* i m p l e m e n t a z i o n e */ };
/* s t a r t p o i n t */
p u b l i c s t a t i c v o i d m a i n ( S t r i n g [] a r g v ) { A u t o m o b i l e a = new A u t o m o b i l e ();
a . a v v i a t i ();
}
Cosa sono le reference
Java fa una netta distinzione tra Classi e tipi primitivi.
int c o u n t e r ;
Questa dichiarazione crea una variabile intera chiamata counter ed alloca subito quattro byte per lo “storage” del dato.
S t a c k s ;
crea una variabile che referenzia l’oggetto, ma non crea l’oggetto Stack. Una variabile di referenza, `e quindi una variabile speciale che tiene traccia di istanze di tipi non primitivi.
N.B.: una referenza ad un oggetto di tipo Stack non pu`o tracciare oggetti di diverso tipo.
Un discorso simile vale anche per gli array:
int[] n u m b e r s ; /* r e f e r e n c e */
Considerazioni sulle variabili
Allocazione delle variabili
A differenza di C e C++ in cui un dato rappresenta il
corrispondente dato-macchina ossia una variabile intera in C++
occupa 32 bit ed una variabile byte ne occupa 8, ora le variabili si
“comportano come se”.
La virtual machine Java difatti alloca per ogni dato primitivo il massimo disponibile in fatto di rappresentazione macchina dei dati.
La virtual machine riserver`a, su una macchina a 32 bit, 32 bit sia per variabili intere che variabili byte, quello che cambia `e che il programmatore vedr`a una variabile byte comportarsi come tale ed altrettanto per le altre primitive.
Blocchi
Un blocco `e una porzione di codice racchiusa tra graffe
{ // b l o c c o ...
}
La visibilit`a del nome di una variabile indica le parti di un programma in cui il nome pu`o essere utilizzato per riferirsi alla variabile.
Una variabile dichiarata all’interno di un blocco
`
e visibile solo all’interno del blocco stesso, e dei sottoblocchi
`
e visibile solo dal punto della dichiarazione in poi Vincoli (controllati staticamente dal compilatore)
ogni variabile deve essere inizializzata prima del suo utilizzo
Scope dei dati
`E possibile dichiarare variabili con lo stesso nome, la visibilit`a delle variabili dipende dal blocco di istruzioni in cui essa `e dichiarata.
Si dice che una variabile ha scope limitato al blocco in cui `e dichiarata.
c l a s s O b j e c t N a m e { d a t a _ d e c l a r a t i o n s m e t h o d _ d e c l a r a t i o n s }
Scope variabili in una dichiarazione di metodo:
r e t u r n _ t y p e m e t h o d _ n a m e ( a r g _ t y p e n a m e [ , a r g _ t y p e n a m e ]) {
int i = 0;
{
f l o a t j = 5 . 0 ; }
}
Oggetto null
null
Il linguaggio Java prevede un valore speciale per le variabili reference che non referenzia nessuna istanza di un oggetto. Il valore speciale null rappresenta un oggetto inesistente, e viene assegnato di default ad ogni variabile reference.
Garbage collector
Quando ad una variabile reference viene assegnato il valore null, l’oggetto referenziato verr`a rilasciato e, se non utilizzato verr`a dato in pasto al garbage collector che si occuper`a di rilasciare la
memoria allocata per la entit`a.
S t a c k s = n u l l; int[] n u m b e r s ;
if ( n u m b e r s == n u l l) { /* t h e n */ }
Creazione di istanze
Creata la variabile reference, siamo pronti a creare una istanza di un nuovo oggetto o di un array. L’operatore new fa questo per noi, allocando la memoria necessaria per il nostro oggetto e tornando la locazione in memoria della entit`a creata.
Si usa l’operatore new:
S t a c k s = new S t a c k ();
Anche gli array vengono allocati nello stesso modo:
int[] m y _ a r r a y = new int[ 2 0 ] ;
Operatore .
Definizione
Questo operatore `e utilizzato per accedere ai membri di un oggetto tramite la variabile reference.
c l a s s S t a c k E l e m e n t { int val ;
}
c l a s s S t a c k {
S t a c k E l e m e n t pop () { ...
}
v o i d p u s h ( S t a c k E l e m e n t ele ) { ...
} }
Operatore .
Creiamo l’oggetto ed accediamo ai metodi e attributi:
S t a c k E l e m e n t s _ e l e = new S t a c k E l e m e n t ();
int v a l u e = 10;
s _ e l e . val = v a l u e ;
S t a c k s = new S t a c k ();
s . p u s h ( s _ e l e );
// R i l a s c i a m o s _ e l e e r e c u p e r i a m o il v a l o r e // d a l l o s t a c k
s _ e l e = n u l l; s _ e l e = s . pop ();
this
A cosa serve this
Java prevede una modalit`a di referenziazione speciale identificata da this. Difatti il valore di this viene modificato automaticamente da Java in modo che ad ogni istante sia sempre referenziato all’oggetto attivo.
c l a s s S t a c k E l e m e n t { int val ;
v o i d s e t V a l (int val ) { t h i s. val = val ; }
}
String
Java fornisce varie classi gi`a definite, una di queste `e String. Sono oggetti che possono essere facilmente creati, possono essere concatenati e fornire un controllo sulla lunghezza della stringa.
S t r i n g p r i m a = " h e l l o " ; S t r i n g s e c o n d a = " w o r l d " ;
S t r i n g t e r z a = p r i m a + s e c o n d a ; int l u n g h e z z a = p r i m a . l e n g t h ();
Comparazione di oggetti
Stato di un oggetto
Gli oggetti Java rappresentano dati molto complessi il cui stato, a differenza di un tipo primitivo, non pu`o essere definito
semplicemente dal valore della variabile reference. In particolare, definiamo stato di un oggetto il valore in un certo istante di tutti i dati membro della classe. Ad esempio lo stato dell’oggetto
StackElement `e rappresentato dal valore del dato membro val di tipo intero.
S t a c k a = new S t a c k ();
S t a c k b = new S t a c k ();
( a == b ) - > f a l s e!!
Ci`o che si confronta sopra `e il valore del reference e non lo stato dell’oggetto.
Comparazione di oggetti
Per confrontare correttamente i due oggetti visti prima `e necessario usare il metodo equals, che viene ereditato dalla classe Object.
a . p u s h ( 1 ) ; b . p u s h ( 1 ) ;
a . e q u a l s ( b ) - > t r u e
Metodi statici
Definizione
Metodi statici, ossia metodi che appartengono a classi, ma non richiedono oggetti attivi. Questi metodi possono essere creati utilizzando la parola chiave static a sinistra della dichiarazione
c l a s s e s e m p i o {
s t a t i c int m e t o d o _ s t a t i c o () { ...
} }
c l a s s a l t r a { v o i d m e t o d o () {
int i = e s e m p i o . m e t o d o _ s t a t i c o ();
} }
Per chiamare metodo statico non `e stato necessario istanziare
Metodi statici
Affinch´e la Java Virtual Machine possa eseguire una applicazione,
`e necessario che abbia ben chiaro quale debba essere il primo metodo da eseguire. Questo metodo viene detto entry point della applicazione.
c l a s s p r i m a _ a p p l i c a z i o n e {
p u b l i c s t a t i c v o i d m a i n ( S t r i n g [] a r g v ) { ...
} }
Anche l’applicazione `e un oggetto!
Classe System
Un’altra delle classi predefinite in Java `e la classe System. Questa classe ha una serie di metodi statici. Due dei metodi statici contenuti nella classe sono System.out e System.err che rappresentano rispettivamente lo standard output e lo standard error dell’interprete java.
S y s t e m . out . p r i n t l n ( " S c r i v o s u l l o s t a n d a r d o u t p u t " );
S y s t e m . err . p r i n t l n ( " S c r i v o s u l l o s t a n d a r d e r r o r " );
S y s t e m . e x i t ( 0 ) ; // E s c o d a l l a a p p l i c a z i o n e
Esempio2
Uno Stack o Pila `e una struttura dati gestita secondo la filosofia LIFO (Last In First Out) ovvero l’ultimo elemento ad essere inserito `e il primo ad essere recuperato.
Disegnare e realizzare una classe Stack che contenga al massimo 20 numeri interi e deve avere i due metodi:
v o i d p u s h (int) int pop ()
Istruzioni per il controllo del flusso
Modificatori
condizionali: if – else, switch di ciclo: while, do – while, for di interruzione: break, continue di ritorno: return
gestione eccezioni: try - catch, finally
Condizionale if–then–else
if ( < c o n d i z i o n e >) {
< C1 >
} e l s e if ( < c o n d i z i o n e >) {
< C2 >
} e l s e {
< C3 >
}
Con condizione si intende indicare un’espressione booleana. I blocchi if-then-else possono presentarsi annidati.
Condizionale switch
s w i t c h ( < e s p r e s s i o n e >) { c a s e e s p r _ c o s t a n t e : ...
b r e a k;
c a s e e s p r _ c o s t a n t e : ...
b r e a k;
d e f a u l t:
...
}
Dove con espressione si indica ogni espressione valida che produca un intero.
Ciclo while e do–while
w h i l e ( < c o n d i z i o n e >) {
< corpo >
}
La condizione booleana viene valutata all’inizio del blocco di istruzioni.
do {
< corpo >
} w h i l e( < c o n d i z i o n e >);
La condizione viene valuta al termine del blocco di istruzioni.
Ciclo for
for ( i n i t _ s t a t e m e n t ; c o n d i t i o n a l _ e x p r ; i t e r a t i o n _ s t m t ) { i s t r u z i o n i
}
L’istruzione for, come in C e in C++, `e molto versatile in quanto consente di scrivere cicli di esecuzione utilizzando molte varianti alla forma descritta sopra. Ad esempio per creare un ciclo infinito si pu`o scrivere:
for ( ; ; ) {}
Oppure si possono complicare le espressioni di inizio, condizione ed incremento:
for (int i =0 , j = 0 ; ( i <10 && j > 0 ) ; i ++ , j ++) { ...
}
Istruzioni di ramificazione
Istruzioni
break interrompe l’esecuzione di un ciclo evitanto ogni controllo condizionale
continue salta un blocco di istruzioni all’interno del ciclo e torna il controllo all’espressione booleana
return interrompe l’esecuzione e torna in controllo al metodo chiamante
Esempio:
for (int i = 0 ; ; i ++) { if ( i = = 1 0 ) b r e a k; ...
}
continue e return
w h i l e( i < 2 0 ) { i ++;
if (( i % 2 ) ! = 0 ) c o n t i n u e; p a i r s ++
}
v o i d m e t o d o () {
for (int i =0; i < 1 0 ; i ++) { ...
if ( i = = 5 ) r e t u r n; }
}
Nel caso in cui il metodo non restituisse void, avremmo usato l’espressione return ret val.
Package java
Definizione
I package sono meccanismi per raggruppare definizioni di classe in librerie, similmente ad altri linguaggi di programmazione. Il meccanismo `e provvisto di una struttura gerarchica per
l’assegnamento di nomi alle classi in modo da evitare eventuali collisioni in caso in cui alcuni programmatori usino lo stesso nome per differenti definizioni di classe.
Benefici dall’uso dei package:
le classi possono essere mascherate all’interno dei package (incapsulamento nel file)
le classi possono condividere dati e metodi con classi di altri package
i package forniscono un meccanismo efficace per distribuire
Package in pratica
I package combinano definizioni di classi in un unico archivio la cui struttura gerarchica rispetta quella del file system. I nomi dei package sono separati tra loro da punto.
Se la pippo corporation avesse generato un insieme di classi dedicate al calcolo statistico, le classi dovrebbero essere contenute in un package chiamato ad esempio pippo.stat
Per archiviare una classe all’interno di un package `e necessario aggiungere l’istruzione
p a c k a g e p i p p o . s t a t ;
all’inizio del codice sorgente.
Package
Creazione del package
Una volta definito il nome di un package, deve essere creata su disco la struttura a directory che rappresenti la gerarchia definita dai nomi.
Per trovare le classi contenute in un package, Java utilizza la variabile di ambiente CLASSPATH che contiene le informazioni per puntare alla root del nome del package e non direttamente alle classi all’interno del package.
CLASSPATH = /java/lib/tools.jar;/java/import;.;./;
Modificatore public
Comportamento di default
Di default, la definizione di una classe Java pu`o essere utilizzata solo dalle classi all’interno del suo stesso package.
p a c k a g e app . s t a c k ;
c l a s s S t a c k { int[] d a t a ; int n d a t a ;
v o i d p u s h (int i ) { ...
}
int pop () {
...
} }
Modificatore public
Come usare public
Java richiede al programmatore di esplicitare quali classi e quali membri possano essere utilizzati all’esterno del package. A questo scopo Java riserva il modificatore public da utilizzare prima della dichiarazione della classe o di un membro.
p u b l i c c l a s s S t a c k { int[] d a t a ;
int n d a t a ;
p u b l i c v o i d p u s h (int i ) { ...
}
p u b l i c int pop () { ...
} }
Modificatore public
Le specifiche del linguaggio richiedono che il codice sorgente di classe pubblica sia memorizzata in un file avente lo stesso nome della classe (incluse maiuscole e minuscole), ma con estensione
“.java”.
Come conseguenza alla regola, pu`o esistere solo una classe pubblica per ogni file di sorgente. Questa regola `e rinforzata dal compilatore che scrive il bytecode di ogni classe in un file avente lo stesso nome della classe (incluse maiuscole e minuscole), ma con estensione “.class”.
Package
Dal momento che le classi possono essere organizzate in package, `e necessario specificare a quale package una classe appartenga, pena l’incapacit`a della virtual machine di trovarla.
Un modo per indicare il package a cui una classe appartiene
`e quello di specificare il package ad ogni chiamata alla classe ossia utilizzando nomi qualificati.
i m p o r t app . s t a c k . S t a c k ;
oppure mediante l’uso di wildcard:
i m p o r t app . s t a c k .*;
che risolve il nome di tutte le classi pubbliche di un package.
Esempio3
Utilizzando la classe stack definita in precedenza, scrivere un ciclo while che stampi tutti gli interi pari da 1 a 13 inserendoli nello stack. La definizione di classe di stack dovr`a essere memorizzato nel package “esempi.lab”.
Incapsulamento
Definizione
L’incapsulamento di oggetti `e il processo di mascheramento dei dettagli dell’implementazione ad altri oggetti per evitare riferimenti incrociati. I programmi scritti con questa tecnica risultano molto pi`u leggibili e limitano i danni dovuto alla propagazione di bachi.
Nascondendo i dettagli, possiamo assicurare a chi utilizza l’oggetto che ci`o che sta utilizzando `e sempre in uno stato consistente a meno di bug dell’oggetto stesso. Uno stato consistente `e uno stato permesso dal disegno di un oggetto. `E per`o importante notare che uno stato consistente non corrisponde sempre allo stato aspettato dall’utente dell’oggetto.
Public, protected, private
Modificatori
I modificatori di accesso determinano ci`o che fa parte dell’interfaccia pubblica o privata
public: una classe, un metodo o un campo pubblico pu`o essere visto in qualunque parte del codice
protected: un metodo o un campo protetto pu`o essere visto solo nelle sottoclassi della classe che lo dichiara
private: un metodo o un campo privato pu`o essere visto solo nella classe che lo dichiara
package friendly: una classe, un metodo o un campo senza modificatori espliciti ha visibilit`a di default (visibile a livello di package)
Modificatore private
Il modificatore private realizza incapsulamento a livello di definizione di classe e serve a definire membri che devono essere utilizzati solo da altri membri della stessa classe di definizione.
p r i v a t e i d e n t i f i c a t a v a r _ n a m e ;
p r i v a t e r e t u r n _ t y p e m e t h o d _ n a m e ( a r g _ t y p e n a m e [ ,...]) {
i s t r u z i o n i }
Modificatore public
Il modificatore public consente di definire classi o membri di una classe visibili da qualsiasi classe all’interno dello stesso package e non.
Tipicamente metodi membro public utilizzano membri private per implementare le funzionalit`a dell’oggetto.
p u b l i c i d e n t i f i c a t a v a r _ n a m e ;
p u b l i c r e t u r n _ t y p e m e t h o d _ n a m e ( a r g _ t y p e n a m e [ ,...]) {
i s t r u z i o n i }
Modificatore protected
Membri di una classe dichiarati protected possono essere utilizzati sia dai membri della stessa classe che da altre classi purch´e appartenenti allo stesso package
p r o t e c t e d i d e n t i f i c a t a v a r _ n a m e ;
p r o t e c t e d r e t u r n _ t y p e m e t h o d _ n a m e ( a r g _ t y p e n a m e [ ,...]) {
i s t r u z i o n i }
Esempio di incapsulamento: classe Impiegato
p a c k a g e it . u n i t s . e s e m p i o ;
p u b l i c c l a s s I m p i e g a t o {
p r i v a t e S t r i n g n o m e ;
p r i v a t e b o o l e a n a f f a m a t o = t r u e; p u b l i c b o o l e a n h a i F a m e () {
r e t u r n a f f a m a t o ; }
p u b l i c S t r i n g n o m e () { r e t u r n n o m e ;
}
p u b l i c v o i d v a i A P r a n z o ( S t r i n g l u o g o ) { // m a n g i a
a f f a m a t o = f a l s e; }
}
Esempio di incapsulamento: classe DatoreDiLavoro
p a c k a g e it . u n i t s . e s e m p i o ;
p u b l i c c l a s s D a t o r e D i L a v o r o {
p u b l i c v o i d s i i C o r r e t t o C o n I m p i e g a t o ( I m p i e g a t o i m p i e g a t o ) {
// if ( i m p i e g a t o . a f f a m a t o ) i l l e g a l e , p r i v a t e ! // i m p i e g a t o . a f f a m a t o = true , i l l e g a l e , p r i v a t e ! if ( i m p i e g a t o . h a i F a m e ())
{
i m p i e g a t o . v a i A P r a n z o ( " R i s t o r a n t e " );
} } }
Completiamo l’esempio
Scriviamo il codice per far funzionare le due classi definite nei lucidi precedenti.
Costruttori
Definizione
I costruttori sono metodi speciali chiamati quando viene creata una nuova istanza di classe e servono ad inizializzare lo stato iniziale dell’oggetto. Questi metodi hanno lo stesso nome della classe di cui sono membro e non restituiscono nessun tipo. Se una classe non `e provvista di costruttore, Java ne utilizza uno speciale di default.
Operatore new
Per creare un oggetto attivo dalla sua definizione di classe, Java mette a disposizione l’operatore new.
La sintassi dell’operatore new prevede un tipo seguito da un insieme di parentesi. Le parentesi indicano che per creare l’oggetto verr`a chiamato il costruttore.
Esempio costruttore 1/2
p a c k a g e it . u n i t s . e s e m p i o ; p u b l i c c l a s s S t a c k {
p r i v a t e int m a x s i z e ; p r i v a t e int[] d a t a ; p r i v a t e int f i r s t ;
p u b l i c S t a c k () // c o s t r u t t o r e {
m a x s i z e = 10;
d a t a = new int[ 1 0 ] ; f i r s t = 0;
}
int pop () {
if ( f i r s t > 0) {
first - -;
r e t u r n d a t a [ f i r s t ];
}
r e t u r n 0;
}
Esempio costruttore 2/2
v o i d p u s h (int i ) { if ( f i r s t < m a x s i z e ) {
d a t a [ f i r s t ] = i ; f i r s t ++;
} }
p u b l i c s t a t i c v o i d m a i n ( S t r i n g [] a r g s ) { S t a c k s = new S t a c k ();
s . p u s h ( 5 ) ;
S y s t e m . out . p r i n t l n ( " Ho l e t t o " + s . pop ( ) ) ; }
Overloading dei costruttori
Al programmatore `e consentito scrivere pi`u di un costruttore per una data classe a seconda delle necessit`a di disegno dell’oggetto, permettendogli di passare diversi insiemi di dati di inizializzazione.
p u b l i c S t a c k () { m a x s i z e = 10:
d a t a = new int[ 1 0 ] ; f i r s t = 0;
}
p u b l i c S t a c k (int s i z e ) { m a x s i z e = s i z e ;
d a t a = new int[ m a x s i z e ];
f i r s t = 0;
}
Stack s = new Stack(25);
Chiamate incrociate di costruttori
Guardando la definizione di Stack, notiamo che i due costruttori fanno esattamente la stessa cosa. Per ridurre la quantit`a di codice, possiamo chiamare un costruttore da un altro. Per chiamare un costruttore da un altro, `e necessario utilizzare una sintassi speciale.
p u b l i c S t a c k () { t h i s( 1 0 ) ; }
p u b l i c S t a c k (int m a x s i z e ) { d a t a = new int[ m a x s i z e ];
t h i s. m a x s i z e = m a x s i z e ; f i r s t = 0;
}
Una chiamata cross–call tra costruttori, DEVE essere la prima riga di codice del costruttore chiamante.
Esempio5
Definire un oggetto chiamato Set che rappresenta un insieme di interi.
L’insieme deve avere al massimo tre metodi:
boolean isMember(int); //Ritorna true se il numero `e nell’insieme
void addMember(int); //Aggiunge un numero all’insieme void showSet(); //Stampa a video il contenuto dell’insieme nel formato:
1, 4, 5, 12
Cosa ` e l’ereditariet` a
Definizione
L’ereditariet`a `e la caratteristica dei linguaggi object oriented che consente di utilizzare classi come base per la definizione di nuovi oggetti che ne specializzano il concetto.
Essa permette di:
aggiungere funzionalit`a
rischi minimi per le funzionalit`a esistenti
auto–documentante rispetto ai linguaggi procedurali Aspetti essenziali dell’ereditariet`a sono l’overload e l’overriding dei metodi.
Ereditariet` a singola
Object
ClassB
ClassB1
ClassC ClassD
ClassD1 ClassD2
Ereditariet` a
Ogni volta che si utilizza una classe per ereditariet`a ci si riferisce a questa come alla “classe base” o “superclasse”.
Quando definiamo nuovi oggetti utilizzando l’ereditariet`a, tutte le funzionalit`a della classe base sono trasferite alla nuova classe detta
“classe derivata“ o “sottoclasse”.
Concetti base
i membri della classe base sono “concettualmente” copiati nella nuova classe
consente alla classe derivata di modificare la superclasse ogni aggiunta o modifica ai metodi della superclasse sar`a applicata solo alle classe derivata
Considerazioni sull’ereditariet` a
Tramite questa tecnica `e possibile creare nuove variet`a di entit`a gi`a definite mantenendone tutte le caratteristiche e le funzionalit`a.
Questo significa che se una applicazione `e in grado di utilizzare una classe base, sar`a in grado di utilizzarne la derivata allo stesso modo. Per questi motivi, `e importante che una classe base
rappresenti le funzionalit`a generiche delle varie specializzazioni che andremo a definire.
Overload di metodi 1/2
Definizione
Fare l’overloading di un metodo significa, in generale, dotare una classe di metodi aventi stesso nome, ma con parametri differenti.
p u b l i c c l a s s V e i c o l o { S t r i n g n o m e ;
int v e l o c i t a ; int d i r e z i o n e ;
f i n a l s t a t i c int D I R I T T O = 0;
f i n a l s t a t i c int S I N I S T R A = 1;
f i n a l s t a t i c int D E S T R A = 2;
Overload di metodi 2/2
p u b l i c V e i c o l o () { ...
}
p u b l i c v o i d m u o v i () { v e l o c i t a = 1;
}
p u b l i c v o i d m u o v i (int q u a l e V e l o c i t a ) { v e l o c i t a = q u a l e V e l o c i t a ;
}
p u b l i c s t a t i c v o i d m a i n ( S t r i n g [] a r g s ) { V e i c o l o v = new V e i c o l o ();
v . m u o v i ();
v . m u o v i ( 1 0 ) ; }
}
Estendere una classe
Definita la classe base Veicolo, sar`a possibile definire nuovi tipi di veicoli estendendo la classe generica.
class nome classe extends nome super classe
c l a s s M a c c h i n a e x t e n d s V e i c o l o { p u b l i c M a c c h i n a () {
v e l o c i t a = 0;
d i r e z i o n e = D I R I T T O ; n o m e = " M a c c h i n a " ; }
}
Estendendo la classe Veicolo, ne ereditiamo tutti i dati membro ed i metodi. L’unico cambiamento che abbiamo dovuto apportare `e quello di creare un costruttore ad hoc. Il nuovo costruttore semplicemente modifica il contenuto della variabile nome affinch´e l’applicazione stampi i messaggi corretti.
Usare la nuova classe Macchina
p u b l i c c l a s s A u t i s t a {
p u b l i c s t a t i c v o i d m a i n ( S t r i n g [] a r g v ) { M a c c h i n a f i a t = new M a c c h i n a ();
f i a t . m u o v i ();
} }
Ereditariet` a ed incapsulamento
Come si comportano i modificatori public, private e protected quando sono ereditati da altre classi?
public consente di dichiarare dati e metodi membro visibili
private nasconde completamente dati e metodi membro protected... vediamo
Modificatore protected 1/3
p a c k a g e p a c k b a s e ;
c l a s s C l a s s e B a s e {
p r o t e c t e d int d a t o P r o t e t t o ; p r o t e c t e d v o i d m e t o d o P r o t e t t o () {
...
} }
Proviamo ad estendere questa classe nello stesso package.
Modificatore protected 2/3
p a c k a g e p a c k b a s e ;
c l a s s A l t r a C l a s s e {
C l a s s e B a s e a = new C l a s s e B a s e ();
v o i d m e t o d o () { a . m e t o d o P r o t e t t o ();
} }
Lecito in quanto siamo nello stesso package. Proviamo ora ad usare qualcosa di protetto da una classe in un altro package.
Modificatore protected 3/3
p a c k a g e a l t r o p a c k a g e ; i m p o r t p a c k b a s e ;
c l a s s T e r z a C l a s s e e x t e n d s C l a s s e B a s e {
v o i d m e t o d o I n t e r n o ( C l a s s e B a s e cb , T e r z a C l a s s e tc ) { tc . m e t o d o P r o t e t t o (); // L e g a l e
cb . m e t o d o P r o t e t t o (); // I l l e g a l e }
}
Ricapitolando quindi...
Riassunto modificatori
Class Package
Subclass (same pkg)
Subclass
(diff pkg) World
public * * * * *
protected * * * *
friendly * * *
private *
Considerazioni sul costruttore
Considerazioni
Negli esempi precedenti si nota facilmente che il codice del metodo costruttore della classe Veicolo `e molto simile a quello del
costruttore della classe Macchina
Potrebbe tornare utile utilizzare il costruttore della classe base...
Cosa potrebbe succedere se sbagliassimo qualcosa nella definizione del costruttore della classe derivata?
Cosa succederebbe se nella classe base ci fossero dei dati privati che il costruttore della classe derivata non pu`o aggiornare?
Soluzione per il costruttore
Ogni classe deve avere un costruttore Costruttore di default, con codice vuoto
Una classe derivata, pu`o chiamare il costruttore della classe base mediante la sintassi:
super(argument list)
p u b l i c c o s t r u t t o r e D e f i n i t o D a U t e n t e () { s u p e r( 2 3 ) ;
int i ; }
Se l’utente non effettua una chiamata esplicita al costruttore della classe base, Java esegue implicitamente tale chiamata (senza argomenti).
Aggiungere metodi
Quando estendiamo una classe, possiamo aggiungere nuovi metodi alla classe derivata.
Questo ci permette di implementare caratteristiche particolari non definite nella definizione generica della classe base.
p u b l i c c l a s s M a c c h i n a e x t e n d s V e i c o l o { p u b l i c M a c c h i n a () {
v e l o c i t a = 0;
d i r e z i o n e = D I R I T T O ; n o m e = " M a c c h i n a " ; }
p u b l i c v o i d s u o n a () {
S y s t e m . out . p r i n t l n ( n o m e + " s u o n a t o il c l a c s o n " );
} }
Quinti Autista pu`o ora chiamare anche il metodo suona().
Overriding di metodi
Idea
Se un metodo ereditato non lavorasse correttamente rispetto a quanto ci aspettiamo dalla specializzazione del concetto definito nella classe base, Java ci consente di riscrivere il metodo originale.
Riscrivendo nuovamente il metodo solo nella nuova classe, non c’`e pericolo che la classe base venga manomessa. Il nuovo metodo verr`a chiamato al posto del vecchio anche se la chiamata venisse effettuata da un metodo ereditato dalla classe base.
Chiamare medodo classe base
Utilizzo di super
La parola chiave super pu`o essere utilizzata anche nel caso in cui sia necessario richiamare un metodo della super classe ridefinito nella classe derivata con il meccanismo di overriding.
p u b l i c c l a s s M a c c h i n a e x t e n d s V e i c o l o { p u b l i c v o i d m u o v i (int q u a l e _ v e l o c i t a ) {
s u p e r. m u o v i ( q u a l e _ v e l o c i t a );
...
} }
In questo caso super ha lo stesso significato di una variabile reference con la differenza che viene istanziato dalla JVM e referenzia sempre la superclasse della classe attiva ad ogni istante.
Variabili reference
Una volta che una classe Java `e stata derivata, Java consente alle variabili reference che rappresentano il tipo della classe base di referenziare ogni istanza di un oggetto derivato da essa nella gerarchia definita dalla ereditariet`a.
V e i c o l o v = new M a c c h i n a ();
v . s u o n a (); // C h i a m a il m e t o d o di M a c c h i n a
Tutti gli oggetti derivati hanno sicuramente almeno tutti i metodi della classe base.
Nel caso in cui un metodo sia stato ridefinito mediante overriding, queste tipo di referenziamento comunque effettuer`a una chiamata al nuovo metodo.
casting
Il cast di un tipo consente di dichiarare che una variabile reference temporaneamente rappresenter`a un tipo differente da quello rappresentato al compile-time. La sintassi di una cast di tipo `e la seguente:
(new type) variabile
Dove new type `e il tipo desiderato e variable `e la variabile che vogliamo convertire temporaneamente.
instanceof
Dal momento che in una applicazione Java esistono variabili reference in gran numero, `e a volte utile determinare al run-time che tipo di oggetto la variabile sta referenziando.
A tal fine Java supporta l’operatore booleano instanceof che controlla il tipo di oggetto referenziato al run-time da una variabile reference.
A instanceof B
Dove A rappresenta una variabile reference, e B un tipo referenziabile.
java.lang.Object
Tutte le classi estendono un’altra classe
Se non si estende esplicitamente un’altra classe, si estende (implicitamente) la classe Object
Tutte le classi ereditano (direttamente o indirettamente) da Object
Alcuni metodi definiti da Object
toString(): rappresentazione a stringa dell’oggetto
hashCode(): intero che identifica univocamente un oggetto equals(Object o): restituisce true se l’oggetto `e uguale all’argomento (confronta lo stato degli oggetti)
finalize(): termina l’oggetto
toString() viene usato da System.out.print() per “visualizzare il
Esempio6
Creare, a partire dalla classe Veicolo, nuovi tipi di veicolo mediante il meccanismo della ereditariet`a: Cavallo, Macchina. Creare Driver per “pilotare” un veicolo.
Eccezioni
Definizione
Le eccezioni sono utilizzate da Java in quelle situazioni in cui sia necessario gestire condizioni anomale, ed i normali meccanismi sono insufficienti ad indicare l’errore. Formalmente, una eccezione
`e un evento che si scatena durante la normale esecuzione di un programma, causando l’interruzione del normale flusso di esecuzione della applicazione.
Spesso situazioni del genere sono legate all’inizializzazione dei costruttori che non hanno possibilit`a di restituire uno stato di ritorno.
Le eccezioni facilitano la vita al programmatore fornendo un meccanismo flessibile per descrivere eventi che, in mancanza delle quali, risulterebbero difficilmente gestibili
Propagazione
Il punto di forza del meccanismo delle eccezioni consiste nel consentire la propagazione di un oggetto a ritroso, attraverso la sequenza corrente di chiamate tra metodi.
Opzionalmente, ogni metodo pu`o fermare la propagazione e gestire la condizione di errore utilizzando le informazioni trasportate, oppure continuare la propagazione ai metodi subito adiacenti nella sequenza di chiamate.
Ogni metodo che non sia in grado di gestire l’eccezione viene interrotto nel punto in cui aveva chiamato il metodo che sta propagando l’errore.
Se la propagazione raggiunge l’entry point della applicazione e non viene arrestata, l’applicazione viene terminata.
Propagazione
c l a s s E x a m p l e { d o u b l e m e t o d o 1 () {
d o u b l e d ;
d = 4 . 0 / m e t o d o 2 ();
S y s t e m . out . p r i n t l n ( d );
}
f l o a t m e t o d o 2 () { f l o a t f ;
f = m e t o d o 3 ();
f = f * f ; r e t u r n f ; }
int m e t o d o 3 () {
if ( c o n d i z i o n e ) r e t u r n e s p r e s s i o n e ; e l s e // g e n e r a un ’ e c c e z i o n e e p r o p a g a }
}
Immagino di chiamare metodo1() → eccezione!
Propagazione oggetti
Quali oggetti vengono propagati
Gli oggetti da propagare come eccezioni devono derivare dalla classe base java.lang.Exception.
Gli oggetti che vengono propagati sono derivati dalla classe java.lang.Throwable che contiene i metodi per la gestione dello stack tracing. I suoi 2 costruttori sono:
Throwable() Throwable(String)
Entrambi inizializzano lo stack, il secondo lo inizializza con un messaggio dettagliato, accessibile tramite toString().
Gestione della propagazione
Per poter gestire la propagazione dell’oggetto lungo la sequenza delle chiamate, i due costruttori effettuano una chiamata al metodo public Throwable fillInStackTrace()
il quale registra lo stato dello stack di sistema.
Il metodo
public void printStackTrace()
consente invece di stampare sullo standard error la sequenza restituita dal metodo precedente.
Exception/Throwable
La classe Exception `e derivata dalla Throwable che, a sua volta, deriva da Object.
Le altre eccezioni del sistema derivano tutte da Exception.
Esempio eccezione
c l a s s C l a s s e E s e m p i o {
p u b l i c s t a t i c v o i d m a i n ( S t r i n g [] a r g v ) { m e t o d o 1 (n u l l);
}
s t a t i c v o i d m e t o d o 1 (int[] a ) { m e t o d o 2 ( a );
}
s t a t i c v o i d m e t o d o 2 (int[] b ) { S y s t e m . out . p r i n t l n ( b [ 0 ] ) ; }
}
Exception in thread "main" java.lang.NullPointerException at ClasseEsempio.metodo2(ClasseEsempio.java:14)
at ClasseEsempio.metodo1(ClasseEsempio.java:9) at ClasseEsempio.main(ClasseEsempio.java:5)
Eccezioni personalizzate
c l a s s O u t O f D a t a E x c e p t i o n e x t e n d s E x c e p t i o n { S t r i n g e r r o r m e s s a g e ;
p u b l i c O u t O f D a t a E x c e p t i o n ( S t r i n g s ) { s u p e r( s );
e r r o r m e s s a g e = s ; }
p u b l i c O u t O f D a t a E x c e p t i o n () { s u p e r();
e r r o r m e s s a g e = " O u t O f D a t a E x c e p t i o n " ; }
p u b l i c S t r i n g t o S t r i n g () { // da O b j e c t r e t u r n e r r o r m e s s a g e ;
}
throw
Le eccezioni vengono propagate a ritroso attraverso la sequenza dei metodi chiamanti tramite l’istruzione throw, che ha sintassi:
throw Object instance
dove Object instance `e un oggetto di tipo Throwable.
Questo metodo causa la terminazione del metodo corrente (come se fosse stata utilizzata l’istruzione return), ed invia l’oggetto specificato al metodo chiamante.
Clausola throws
Questo metodo causa la terminazione del metodo corrente (come se fosse stata utilizzata l’istruzione return), ed invia l’oggetto specificato al metodo chiamante.
r e t u r n _ t y p e m e t h o d _ n a m e ( p a r a m _ l i s t ) t h r o w s T h r o w a b l e _ t y p e {
M e t h o d B o d y }
Immaginiamo di gestire un’eccezione nella classe Stack:
v o i d p u s h (int i ) t h r o w s O u t O f D a t a E x c e p t i o n { if ( first < m a x s i z e ) {
d a t a [ f i r s t ] = i ; f i r s t ++;
}
e l s e t h r o w new O u t O f D a t a E x c e p t i o n ( " E r r o r e ins . " );
Istruzioni try/catch
Per evitare che l’eccezione generata si propaghi fino all’entry point e causi cos`ı la terminazione della applicazione, si usano le istruzioni try, catch e finally (istruzioni guardiane).
try {
i s t r u z i o n i }
c a t c h ( E x c e p t i o n v a r 1 ) { i s t r u z i o n i
}
c a t c h ( E x c e p t i o n v a r 2 ) { }
f i n a l l y {
i s t r u z i o n i e s e g u i t e s e m p r e }
Se una istruzione nel blocco try genera un’eccezione, le rimanenti istruzioni nel blocco non vengono eseguite. L’esecuzione di un
Esempio try/catch
try {
f1 (); // Una e c c e z i o n e in q u e s t o p u n t o fa s a l t a r e f2 () // e f3 ()
f2 (); // Una e c c e z i o n e in q u e s t o p u n t o fa s a l t a r e f3 () f3 ();
}
c a t c h ( I O E x c e p t i o n _e ) {
S y s t e m . out . p r i n t l n ( _e . t o S t r i n g ( ) ) ; }
c a t c h ( N u l l P o i n t e r E x c e p t i o n _ n p e ) { S y s t e m . out . p r i n t l n ( _ n p e . t o S t r i n g ( ) ) ; }
Eccezioni multiple
Dato che tutte le eccezioni sono derivate da Exception, possiamo catturare eccezioni multiple come mostrato nel seguente codice:
try {
f1 (); // Una e c c e z i o n e in q u e s t o p u n t o fa s a l t a r e f2 () // e f3 ()
f2 (); // Una e c c e z i o n e in q u e s t o p u n t o fa s a l t a r e f3 () f3 ();
}
c a t c h ( j a v a . l a n g . E x c e p t i o n _e ) { // S i m i l e a d e f a u l t s w i t c h S y s t e m . out . p r i n t l n ( _e . t o S t r i n g ( ) ) ;
}
c a t c h ( N u l l P o i n t e r E x c e p t i o n _ n p e ) { // I n u t i l e ! S y s t e m . out . p r i n t l n ( _ n p e . t o S t r i n g ( ) ) ;
}
finally
Utilizzo
Questo blocco vuole fornire ad un metodo la possibilit`a di eseguire sempre un certo insieme di istruzioni a prescindere da come il metodo manipola le eccezioni.
I blocchi finally non possono essere evitati dal controllo di flusso della applicazione. Le istruzioni break, continue o return
all’interno del blocco try o all’interno di un qualunque blocco catch verranno eseguite solo dopo l’esecuzione del codice nel blocco finally.
Solo una chiamata del tipo System.exit() ha la capacit`a di evitare l’esecuzione del blocco di istruzioni in questione.
Ereditariet` a avanzata
Uno dei limiti pi`u comuni della ereditariet`a singola `e che non prevede l’utilizzo di una classe base come modello puramente concettuale, ossia priva della implementazione delle funzioni base.
Inoltre JAVA non consente l’ereditariet`a multipla come in C++.
JAVA risolve questo problema ricorrendo all’uso di interfacce e classi astratte.
interface: non contengono implementazioni delle funzionalit`a descritte
abstract: consentono di non implementare tutte le caratteristiche dell’oggetto rappresentato
Polimorfismo `e la terza parola chiave del paradigma ad oggetti:
un’interfaccia, molti metodi.
Ci`o significa che possiamo definire una interfaccia unica da utilizzare in molti casi collegati logicamente tra di loro.
Interface
Definizione
Una interfaccia Java rappresenta un prototipo e consente al programmatore di definire lo scheletro di una classe: nomi dei metodi, tipi ritornati, lista dei parametri.
Se vengono definiti dati membro primitivi, essi vengono considerati come static, final.
Dichiarazione interface
i n t e r f a c e i d e n t i f i c a t o r e { c o r p o _ i n t e r f a c c i a
}
Caso reale:
i n t e r f a c e S t a c k {
p u b l i c v o i d p u s h (int i );
p u b l i c int pop ();
}
Implementazione interface
c l a s s n o m e _ c l a s s e i m p l e m e n t s n o m e _ i n t e r f a c c i a { c o r p o _ i n t e r f a c c i a
}
Esempio:
p u b l i c i n t e r f a c e S t a c k D e f { p u b l i c v o i d p u s h (int i );
p u b l i c int pop ();
}
c l a s s S t a c k i m p l e m e n t s S t a c k D e f { p u b l i c v o i d p u s h (int i ) {
...
}
p u b l i c int pop () { ...
} }
Implementazione
Come si implementa
Quando una classe implementa una interfaccia `e obbligata ad implementarne i prototipi dei metodi definiti nel corpo. In caso contrario il compilatore generer`a un messaggio di errore.
Conseguenza diretta sar`a la possibilit`a di utilizzare le interfacce come tipi per definire variabili reference in grado di referenziare oggetti costruiti mediante implementazione di una interfaccia.
Se l’operatore extends limitava la derivazione di una classe a partire da una sola classe base, l’operatore implements ci consente di implementare pi`u di una interfaccia (ereditariet`a multipla).
Classi abstract
Definizione
Capitano casi in cui solo una parte della interfaccia base debba essere implementata. In questo caso si usano le classi di tipo abstract.
Per estendere le classi abstract si usa l’istruzione extends.
Le abstract non sono “complete“, quindi non possono essere istanziate.
a b s t r a c t c l a s s n o m e _ c l a s s e { d a t a _ m e m b e r s
a b s t r a c t _ m e t h o d s n o n _ a b s t r a c t _ m e t h o d s }
Quando una nuova classe viene derivata a partire dalla classe astratta, il compilatore richiede che tutti i metodi astratti vengano definiti. Se la necessit`a del momento costringe a non definire questi
interface vs abstract
interface abstract
Istanziabile no no
Fields static/final s`ı
Costruttore no s`ı
Metodi statici java8+ s`ı
Dichiarazione metodi no s`ı
Implementazione metodi java8+ (default) s`ı
Esempio4
Usare un’interfaccia per implementare Veicolo.
Thread
Multithread in Java
Java `e un linguaggio multihread che consente di creare applicazioni in grado di utilizzare la concorrenza logica tra i processi,
continuando a condividere tra i thread lo spazio in memoria riservato ai dati.
Dal punto di vista dell’utente, i thread logici appaiono come una serie di processi che eseguono parallelamente le loro funzioni.
Dal punto di vista della applicazione rappresentano una serie di processi logici che, da una parte condividono la stessa memoria della applicazione che li ha creati, dall’altra concorrono con il processo principale al meccanismo di assegnazione della CPU.
Osservazioni
Esistono dei thread che vengono eseguiti senza che l’utente se ne renda conto:
Thread per la gestione delle interfacce grafiche che si occupa della gestione degli eventi e di aggiornare i contenuti grafici.
Il garbage collector `e un thread responsabile di trovare gli oggetti non pi`u referenziati e quindi da eliminare dallo spazio di memoria della applicazione.
Lo stesso metodo main() di una applicazione viene avviato come un thread sotto il controllo della Java Virtual Machine.
java.lang.Thread
In Java, un modo per definire un oggetto thread `e quello di utilizzare l’ereditariet`a derivando il nuovo oggetto dalla classe base java.lang.Thread.
Metodi di java.lang.Thread
run(): `e il metodo utilizzato per implementare le funzionalit`a eseguite thread → override!
start(): causa l’esecuzione del thread
destroy(): distrugge il thread e rilascia le risorse allocate (deprecated)
Esempio di thread
c l a s s P r i m o T h r e a d e x t e n d s T h r e a d { int s e c o n d i ;
P r i m o T h r e a d (int s e c o n d i ) { t h i s. s e c o n d i = s e c o n d i ; }
p u b l i c v o i d run () { // Fai q u a l c o s a }
}
Interfaccia Runnable
PROBLEMA: supponiamo ora che la nostra classe PrimoThread sia stata definita a partire da una classe generale diversa da java.lang.Thread. A causa dei limiti stabiliti dalla ereditariet`a singola, sar`a impossibile creare un thread utilizzando lo stesso meccanismo definito...
Usando le interfacce si pu`o ovviare a questo problema
p u b l i c i n t e r f a c e R u n n a b l e { p u b l i c v o i d run ();
}
Soluzione del problema
c l a s s C l a s s e B a s e { C l a s s e B a s e () {
...
}
p u b l i c v o i d f a i Q u a l c o s a () { ...
} }
e quindi
c l a s s P r i m o T h r e a d e x t e n d s C l a s s e B a s e i m p l e m e n t s R u n n a b l e { int s e c o n d i ;
...
p u b l i c v o i d run () { // Fai q u a l c o s a }
Per lanciare il thread
p u b l i c c l a s s L a n c i a T h r e a d {
p u b l i c s t a t i c v o i d m a i n ( S t r i n g [] a r g s ) { P r i m o T h r e a d t = new P r i m o T h r e a d ( 1 0 ) ; t . s t a r t (); // C h i a m a il m e t o d o run () }
}
Sincronizzazione ed accesso ai dati
Concorrenza
Quando due o pi`u thread possono accedere ad un oggetto
contemporaneamente per modificarlo, il rischio a cui si va incontro
`e quello della corruzione dei dati rappresentati dall’oggetto utilizzato tra i thread in regime di concorrenza.
`E necessario che due (o pi`u) thread vengano sincronizzati, ovvero che mentre uno esegue l’operazione, l’altro deve rimanere in attesa.
Java fornisce un metodo per gestire la sincronizzazione tra thread mediante la parola chiave synchronized.
Come si usa synchronized
Il modificatore synchronized deve essere aggiunto alla
dichiarazione del metodo per assicurare che solo un thread alla volta sia in grado di utilizzare i dati.
c l a s s Set {
p r i v a t e int[] d a t a ; ...
b o o l e a n i s M e m b e r (int n ) {
// c o n t r o l l a se l ’ i n t e r o e ’ n e l l ’ i n s i e m e }
v o i d add (int n ) {
// a g g i u n g e n all ’ i n s i e m e }
}
Immaginiamo che 2 thread accedano ai dati di questa classe in manieraconcorrente→ dati corrotti!