Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Riassunto java breve di oop, Appunti di Programmazione Java

Piccolo riassunto java breve sui fondamenti di oop

Tipologia: Appunti

2018/2019

Caricato il 29/08/2019

francesco-pistorio
francesco-pistorio 🇮🇹

4.8

(8)

17 documenti

1 / 39

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
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:
il corpo (dichiarazioni di variabili e metodi) della classe è racchiuso tra parentesi graffe;
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
private Significa che sarà visibile (accessibile se si tratta di un field, chiamabile se si
tratta di un metodo) solo all’interno della classe che lo contiene.
public significa invece che sarà accessibile anche dall’esterno della classe stessa.
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).
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27

Anteprima parziale del testo

Scarica Riassunto java breve di oop e più Appunti in PDF di Programmazione Java solo su Docsity!

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:

  • il corpo (dichiarazioni di variabili e metodi) della classe è racchiuso tra parentesi graffe ;
  • 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 private Significa che sarà visibile (accessibile se si tratta di un field, chiamabile se si tratta di un metodo) solo all’interno della classe che lo contiene. public significa invece che sarà accessibile anche dall’esterno della classe stessa.

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).

Costruttore

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.

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 ‘.’

È 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.

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

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.

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’.

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.

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 (‘_‘);

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

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;

\

l costrutto if in Java

I niziamo 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

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.

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:

  • 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. 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).

Cicli infiniti

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)”.

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 queue = new LinkedList(); for(Iterator it = queue.iterator(); it.hasNext(); ) { String tmp = it.next(); // ... qui fa qualcosa }

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(); // 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); } } } Per semplicità lavoriamo con un array di String (l’array degli argomenti, args, del main) e una Collection 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.

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à

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 determinano 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).

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:

  • 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:

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:

  • ricerca;
  • sorting (ordinamento);
  • copia;

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:

  • 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.

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.