































Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Piccolo riassunto java breve sui fondamenti di oop
Tipologia: Appunti
1 / 39
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!
































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).
Una classe in java si definisce con la keyword class:
Qualificatore Descrizione protected accessibile solo dalle classi derivate, ne capiremo il significato in seguito 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).
Esistono altri 2 possibilità circa la visibilità:
Per convenzione per i nomi dei tipi, ovvero per le classi, la convenzione prevede invece che la
prima lettera sia maiuscola).
Esempio:
public class Conto { private double amount; private String owner; // costruttore public Conto(String owner, double initialAmount) { this.owner = owner; this.amount = initialAmount; }
Di particolare rilevanza è il metodo contrassegnato con il commento // costruttore: è diverso dagli altri metodi 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.
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).
È noto come operatore membro per accedere al membro o al metodo di un pacchetto o di una classe, ovviamente se il membro/ metodo lo permette (se il membro/metodo eprivate non e possibile accedere) .Una volta costruito l’oggetto conto sarà possibile utilizzare i suoi metodi con la notazione:
conto.versamento(10); conto.prelievo(100);
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.
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
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.
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.
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’.
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.
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.
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.
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 (‘_‘);
I tipi primitivi in Java
Il tipo di una variabile può essere costruito dallo sviluppatore per composizione a partire da un set 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.
Sono supportati anche alcune speciali rappresentazioni (dette escape sequences o sequenze di escape): Escape Carattere \b backspace (indietro) \t tab \n line feed (fine linea) \f form feed (fine pagina / nuova pagina)
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; 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.
Casting
l costrutto if in Java
Switch-case
switch(c) {
case value1:
...
break;
case value2:
...
break;
// eventuali altri case
case valueN:
...
default:
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:
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. 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).
Si osserva che essendo inizializzazione, incremento e condizioneopzionali 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)”.
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
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 coll = new ArrayList
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. Lo statement return merita una certa attenzione anche perché, pur essendo strettamente legato ai
metodi, potremmo accomunarlo a breake continue introdotti in precedenza. Infatti anche
l’esecuzione del return provoca un salto nell’esecuzione, in questo caso un salto fuori dal metodo.
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. Senza dilungarci troppo, anche throw termina l’esecuzione del blocco corrente come return ma
senza che debba essere specificato un valore di ritorno.
Modificatori di visibilità
Metodi final
Metodi 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:
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:
Tipo di metodo Sintassi Statico NomeClasse.nomeMetodo(...) 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.
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 chiudiamo 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 int[][] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9, 10, 11 } }; L’ultima riga mostra anche come sia possible 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]; } 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.util.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 complicanza sintattica degli array multidimensionali), il loro utilizzo è estremamente comune in
molti ambiti. Perciò Java mette a disposizione la classe java.util.Arrays con numerosi algoritmi per operare sugli array:
e altri strumenti per la manipolazione, tutti sotto forma di metodi statici.
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.
Stringa = array di caratteri
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";
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 boolean contains(CharSequence s) ritorna true se e solo se la stringa contiene la sequenza di caratteri specificati dal parametro s boolean equals(Object anObj) confronta la stringa con l’oggetto obj specificato boolean isEmpty() ritorna true se e solo se la lunghezza della stringa è 0 String[] split(String regex) suddivide la stringa intorno ad ogni occorrenza con l’espressione regex e ritorna array con tutte le sottostringhe String trim() ritorna una copia della stringa di partenza eliminando tutti gli spazi bianchi all’inizio e alla fine della stringa
Caratteristiche dello “stile” object oriented
Si individuano solitamente nello stile Object Oriented quattro elementi caratterizzanti:
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.
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.