IL LINGUAGGIO JAVA
Dispense per il corso di laboratorio di sistemi – I.T.I.S. ABACUS – A.S. 2008/2009
Autore:
Roberto Amadini
Testo di riferimento: La programmazione ad oggetti C++ Java (Lorenzi, Moriggia, Rizzi – ATLAS)
2.
IL LINGUAGGIO JAVA
2.1
Introduzione
Java è un linguaggio di programmazione ad alto livello orientato agli oggetti.
Il processo di sviluppo di Java cominciò agli inizi degli anni ’90 da un gruppo di ingegneri della Sun
Microsystems, mentre la sua prima apparizione ufficiale avvenne nel maggio del 1995 .
Le principali caratteristiche di Java sono:
•
•
•
•
L’orientamento agli oggetti
La portabilità delle sue applicazioni
La possibilità di costruire GUI (Graphical User Interface)
La gestione di multithreading e networking
L’ORIENTAMENTO AGLI OGGETTI consiste nella presenza di costrutti che implementano concetti propri
della OOP come ereditarietà, polimorfismo, incapsulamento, gestione delle eccezioni ecc…
Java è un linguaggio OO di tipo ibrido in quanto prevede la possibilità di gestire tipi di dato primitivi oltre
agli oggetti (ad esempio int, char, float, double ecc…).
Tuttavia spesso Java viene considerato come linguaggio puro in quanto gli oggetti costituiscono la struttura
portante del linguaggio, mentre i tipi primitivi ne rappresentano solo una parte marginale.
Di fatto questo linguaggio deriva direttamente dal C++, per cui molte caratteristiche di Java sono del tutto
simili (se non addirittura identiche) al C++.
Le maggiori differenze a livello di programmazione tra Java e C++ riguardano:
•
L’EREDITARIETA’: In Java si utilizza solamente l’ereditarietà SINGOLA (cioè una sottoclasse può
avere al massimo una classe padre) mentre il C++ permette l’ereditarietà MULTIPLA (una
sottoclasse può derivare da più classi padre).
In realtà anche Java implementa un concetto simile all’ereditarietà multipla, grazie all’ausilio di
classi interfaccia (interfaces) opportunamente implementate (implements).
•
La gestione della MEMORIA DINAMICA: In Java, fatta eccezione per i tipi primitivi, ogni variabile
(array e oggetti) è di tipo riferimento (reference): le variabili contengono cioè un riferimento
all’indirizzo di memoria dove sono allocati i dati (e i metodi) contenuti dalle variabili.
Al contrario del C++, non è possibile utilizzare esplicitamente i puntatori per manipolare
l’allocazione e la de-allocazione dinamica di oggetti in memoria; in particolare la de-allocazione è
gestita in modo automatico dal sistema di run-time mediante un apposito strumento chiamato
garbage collector (letteralmente “raccoglitore di rifiuti”, si occupa di “ripulire” la memoria).
•
La forte TIPIZZAZIONE: In Java le conversioni di tipo (cast) devono essere tutte esplicitate (al
contrario il C++ permette in taluni casi conversioni non esplicitate).
Fanno eccezione le promozioni di tipo (conversioni senza perdita di dati, ad esempio da int a float).
La PORTABILITA’ è una proprietà fondamentale di Java; essa consiste nella capacità di poter eseguire
codice Java su qualsiasi piattaforma in modo indipendente dall’hardware e dal sistema operativo sul quale
il programma è in esecuzione.
La portabilità in Java è realizzata con un sistema misto di compilazione/interpretazione:
•
•
•
Il codice sorgente (estensione .java) viene compilato dal compilatore di java (comando javac) che
trasforma il codice sorgente in un apposito codice detto BYTECODE (estensione .class)
Il bytecode non può essere eseguito da una macchina reale ma solamente da una macchina
virtuale: la JAVA VIRTUAL MACHINE (JVM).
La JVM è uno strato software che si pone “sopra” la macchina fisica dove è in esecuzione il file;
il suo compito consiste nel permettere l’esecuzione del bytecode anche sulla macchina fisica
interpretando ogni sua riga, cioè traducendo il bytecode (comprensibile solo dalla JVM) in codice
macchina eseguibile sulla particolare macchina fisica (comando java).
In questo modo, posso eseguire un file .class di java su qualsiasi piattaforma senza bisogno di
dover modificare e/o ricompilare il codice sorgente: la JVM della particolare macchina fisica si
occuperà del processo di interpretazione.
CODICE
SORGENTE
COMPILAZIONE
BYTECODE
JVM
INTERPRETAZIONE
MACCHINA FISICA
MioFile.java javac MioFile.Java MioFile.class java MioFile
Eseguo MioFile
Questa soluzione tuttavia è poco efficiente, in quanto l’interpretazione ha tempi decisamente maggiori
rispetto alla compilazione. Per questo motivo, tutte le implementazioni recenti di JVM hanno incorporato
un compilatore just-in-time (JIT compiler), cioè un compilatore interno, che al momento del lancio traduce
“al volo” il programma bytecode Java in un normale programma nel linguaggio macchina del computer
ospite (cioè anziché compilare il sorgente ed interpretare il corrispondente bytecode, di fatto compilo il
sorgente e poi grazie al JIT ri-compilo il corrispondente byte code).
Questi accorgimenti, a prezzo di una piccola attesa in fase di lancio del programma, permettono di avere
delle applicazioni Java decisamente più veloci e leggere. Tuttavia, anche così Java resta un linguaggio meno
efficiente dei linguaggi compilati come il C++, scontando il fatto di possedere degli strati di astrazione in
più (la JVM) e di implementare una serie di automatismi, come il garbage collector, che se da un lato fanno
risparmiare tempo ed errori in fase di sviluppo dei programmi, dall'altro sprecano spazio di memoria e
tempo di CPU in fase di esecuzione del programma.
Per poter programmare ed eseguire codice Java gli strumenti necessari sono:
•
•
•
2.2
Un EDITOR di testo per scrivere il codice (ad es. Blocco note, emacs…)
Un COMPILATORE (javac) ed un INTERPRETE java (java). Tali strumenti fanno parte del JDK (Java
Development Kit), che comprende inoltre altre funzionalità per lo sviluppo di codice java (ad es.,
strumenti per la documentazione automatica del codice). Ad ogni JDK si associa quindi una JVM per
la traduzione del bytecode e un JRE (Java Run-Time Environment) che permette l’esecuzione
effettiva delle applicazioni Java sulla macchina fisica.
Esistono tuttavia AMBIENTI INTEGRATI (IDE, Integrated Development Environment) che integrano
Editor, JDK, JVM e JRE. Tra i più diffusi attualmente citiamo NetBeans ed Eclipse.
La struttura di un programma Java
In Java, a differenza del C++, un applicazione può essere costituita da una o più classi. Tra tutte le classi che
compongono un applicazione Java, una si differenzia dalle altre perché:
•
•
Ha lo stesso nome del file sorgente che contiene la classe
Contiene lo speciale metodo main. Questo metodo è fondamentale perché l’esecuzione
dell’applicazione Java comincia a partire da questo metodo.
Vediamo quindi lo scheletro di un generico programma prova.java costituito da una sola classe:
/* Importo le librerie (package) necessarie */
import Package_1;
...
import Package_n;
/*
Classe principale, contiene il main e deve avere lo stesso nome del
file sorgente.
*/
class prova {
/* Metodo principale, necessario per l’esecuzione */
public static void main(String args[]) {
// dichiarazione variabili
// istruzioni del programma
} // fine main
} // fine classe nomeFile
I package Java
I package di Java sono insiemi di classi che forniscono servizi simili. Ogni utente può creare un proprio
package oppure importare un package pre-esistente. I package di base di java (J2SE 6.0) sono:
•
•
•
•
•
•
•
•
java.lang
java.util
java.io, java.nio
java.math
java.net
java.security
java.sql
java.awt, java.swing
funzionalità di base del linguaggio e tipi di dato fondamentali
classi di collezione (strutture dati quali Vector, Stack, List, ecc…)
operazioni si input/output
operazioni aritmetiche in multi precisione
operazioni di networking
gestione di sicurezza e crittografia
operazioni su basi di dati
gestione di interfaccie GUI
Per importare un package predefinito si usa l’istruzione import nomePackage; in questo modo sarà
possibile utilizzare tutte le funzionalità del package specificato.
Di fatto quest’istruzione corrisponde alla direttiva di pre-processing #include che è possibile utilizzare
in C++ per includere librerie o file esterni al sorgente.
2.3
Il metodo main
Come per il C++, il metodo main è fondamentale per il programma, in quanto da esso parte l’esecuzione del
codice. A differenza del C++, nel quale il main è una funzione, in Java il main è un metodo: in sostanza è una
funzione propria della classe che lo contiene (nel nostro esempio la classe prova, che deve necessariamente
avere lo stesso nome del sorgente in cui è contenuta: prova.java).
In Java infatti non esistono funzioni (al contrario del C++, essendo tale linguaggio fortemente legato al
linguaggio procedurale C) ma solamente metodi: ogni “funzione” quindi deve appartenere ad una certa
classe. Per questo motivo Java viene spesso considerato linguaggio object-oriented puro.
Nell’esempio inoltre il metodo main è preceduto da tre parole-chiave:
•
•
public
static
•
void
indica che il metodo è pubblico, cioè visibile ed accessibile da chiunque.
indica che il metodo è statico, cioè associato alla classe in cui è contenuto (prova) e
non ad oggetti della classe. Per ora ci basti sapere solo questo…
indica che non ritorna nulla (esattamente come in C++)
Inoltre, il main possiede anche un parametro: String args[]
Come si può intuire, tale parametro è un array di stringhe, che corrisponde ai parametri passati da riga di
comando quando l’applicazione viene lanciata.
Basandoci su quanto appena visto, scriviamo ora il nostro primo programma in Java:
/*
Questo semplice programma legge una stringa da riga di comando e
la stampa a video. Non importo nessuna libreria, in quanto il
programma non lo richiede (non utilizzo costrutti particolari)
*/
class primoProg {
public static void main(String args[]) {
String stringaLetta = args[0];
System.out.println(
“Hai inserito la stringa: “ + stringaLetta);
} // fine main
} // fine classe primoProg
Innanzitutto si noti che la sintassi e la semantica dei commenti ( /* … */ e // ) è identica al C++.
Inoltre è importante sottolineare che come il C++ anche Java è case-sensitive: non è indifferente utilizzare
le maiuscole anziché le minuscole e viceversa.
Il sorgente java si chiamerà primoProg.java (stesso nome della classe contenente il main).
Il nostro programma per prima cosa dichiara una variabile di tipo String (stringa di caratteri, cfr. tipo string
in C++) di nome stringaLetta che inizializza con il valore di args[0].
Cosa significa? In pratica, quando l’utente da riga di comando esegue il programma, può aggiungere uno o
più parametri di tipo stringa che vengono automaticamente inseriti nell’array args.
Quindi, all’interno del main è possibile estrarre tali parametri; nel nostro esempio estraggo il valore di
args[0](che corrisponderà al primo parametro inserito) e lo inserisco nella variabile stringaLetta.
Dopodiché, stampo a video un messaggio contenente il valore di stringaLetta.
Per fare ciò utilizzo il metodo println della classe System.out che permette di stampare su
standard output (il video) tale messaggio. Ciò è l’equivalente del cout in C++; si noti tuttavia il mancato
utilizzo dell’operatore <<: la concatenazione delle stringhe “Hai inserito la stringa: “ e
stringaLetta avviene mediante l’operatore +.
Vediamo ora come è possibile eseguire il nostro primo programma primoProg.java
Per scrivere il programma è sufficiente un qualsiasi editor di testo (ad es. Blocco note); ovviamente bisogna
ricordarsi di salvare il file con l’estensione .java
Una volta scritto, il programma va compilato: apriamo quindi il prompt dei comandi di MS-DOS e, dopo
esserci posizionati nella cartella dove è presente il file primoProg.java, digitiamo:
javac primoProg.java
A questo punto, se non ci sono errori, viene generato il bytecode del sorgente: primoProg.class
Ora per eseguire il programma è sufficiente richiamare l’interprete Java che trasforma il bytecode in
linguaggio macchina:
java primoProg
Il programma verrà quindi eseguito, stampando a video il messaggio: Hai inserito la stringa:
Perché? Semplicemente perché non abbiamo inserito alcun parametro da riga di comando!
Se invece provassimo a digitare (senza dover ricompilare!)
java primoProg Ciao!!!
Allora l’output del programma sarà:
Hai inserito la stringa: Ciao!!!
In questo caso infatti args[0]conterrà la stringa Ciao!!!
E se invece scrivessimo da riga di comando:
java primoProg Ciao mondo!!!
In questo caso l’output sarà: Hai inserito la stringa: Ciao
Questo perché i parametri passati da riga di comando sono due: Ciao e mondo!!!
Tali parametri verranno quindi inseriti rispettivamente in args[0] e args[1], ma siccome il
programma stampa solamente il primo parametro (Ciao), il secondo (mondo!!!) viene ignorato.
2.3
Gli identificatori
Gli identificatori sono nomi (sequenze di lettere e numeri) che possono essere attribuiti a variabili, metodi,
classi ed oggetti.
Come per il C++ non tutti gli identificatori sono formalmente corretti: esistono delle regole ben precise (ad
es. devono cominciare con una lettera, non possono contenere caratteri speciali, devono avere una
lunghezza massima, ecc…).
In particolare, un identificatore non può chiamarsi come una parola-chiave di Java, in quanto tali parole
sono riservate per particolari scopi. Le parole-chiave di Java sono ad oggi (J2SE 6.0) cinquanta:
abstract
assert
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
enum
extends
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
Le celle evidenziate sono parole chiave anche del C++.
new
package
private
protected
public
return
short
static
strictfp
super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while
2.4
Variabili, costanti e tipi di dato
La sintassi e la semantica delle variabili è molto simile al C++; una variabile è caratterizzata da un nome, un
tipo, un valore e dalla sua visibilità (scope). Ad esempio:
int num;
float altezza = 1.68;
boolean x = true; //equivalente al tipo bool in C++
byte temperatura; //intero a 8 byte (può contenere valori da -128 a 127)
Come nel C++ esistono regole di scope per le variabili.
In sintesi una variabile è visibile all’interno del blocco in cui è dichiarata ed invisibile all’esterno del blocco;
inoltre a differenza del C++ non è possibile dichiarare variabili con lo stesso nome all’interno di blocchi
annidati, cioè non è possibile avere una situazione di questo tipo:
{
int x;
...
{
int x; //ri-dichiarazione di x: NON PERMESSO IN JAVA!
...
} //blocco interno
} //blocco esterno
Una costante è una variabile che deve assumere sempre lo stesso valore per tutta la durata del
programma; in Java anziché la parola chiave const si utilizza final, ad esempio:
final double PI_GRECO = 3.14;
Tipicamente in Java le variabili sono scritte sempre in minuscolo, mentre la maiuscola viene usata al posto
dello spazio (ad es. numeroPersone, minimoComuneMultiplo, valoreAssoluto).
Al contrario, le costanti sono scritte in maiuscolo, mentre al posto dello spazio si usa il carattere di
underscore (ad es. TEMP_MAX, NUM_DI_NEPERO, ALIQUOTA_IVA).
I tipi di dato in Java si suddividono in:
•
•
tipi primitivi: byte, short, int, long, float, double, char, boolean.
tipi riferimento: sono gli array e le classi , ce ne occuperemo più avanti.
I tipi primitivi sono gli stessi del C++ a differenza di boolean e byte; in Java tuttavia non esistono i
numeri unsigned (cioè senza segno, sempre positivi).
La differenza fondamentale col C++ sta nella forte tipizzazione di Java: le conversioni (cast) tra tipi diversi
devono essere quasi sempre esplicitate (fanno eccezione per le promozioni di tipo, cioè conversioni senza
perdita di dati, ad es. int float, byte double ecc…):
float piGreco = 3.14;
int pg = piGreco; //errore di compilazione!
Le precedenti istruzioni generano un errore a tempo di compilazione in quanto sto convertendo una
variabile float in una variabile int (ho una perdita di dati, perciò non ho una promozione) senza avere
esplicitato il cast. La versione corretta dell’assegnamento è quindi:
float piGreco = 3.14;
int pg = (int) piGreco; //converto piGreco nell’intero pg: pg = 3
2.5
Operatori logici e matematici
Anche in questo caso la sintassi e la semantica è molto simile al C++; gli operatori utilizzati da Java sono:
=
==
+
+=
>
<=
−
−=
<
>=
*
*=
!
!=
/
/=
~
&&
&
&=
?:
||
|
|=
++
^
^=
−−
%
%=
<<
<<=
>>
>>=
>>>
>>>=
Le celle evidenziate sono quelle di maggior interesse per i nostri scopi
Vale la pena ricordare che gli operatori non evidenziati in tabella servono per operazioni bit-a-bit (bit-wise)
tra numeri binari.
Ad esempio, << e >> in Java non servono per l’input/output ma per shiftare bit a sinistra e a destra; ~
calcola il complemento ad uno, ^ l’or esclusivo (XOR).
2.6
La gestione dell’input/output
Abbiamo già visto nel programma precedente un esempio di scrittura in output:
System.out.println(“Hai inserito la stringa: “ + stringaLetta);
System.out è un oggetto associato allo standard output (video); viene quindi richiamato il metodo
println di tale classe per poter stampare il parametro contenuto tra parentesi (due stringhe di testo
concatenate in questo caso)e poi andare a capo.
Tale parametro può essere tuttavia anche un numero intero o reale, un carattere o un booleano.
L’oggetto System.out possiede inoltre altri metodi; ad esempio il metodo print(x) stampa il
valore di x senza poi andare a capo.
Analogamente a System.out esistono gli oggetti System.err (associato allo standard error, il video)
e System.in (associato allo standard input, tastiera).
Tuttavia l’utilizzo diretto di quest’ultimo non è conveniente; si preferisce quindi combinarlo con oggetti più
potenti quali InputStreamReader e BufferedReader nel seguente modo:
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader tastiera = new BufferedReader(input);
Con queste istruzioni definisco prima un oggetto input di classe inizializzato con System.in; quindi
creo un altro oggetto tastiera di classe BufferedReader inizializzato con input.
Questo mi serve per sfruttare al meglio le potenzialità della classe BufferedReader ; ad esempio il
metodo readLine() di tale classe permette di leggere da standard input una riga per volta.
Attenzione! Il metodo readLine() accetta solo stringhe: se si vogliono acquisire altri tipi di dato,
occorre fare il casting.
Inoltre, tale metodo deve poter gestire eventuali eccezioni. Un eccezione è una situazione anomala che si
verifica durante l’esecuzione del programma, come ad esempio una divisione per zero.
La gestione delle eccezioni verrà ripreso più avanti; per ora ci basti sapere che la lettura di un dato da
tastiera deve avvenite all’interno di un apposito blocco try-catch. Vediamo un esempio:
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader tastiera = new BufferedReader(input);
String nome;
System.out.println(“Come ti chiami?”);
try {
nome = tastiera.readLine();
} catch(Exception e) {}
In questo caso il programma chiede semplicemente all’utente il proprio nome da standard input e lo legge
nella variabile tastiera. L’istruzione di lettura readLine deve sempre essere compresa in un blocco
try seguito da un blocco catch (anche vuoto, come in questo caso).
Esempio: Scrivere un programma Java che chieda all’utente il proprio anno di nascita e, successivamente,
gli comunichi se è nato in un anno bisestile oppure no.
/*
Questo programma chiede all’utente il proprio anno di nascita e,
successivamente, gli comunica se è nato in un anno bisestile oppure
no.
*/
class esBisestile {
public static void main(String args[]) {
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader tastiera = new BufferedReader(input);
String annoStr;
int annoInt;
System.out.println(“In quale anno sei nato?”);
try {
annoStr = tastiera.readLine();
annoInt = Integer.valueOf(annoStr).intValue(); //cast
}
catch(Exception e) {
System.out.println(“Si è verificata un’eccezione!”);
}
if(annoInt % 4 == 0)
System.out.println(“Sei nato in un anno bisestile!”);
else
System.out.println(“Non sei nato in un anno bisestile!”);
} // fine main
} // fine classe esBisestile
Questo programma legge l’anno inserito da utente nella variabile annoStr. Tuttavia, come detto, l’input
da tastiera è sempre una sequenza di caratteri, per cui è necessario convertire annoStr in un intero per
poi poter valutare se si tratta di un anno bisestile oppure no. Ciò è effettuato con l’istruzione:
annoInt = Integer.valueOf(annoStr).intValue();
Questa espressione converte il valore di annoStr in un intero che poi assegna alla variabile annoInt .
Si tratta di due metodi della classe Integer: valueOf e intValue. Al momento non ci interessa sapere nel
dettaglio come viene effettuata la conversione; si ricordi solamente che ciò permette il casting.
Ad esempio le istruzioni:
float annoF = Float.valueOf(annoStr).floatValue();
double annoD = Double.valueOf(annoStr).doubleValue();
avrebbero permesso di convertire annoStr in variabili di tipo float e double rispettivamente.
Infine, si può notare come nella gestione delle eccezioni il blocco catch non sia vuoto: ciò significa che,
nel caso in cui si verifichi un eccezione, viene stampato a video il messaggio “Si è verificata un eccezione!”.
Ad esempio, in questo caso vi è eccezione se il valore inserito da utente non è un numero intero (ad es.
1984.567) oppure se inserisco una stringa di testo (ad es. MCMLXXXIV)
2.7
Le strutture di controllo
Le strutture di controllo in Java sono pressoché identiche al C++.
Nell’esempio precedente abbiamo visto l’utilizzo della struttura di selezione singola (if-else);
allo stesso modo del C++ è possibile utilizzare la selezione multipla attraverso il costrutto switch-case.
Per valutare la condizione in una struttura di selezione si utilizzano gli operatori logici (!, ||, &&) e
matematici (<, <=, >, >=, ==, !=, %) esattamente come in C++.
Tra le funzioni matematiche di uso comune fa invece eccezione la generazione di numeri (pseudo)casuali:
anziché utilizzare srand e rand, uso il metodo random() della classe Math:
double x = Math.random();
double y = Math.random() * 10;
int n = (int) ( Math.random() * 5 );
int m = (int) ( Math.random() * 4 + 6 );
Math.random() restituisce un numero casuale di tipo double tra 0.0 e 1.0 (ad es. 0.6633709….).
Nell’esempio precedente quindi x sarà un numero reale tra 0 e 1 mentre y sarà un numero reale compreso
tra 0 e 10 (moltiplico il numero estratto per 10).
Ovviamente è anche possibile arrotondare (o meglio troncare in questo caso) il numero estratto per
ottenere numeri interi; nell’esempio precedente n sarà un intero compreso tra 0 e 5 mentre m sarà
compreso tra 6 e 10.
Le strutture di ripetizione (cicli) non si differenziano dal C++, in particolare hanno la stessa sintassi e
semantica i cicli do-while, while e for.
Inoltre, come per il C++, è possibile modificare l’ordine di esecuzione dei cicli attraverso particolari
istruzioni: break e continue.
L’istruzione break forza l’uscita da un ciclo: se viene eseguita, l’esecuzione riprende
dalla prima istruzione presente dopo il ciclo (cfr. struttura switch-case).
L’istruzione continue invece forza il ritorno all’inizio del ciclo: se viene
eseguita, l’esecuzione riprende dalla prima istruzione del ciclo (nel caso di for e while prima però valuto la
condizione)
Nel malaugurato caso che si venga a creare un loop infinito, in Java è possibile bloccare l’esecuzione del
programma premendo simultaneamente i tasti Ctrl + C.
Esempio: Modo altamente masochista per visualizzare il valore di π approssimato a 5 cifre decimali.
int k = 0;
double d, pi = 1;
while( true ) {
d = (double) 2*k + 1;
if( k % 2 == 0 )
pi += 1/d;
else
pi -= 1/d;
k++;
if( k == 1000000 )
break;
}
System.out.println( 4 * pi ); //System.out.prinln(Math.PI)...
Esercizi
Implementare in codice Java i seguenti esercizi:
1.
2.
3.
4.
Scrivere un programma che stampi a video l’area di un triangolo data la base e l’altezza.
Inserita da utente una data (a partire dal 01/01/1900), verificare se è corretta o no.
Visualizzare sullo schermo 10 numeri casuali compresi tra due interi a e b letti da tastiera.
Inserire una sequenza di numeri terminata da 0. Al termine dell’inserimento visualizzare il
massimo, il minimo e la media dei numeri inseriti 0 escluso.
5. Inserire da utente il nome, il cognome e l’età di 5 ragazzi. Al termine dell’inserimento stampare
nome e cognome del ragazzo più giovane e il numero di ragazzi il cui cognome comincia con una
vocale.
2.8
Gli array
Un array è una collezione finita di elementi omogenei (stesso tipo). Gli array monodimensionali spesso
prendono il nome di vettori, mentre quelli bidimensionali prendono il nome di matrici.
Un array è realizzato mediante un puntatore che punta all'area di memoria dove si trovano i suoi elementi;
ciò significa che se dichiaro un array di nome V, la variabile V non conterrà il valore dei suoi elementi ma
l'indirizzo di memoria del suo primo elemento.
Tuttavia, come già sottolineato, Java non permette l'utilizzo esplicito di puntatori (gestione automatica
della memoria) che quindi vengono mascherati (hiding) dal linguaggio.
Nonostante ciò è importante notare che un array non è un tipo primitivo, bensì un tipo reference: ciò
significa che una variabile di tipo array non contiene un valore ma un riferimento ai valori contenuti
nell'array (indirizzo di memoria del suo primo elemento).
Per poter creare ed utilizzare un array si devono seguire tre passaggi fondamentali:
•
•
•
DICHIARAZIONE
ALLOCAZIONE
INIZIALIZZAZIONE
La dichiarazione consiste nella specificazione del nome dell'array e del tipo di dati che esso contiene. Un
array può contenere sia tipi primitivi (ad es. int, double, ecc...) che tipi riferimento (classi oppure array
stessi). La sintassi è la seguente:
tipoElementi nomeArray[];
Ad es.
int voti[]; //oppure int[] voti;
String nomi[]; //oppure String[] nomi;
Come si può notare, l'operatore [ ] può essere inserito equivalentemente sia dopo il nome che dopo il tipo.
Si noti inoltre che al momento della dichiarazione non viene specificata la dimensione dell'array.
L'istruzione di dichiarazione infatti crea una variabile di tipo array ma ad esso non associa nessun valore; la
variabile array conterrà quindi il valore speciale null: ciò significa che la variabile di tipo riferimento che
non si riferisce proprio a nulla.
L'allocazione di un array consente di specificare la dimensione dell'array, cioè di allocare la quantità di
memoria necessaria per contenere gli elementi dell'array specificato. La sintassi è la seguente:
nomeArray = new tipoElementi[dimensione];
Ad es.
voti = new int[10];
nomi = new String[15];
Come si può notare l'allocazione viene eseguita attraverso l'operatore new.
Una eseguita l'allocazione l'array non conterrà più il valore null ma l'indirizzo di memoria del primo
elemento dell'array, mentre i successivi saranno memorizzati nelle celle adiacenti.
Spesso comunque le operazioni di dichiarazione ed allocazione vengono effettuate contemporaneamente
nella stessa istruzione:
tipoElementi nomeArray[] = new tipoElementi[dimensione]
Ad es.
int voti[] = new int[10];
String nomi[] = new String[15];
L'inizializzazione permette di inserire valori iniziali nell'array; è possibile assegnare immediatamente valori
all'array al momento della dichiarazione nel seuente modo:
tipoElementi nomeArray[ ] = {valore1, valore2, ..., valoreN}
Ad es.
int voti[] = {4,5,6,5,7,6};
String nomi[] = {“Tizio”, “Caio”, “Sempronio”};
In questo modo, dichiarazione, allocazione e inizializzazione vengono fatte contemporaneamente.
Tuttavia spesso l'inizializzazione viene eseguita in un momento successivo alla dichiarazione; in questo caso
è necessario potersi riferire ai singoli elementi dell'array attraverso l'utilizzo di identificatori univoci: gli
indici.
Il primo elemento dell'array ha indice 0
Il secondo elemento dell'array ha indice 1
...
L'elemento n-esimo dell'array ha indice n – 1
Un errore comune è l'utilizzo di indici che escono dai limiti dell'array: se l'array ha dimensione n, posso
usare solamente indici compresi tra 0 ed n – 1.
Se invece utilizzo un indice i < 0 oppure i > n – 1 verrà generata l'apposita eccezione
ArrayIndexOutOfBoundsException.
Per accedere in lettura o in scrittura all'elemento di indice i si utilizza l'operatore []:
/* leggo l'elemento di indice i e lo assegno ad elem */
tipoElementi elem = nomeArray[i];
/* assegno all'elemento di indice j il valore di elem */
nomeArray[j] = elem;
Ad es.
int voti[] = new int[10]; voti[0] = 4; voti[1] = 5;
int x = voti[0];
x += voti[1];
String nomi[] = new String[15];
nomi[0] = “Tizio”; nomi[1] = “Caio”; nomi[2] = “Sempronio”;
String s = nomi[0] + nomi[2];
s += nomi[1];
Un importante proprietà dell'array è l'attributo length che ne ritorna la dimensione; ad es.
double temperature = new double[30];
int dim = temperature.length; //n assumerà il valore 30
Questa proprietà risulta molto utile quando si utilizzano cicli per iterare su tutti gli elementi dell'array; si
veda ad esempio il seguente ciclo for:
for(int i = 0; i < voti.lentgh; i++) {
voti[i] = (int) ( Math.random()*9 + 1 );
System.out.println( voti[i] );
}
Il ciclo scorre tutti gli elementi del vettore, assegnando ad ogni elemento un numero casuale intero tra 1 e
10 che successivamente stampa a video.
Esempio:
/*
Programma che stampa a video tutti i parametri inseriti
dall'utente da riga di comando
*/
class parametri {
public static void main(String args[]) {
if(args.length == 0)
System.out.println(“Nessuno parametro inserito”);
else {
System.out.println(“Hai inserito i seguenti parametri: ”);
for(int i=0; i < args.lenth; i++)
System.out.println(args[i]);
} //fine else
} //fine main
} //fine classe
Questo programma estrae dal vettore args tutti gli argomenti passati da riga di comando (se ce ne sono) e
li stampa a video.
Dopo aver compilato (javac parametri.java) posso quindi eseguire il file da riga di comando; ad
esempio il comando:
java parametri Ciao Mondo!!! STOP
produrrà il seguente output:
Hai inserito i seguenti parametri:
Ciao
Mondo!!!
STOP
Analogamente al C++, è inoltre possibile anche definire array bidimensionali: le cosiddette matrici.
Dichiarazione:
tipoElementi nomeMatrice[][];
Allocazione:
nomeMatrice = new tipoElementi[numRighe][numColonne];
Dichiarazione + Allocazione:
tipoElementi nomeMatrice = new tipoElementi[numRighe][numColonne];
Accesso:
/* leggo l'elemento di indice di riga i e indice di colonna j e lo
assegno ad elem */
tipoElementi elem = nomeMatrice[i][j];
/* assegno all'elemento di indice di riga i e indice di colonna j il
valore di elem */
nomeMatrice[i][j] = elem;
Esempio:
/*
Programma che carica una matrice 4x5 con valori interi casuali tra 0 e
5 e la stampa a video.
*/
class matrice {
public static void main(String args[]) {
int mat[] = new int[4][5];
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 5; j++) {
mat[i][j] = Math.random() * 5;
System.out.print(mat[i][j]+’ ‘);
} //fine for colonne
System.out.println();
} //fine for righe
} //fine main
} //fine classe
//vado a capo di una riga
Esercizi
Implementare in codice Java i seguenti esercizi.
Per ogni esercizio si raccomanda di stampare a video i vettori utilizzati nel formato
[elem_1, elem_2, …, elem_n]
1. Chiedere un numero n ≥ 0 da tastiera ed inserire in un vettore i primi n quadrati (1, 2, 4, 9, 16,
25,…,n2 ). Quindi, calcolare e visualizzare la somma, il prodotto e la media degli elementi del
vettore.
2.
Caricare in modo casuale due vettori di double x e y di uguale dimensione n (dove n è chiesto
da utente). Quindi calcolare il prodotto scalare tra x ed y, cioè la somma dei prodotti di tutti gli
elementi di indice uguale ( es. x = [1, 0, 2] e y = [2.3, 3.7, 3] 1*2.3 + 0*3.7 + 2*3 = 8.3 ).
3. Caricare in un vettore di 10 elementi una serie di stringhe inserite da utente; l’inserimento
termina quando inserisco la stringa ‘STOP’ (che non va inserita nel vettore) oppure il vettore è
pieno (cioè, ho già inserito 10 stringhe…). Al termine dell’inserimento, stampare a video (uno sotto
all’altra) solo le stringhe del vettore che hanno lunghezza maggiore o uguale a 3.
4. Caricare in un primo vettore di 5 elementi numeri casuali interi tra 1 e 10, corrispondenti alla
combinazione vincente di un gratta e vinci. Quindi leggere da tastiera 5 numeri tra 1 e 10 e inserirli
in un secondo vettore, corrispondente alla vostra giocata. Infine, controllate e stampate a video
quante sono le corrispondenze (cioè, stesso numero nella stessa posizione) tra la combinazione
vincente e la vostra giocata.
5. Caricare un vettore vett con n numeri casuali tra 0 e 10 (n è un intero chiesto da tastiera).
Visualizzare quindi:
•
•
•
2.9
l’elemento più piccolo contenuto in vett.
l’elemento più grande contenuto in vett.
l’indice del primo elemento pari di vett (se non ci sono numeri pari devo visualizzare -1).
Le eccezioni
Come già accennato, un eccezione è un anomalia che si presenta durante l’esecuzione del programma.
Esempi classici di situazione anomala sono la divisione per zero oppure l’utilizzo di un indice che esce dai
limiti di un array.
Come visto per la lettura di input da utente, la gestione delle eccezioni è possibile mediante l’utilizzo del
costrutto try-catch:
try {
//istruzioni da controllare (potenzialmente “pericolose”)
}
catch(Exception e) {
//operazioni da eseguire se si verifica un eccezione
}
Quando si verifica un eccezione, l’esecuzione del programma si interrompe per cercare di gestirla.
Se tale eccezione si verifica all’interno di un blocco try , allora l’esecuzione riprende a partire dalla prima
istruzione di tale blocco catch.
Viceversa, il programma termina segnalando un errore.
Esempio:
/*
Questo programma genera un indice intero casuale tra 0 e 10.
Successivamente carica un vettore di double di 5 elementi con numeri
casuali tra 0 ed 1.
Quindi prova ad accedere all’elemento di indice i del vettore: se è
possibile lo stampa, altrimenti gestisce l’eccezione stampando a video
un messaggio di errore.
*/
class eccezione {
public static void main (String args[]) {
int i = (int)(Math.random()*10);
double vett[] = new double[5];
for(int i = 0; i < 5; i++)
vett[i] = Math.random();
try {
System.out.println("indice: " + i + " elemento: " + vett[i]);
} //fine try
catch(Exception e) {
System.out.println("Si è verificata un eccezione! Impossibile
accedere all'elemento di indice " + i );
} //fine catch
System.out.println("Fine del programma. Bye!" );
} //fine main
} //fine classe
E’ importante notare che:
•
•
Se 0 ≤ i ≤ 4, il programma non esegue il blocco catch (non c’è eccezione) e viene stampato il
valore di vett[i]
Se invece i > 4, il programma esegue il blocco catch (ho eccezione: esco dai limiti dell’array) e
stampo un messaggio di errore.
In entrambi i casi comunque il programma termina normalmente; viceversa se non avessi avuto un blocco
try-catch in caso di eccezione il programma sarebbe terminato immediatamente segnalando un errore.