6. Il Linguaggio Java
Il linguaggio Java è un linguaggio di programmazione orientato agli oggetti, creato da James
Gosling e altri ingegneri di Sun Microsystems. Il gruppo iniziò a lavorare nel 1991, il linguaggio
inizialmente si chiamava Oak. Il nome fu successivamente cambiato in Java a causa di un problema
di copyright (il linguaggio di programmazione Oak esisteva già nel 1991). Java fu annunciato
ufficialmente il 23 maggio 1995 a SunWorld. La piattaforma di programmazione Java è fondata sul
linguaggio stesso, sulla Java Virtual Machine (JVM) e sulle API. Java è un marchio registrato di
Sun Microsystems.
6.1 Panoramica
Java è stato creato per soddisfare quattro scopi:
1.
2.
3.
4.
essere orientato agli oggetti,
essere indipendente dalla piattaforma,
contenere strumenti e librerie per il networking,
essere progettato per eseguire codice da sorgenti remote in modo sicuro.
Orientamento agli Oggetti
La prima caratteristica, l’orientamento agli oggetti, si riferisce a un moderno metodo di
programmazione e progettazione. L’idea principale della programmazione ad oggetti consiste nel
rendere il software la rappresentazione di entità reali o astratte ma ben definite (oggetti). Questi
oggetti, come nella vita pratica hanno proprietà rappresentate da valori, e qualità o meglio metodi:
ciò che sanno fare questi oggetti. Si pensi ad una automobile: ha delle proprietà come il colore o il
numero di porte, e dei metodi, per esempio può girare a destra o sinistra, andare avanti o indietro,
accelerare, decelerare, riportando la programmazione in questi termini è facile capire come questo
renda più facile la gestione di grandi progetti, migliorarne la qualità e la manutenibilità.
Indipendenza dalla piattaforma
La seconda caratteristica, l’indipendenza dalla piattaforma, significa che l’esecuzione di programmi
scritti in Java deve avere un comportamento simile su hardware diverso. Si dovrebbe essere in
grado di scrivere il programma una volta e farlo eseguire dovunque. Questo è possibile con la
compilazione del codice di Java in un linguaggio intermedio bytecode, basato su istruzioni
semplificate che ricalcano il linguaggio macchina. Esso viene eseguito da una virtual machine, cioè
da un interprete: Java è quindi, in linea di massima, un linguaggio interpretato. Inoltre, vengono
fornite librerie standardizzate per permettere l’accesso alle caratteristiche della macchina (come
grafica e networking) in modo unificato. Il linguaggio Java include anche il supporto per i
programmi con multithread, necessario per molte applicazioni che usano la rete.
La portabilità è un obiettivo tecnicamente difficile da raggiungere, e il successo di Java in questo
ambito è materia di alcune controversie. Sebbene sia in effetti possibile scrivere in Java programmi
che si comportano in modo consistente attraverso molte piattaforme diverse, bisogna tenere
presente che questi poi dipendono dalle virtual machine, che sono programmi a sé e che hanno
1
inevitabilmente i loro bug, diversi dall'una all'altra: per questo è nata una parodia dello slogan di
Sun “Scrivi una volta, esegui dovunque”, che è diventato “Scrivi una volta, fai il debug ovunque”.
Esecuzione sicura del codice remoto
La piattaforma Java fu uno dei primi sistemi a fornire un largo supporto per l’esecuzione del codice
da sorgenti remote. Una applet Java è un particolare tipo di applicazione che può essere avviata
all’interno del browser dell’utente, eseguendo codice scaricato da un server web remoto. Questo
codice viene eseguito in un’area (sandbox) altamente ristretta, che protegge l’utente dalla possibilità
che il codice sia malevolo o abbia un comportamento non desiderato; chi pubblica il codice può
applicare un certificato che usa per firmare digitalmente le applet dichiarandole “sicure”, dando loro
il permesso di uscire dall'area ristretta e accedere al filesystem e al network, presumibilmente con
l’approvazione e sotto il controllo dell’utente. In realtà le applet non hanno avuto molta fortuna.
Infatti presuppone che il client in cui essi vengono eseguiti abbia installata la JRE (deve eseguire il
codice dell’applet). Hanno avuto fortuna le applicazioni che prevedono il cosiddetto thin-client, cioè
un client “leggero” che non ha bisogno di particolari strumenti per eseguire il codice remoto (a volte
è necessario solo il browser).
6.2 Altri aspetti di interesse
Rispetto alla tradizione dei linguaggi a oggetti da cui deriva (e in particolare rispetto al suo diretto
progenitore, il C++), Java ha introdotto una serie di notevoli novità rispetto all’estensione della sua
semantica. Fra le più significative si possono citare probabilmente la possibilità di costruire GUI
(interfacce grafiche) con strumenti standard e non proprietari (per il C++ e altri linguaggi analoghi
solitamente le GUI non fanno parte del linguaggio, ma sono delegate a librerie esterne), la
possibilità di creare applicazioni multi-thread, ovvero che svolgono in modo concorrente molteplici
attività, e il supporto per la riflessione, ovvero la capacità di un programma di agire sulla propria
struttura e di utilizzare classi caricate dinamicamente dall’esterno.
Fra gli argomenti che depongono spesso a favore di Java nella scelta del linguaggio di
implementazione di un progetto software moderno, inoltre, si deve certamente contare la vastità
delle librerie standard di cui il linguaggio è dotato, e che in particolare contribuiscono a renderlo
altamente integrabile con le altre tecnologie. Alcuni esempi di funzionalità di libreria di Java sono:
•
•
•
•
•
•
•
accesso ai database tramite JDBC e ai DBMS con driver ODBC tramite il bridge JDBCODBC,
manipolazione documenti XML,
dialogo con piattaforme CORBA,
potenti strumenti per la programmazione lato server nel contesto Web,
supporto nativo per gran parte dei protocolli della famiglia IP,
supporto per le applicazioni multimediali, streaming audio e video,
abbondanza di sviluppatori che conoscono questo linguaggio e quindi basse retribuzioni.
6.3 Valutazione
Secondo molte persone, la tecnologia Java raggiunge ragionevolmente bene tutti i suoi obiettivi. Il
linguaggio comunque non è privo di incertezze. Java tende ad essere più ad alto livello di altri
linguaggi simili (come il C++); questo comporta carenze in alcune caratteristiche come i tipi di dati
specifici, puntatori alla memoria di basso livello e metodi di programmazione come il
sovraccaricamento degli operatori. Nonostante queste caratteristiche siano abusate frequentemente
2
dai programmatori, esse sono anche strumenti potenti. Comunque, la tecnologia Java include Java
Native Interface (JNI), un modo per chiamare codice nativo da codice Java. Con JNI è quindi
possibile ugualmente usare queste caratteristiche. Alcuni programmatori lamentano anche la
mancanza dell’ereditarietà multipla, un potente mezzo di molti linguaggi orientati agli oggetti, tra
cui il C++. Il linguaggio Java separa l’ereditarietà del tipo dall'implementazione, permettendo
l’ereditarietà multipla dei tipi attraverso le interfacce. Questo permette di ottenere la maggior parte
dei benefici dell’ereditarietà multipla evitando molti dei suoi pericoli. Inoltre, attraverso l’uso di
classi concrete, classi astratte e interfacce, un programmatore ha la possibilità di scegliere un grado
nullo, parziale o completo di implementazione dell'oggetto che definisce, essendo assicurata la
massima flessibilità nella progettazione. Alcune persone pensano che per particolari progetti, la
programmazione orientata agli oggetti renda il lavoro più difficile. Questa particolare lamentela non
è peculiare di Java, ma è rivolta a tutti i linguaggi di questo tipo. Per contro, la gran parte delle
aziende che sviluppano software ha eseguito da tempo il “salto” verso questo nuovo tipo di
tecnologie.
6.4 Tipi
I tipi primitivi supportati da Java sono riassunti nella seguente tabella.
Tipo di dato
byte
short
int
long
float
double
char
boolean
Descrizione
intero con segno a 8 bit
intero con segno a 16 bit
intero con segno a 32 bit
intero con segno a 64 bit
virgola mobile a 32 bit singola precisione (standard IEEE 754)
virgola mobile a 64 bit doppia precisione (standard IEEE 754)
carattere singolo Unicode
vero o falso
Come detto più volte, le classi costituiscono tipi di dati astratti. Esse quindi definiscono l’insieme
dei tipi non primitivi, siano esse classi di default oppure classi definite dall’utente.
6.5 Variabili
Tipi primitivi
Dichiarazione:
int i;
boolean b;
float f;
int i,j;
boolean b1,b2;
float f1,f2;
Inizializzazione:
int i = 2;
equivalente a
int i; i = 2;
3
int i,j = 2;
equivalente a
int i; int j = 2;
Passaggio per valore:
int x,y;
x = 1;
y = x;
x = x+1;
\\alla fine si ottiene x = 2 e y = 1.
Oggetti
Dichiarazione:
class Point {
int x;
int y;
}
Costruttore (inizializzazione):
class Point {
int x;
int y;
Point (int x1, int y1) {
x = x1;
y = y1 ;
}
}
Creazione:
Point pt = new Point(3,4);
Accesso (oggetto.attributo):
pt.x = pt.x + 1;
\\alla fine pt = (4,4).
Passaggio per riferimento:
Point pt1 = new Point(3, 4), pt2;
pt2 = pt1;
pt1.x = pt1.x + 1;
\\alla fine pt1 = (4, 4) e pt2 = (4, 4).
Point pt1 = new Point(3, 4), pt2;
pt2 = pt1;
pt1 = new Point(4, 4);
\\alla fine pt1 = (4, 4) e pt2 = (3, 4).
4
Point pt1 = new Point(3, 4), pt2;
pt2 = pt1;
pt2.y = pt2.x + pt2.y;
\\alla fine pt2 = (3, 7) e pt1 = (3, 7).
6.6 Espressioni
Espressioni booleane:
! < Expression >
< Expression > & < Expression >
< Expression > | < Expression >
< Expression > = = < Expression >
//negazione
//disgiunzione
//congiunzione
//uguaglianza
int x,y;
x = 1;
y = 1;
\\x = = y è vero
Point pt1, pt2;
pt1 = new Point(3, 4);
pt2 = pt1;
\\pt1 = = pt2 è vero
Point pt1, pt2;
pt1 = new Point(3, 4);
pt2 = new Point(3, 4);
\\pt1 = = pt2 è falso!
Point pt1, pt2;
pt1 = new Point(3, 4);
pt2 = pt1;
pt1.x = 4;
\\pt1 = = pt2 è vero!
Espressioni aritmetiche:
< Expression > + < Expression >
< Expression > - < Expression >
< Expression > * < Expression >
< Expression > / < Expression >
< Expression > % < Expression >
+ < Expression >
- < Expression >
< Expression > < < Expression >
< Expression > > < Expression >
< Expression > <= < Expression >
< Expression > >= < Expression >
Espressioni con oggetti:
5
Point pt = new Point(3,4);
int i = pt.x;
int j = new Point(3,4).x;
\\i = 3 e j = 3.
class Point {
int x;
int y;
Point (int x1, int y1) {
x = x1;
y = y1;
}
int distanceToOrigin() {
return sqrt(x * x + y * y);
}
}
Point pt = new Point(3, 4);
int i = pt.distanceToOrigin();
int j = new Point(3, 4).distanceToOrigin();
\\i = 5 e j = 5.
6.7 Istruzioni
Assegnamento:
int x;
x = 5;
Point pt = new Point(3,4);
pt.x = 5;
Return:
return < EXPRESSION >;
Blocco:
{ < STATEMENT >* }
Condizionale:
if (<EXPRESSION>)
< STATEMENT >
else
< STATEMENT >
While:
while (<EXPRESSION>)
< STATEMENT >
Do while:
6
do < STATEMENT >
while ( < EXPRESSION >)
For:
for (< INIT >; < EXPRESSION >; < INCR >)
< STATEMENT >
Switch:
switch (< EXPRESSION >) {
case < EXPRESSION > : < STATEMENT >
case < EXPRESSION > : < STATEMENT >
…..
default : < STATEMENT >
}
6.8 Stringhe
Le stringhe sono oggetti predefiniti Java appartenenti alla classe String. Come tali dispongono di
una serie di metodi predefiniti.
Creazione:
String s;
String s1 = new String(“Hello”);
String s2 = “World”;
\\notazione abbreviata.
Uguaglianza:
Boolean b = “World”.equals(“World”);
\\vale true.
Le stringhe sono oggetti: non si può usare = = per testare l’uguaglianza. Infatti,
new String(“Hello”) = = new String(“Hello”) vale false!!
Concatenazione:
String s3 = s1 + “ “ + s2; \\vale “Hello World”.
Lunghezza:
String s = “World”;
int x = s.length(); \\vale 5.
Ricerca:
String s = “Hello World”;
char x = s.charAt(4); \\vale ‘o’.
int y = s.index(“World”); \\vale 6.
String s1 = s.substring(6,11); \\vale “World”.
7
6.9 Array
Anche gli array sono oggetti predefiniti in Java.
Dichiarazione:
int [] primi;
Creazione:
int [] primi = new int[4];
Inizializzazione:
primi[0] = 2;
primi[1] = 3;
primi[2] = 5;
primi[3] = 7;
Inizializzazione immediata:
int [] primi = {2, 3, 5, 7};
Assegnamento:
int [] primi;
primi = new int[] {2, 3, 5, 7};
Lunghezza:
int x = primi.length; \\vale 4.
A differenza delle stringhe, la lunghezza è un attributo di un array e non un metodo.
La sintassi riportata si estende in maniera naturale al caso di array multidimensionali.
6.10 Oggetti
Un oggetto contiene degli attributi, un costruttore e dei metodi.
class Point {
int x;
int y;
Point (int x1, int y1) {
x = x1;
y = y1;
}
int distanceToOrigin() {
return sqrt(x * x + y * y);
}
8
boolean equals (Point pt) {
return ((x = = pt.x) && (y = = pt.y));
}
}
class Line {
Point pt1;
Point pt2;
Line(Point p, Point q) {
if (p.equals(q))
System.exit(0);
pt1 = p;
pt2 = q;
}
}
Un campo statico è un campo condiviso da tutti gli oggetti appartenenti alla classe. Ogni modifica
al campo statico influenza quindi ogni oggetto.
class Point {
int x;
int y;
static int scaling = 1;
Point (int x1, int y1) {
x = x1;
y = y1;
}
int relativeX() {
return x * scaling;
}
int relativeY() {
return y * scaling;
}
}
Point pt1 = new Point(3,4), pt2 = new Point(4,5);
int x1 = pt1.relativeX();
pt1.scaling = 2;
int x2 = pt1.relativeX();
int x3 = pt2.relativeX();
\\ x1 = 3, x2 = 6, x3 = 8.
Un metodo ha un numero fisso di parametri e ritorna sempre qualcosa (nel caso di una procedura
ritorna void). Le variabili locali definite nei metodi non hanno valore di default e mascherano
eventuali attributi aventi lo stesso nome. All’interno di un metodo, la parola this serve per
referenziare l’oggetto dal quale il metodo è stato invocato. Un metodo statico è un metodo
condiviso da tutti gli oggetti appartenenti ad una classe. Esso può essere invocato sia da un oggetto
che dalla relativa classe.
class Point {
int x;
int y;
9
static int scaling = 1 ;
Point (int x1, int y1) {
x = x1;
y = y1;
}
static int getScaling() {
return scaling;
}
}
Point pt = new Point(3,4);
int s = pt.getScaling();
int t = Point.getScaling();
L’overloading dei metodi risulta essere uno strumento molto potente come si può facilmente capire
dal seguente esempio.
class Print {
static String toStringPt(Point pt) {
return “(“ + pt.x + “, “ + pt.y + “)”;
}
static String toStringLine(Line l) {
return “[“ + toStringPt(l.pt1) + “;“ + toStringPt(l.pt2) + “]”;
}
}
può essere implementata nella seguente maniera:
class Print {
static String toString(Point pt) {
return “(“ + pt.x + “, “ + pt.y + “)”;
}
static String toString(Line l) {
return “[“ + toString(l.pt1) + “;“ + toString(l.pt2) + “]”;
}
}
L’uso dell’overloading nei costruttori è una pratica piuttosto diffusa. Illustriamo questa pratica
attraverso un esempio attraverso il quale discutiamo anche l’uso della parola riservata this.
class Point {
int x;
int y;
Point (int x1, int y1) {
x = x1;
y = y1;
}
Point() {
x = 0;
y = 0;
}
Point(int x) {
10
this.x = x;
y = x;
}
}
Point pt1 = new Point();
Point pt2 = new Point(1);
Si può anche utilizzare la seguente sintassi:
class Point {
int x;
int y;
Point (int x1, int y1) {
x = x1;
y = y1;
}
Point() {
this(0,0);
}
Point(int x) {
this(x,x);
}
}
In questo caso this sta ad indicare il metodo avente lo stesso nome e deve essere la prima istruzione
del metodo.
11