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