Java è stato creato da James Gosling ed altri ingegneri della Sun MicroSystems a partire dal 1991.
L’architettura Java si compone di quattro componenti:
Il linguaggio di programmazione
Java è un linguaggio:
Application Programming Interface (API)
Java API è un insieme di librerie standard per poter accedere alle risorse del computer. Le JAVA API presentano una interfaccia standard, e sono implementate invocando gli opportuni metodi nativi offerti dalla macchina su cui sono eseguite.
Java Virtual Machine (JMV)
Java tools a supporto dello sviluppo ed esecuzione di programmi.
Il compito della JVM è di caricare i class file ed eseguirli. Viene definito un computer astratto, perché fornisce un’astrazione del sistema operativo e dell’apparec-chiatura hardware della macchina.
Java tools a supporto dello sviluppo ed esecuzione di programmi
Java dispone di un insieme di tool a supporto delle fasi di sviluppo di un programma e delle azioni necessarie per la sua esecuzione.
L’insieme dei tool, API e JVM viene spesso definito Java Platform.
L’obiettivo alla base della progettazione di Java è la portabilità: si dovrebbe essere in grado di scrivere il programma una sola volta e di poterlo eseguire dovunque (“write once, run everywhere”).
Il codice in C++ non è portabile ovunque, ma va compilato su ogni piattaforma deve essere eseguito!
Un apposito compilatore (javac) processa il codice sorgente non in linguaggio macchina specifico della piattaforma su cui girerà il codice, ma in un linguaggio per un processore virtuale detto bytecode.
Ogni JVM dispone di un interprete in grado di tradurre il bytecode in linguaggio macchina ed eseguirlo.
La preparazione e l’esecuzione di un programma Java consiste di 5 fasi:
1) La fase di EDIT
C:\PRG>edit PrimoEsempio.java
Viene creato il file contenente il programma e, successivamente, memorizzato nel disco.
ATTENZIONE
Il nome del file deve coincidere con quello della classe (case sensitive!)
I programmi scritti in Java sono unicamente orientati agli oggetti, di conseguenza tutto il codice deve essere necessariamente incluso in una o più classi.
Per rendere eseguibile un’applicazione Java occorre che una classe faccia da “punto di decollo”, ovvero deve contenere un metodo pubblico main()
. Questo è il primo ad essere invocato durante l’esecuzione dell’applicazione.
2) La fase di COMPILAZIONE
C:\PRG>javac PrimoEsempio.java
Il compilatore JAVA traduce il programma nel corrispondente bytecode, il linguaggio compreso dall’interprete java, e lo salva su disco.
3) La fase di CARICAMENTO
C:\PRG>java PrimoEsempio
Il Class loader carica il bytecode nella memoria principale
4) La fase di VERIFICA del bytecode
Il “bytecode verifier” ha il compito di validare il byte-code e assicurarsi che non si violino i vincoli di sicurezza dell’ambiente JAVA.
5) La fase di ESECUZIONE
L’interprete del byte-code traduce le istruzioni del bytecode nelle istruzioni com-patibili con il processore.
Prima azione di controllo
Bytecode Verifier: si analizza la sequenza di bytecode e controlla il riferimento ad altre classi. Ad esempio: se una classe utilizza il metodo di un’altra, controlla se questo metodo è pubblico oppure se vengono sforati i limiti di un array. Oppure controlla se una classe che presenta una sottoclasse non presenti il vincolo di non specializzazione.
Seconda azione di controllo
Security Manager: speciale classe che può essere implementata dal programmatore che disciplina l’accesso a una risorsa (ad esempio accesso a un file o a una connessione di rete).
Java opera una distinzione netta tra classi e tipi primitivi, relativamente al modo in cui avviene l’allocazione in memoria:
Quando si dichiara una variabile del tipo intero (int x) vengono subito allocati quattro byte.
Se si utilizza una classe in qualità di tipo per una certa variabile (MyClass c), verrà creata subito una variabile che referenzia l’oggetto (reference) ma non verrà allocata memoria fino alla creazione vera e propria dell’istanza della classe, tramite l’utilizzo dell’operatore new.
Un reference è, dunque, una variabile speciale che tiene traccia di (punta a) istanze di tipi non primitivi. I reference possono tenere traccia soltanto di oggetti di tipo compatibile, ovvero un reference ad un oggetto di tipo MyClass non potrà tenere traccia di oggetti di diverso tipo.
Nel linguaggio di programmazione C++, sono di comune utilizzo variabili di tipo riferimento: rappresentano dei puntatori costanti alle variabili di cui fanno riferimento, e sono usati per estenderne la visibilità oltre l’ambito in cui sono state definite.
Dichiarando una variabile di tipo riferimento, non è stato allocato nessun oggetto, ma è stato dato un nuovo nome a un oggetto già esistente. Tale variabile, quando viene usata in un’espressione, ha come valore il valore dell’oggetto a cui fa riferimento.
Java si discosta da quanto avviene in C++: i riferimenti puntano solo a oggetti, e non a variabili di tipo primitivo, e non costituiscono dei nomi alternativi. Infatti, sono degli oggetti allocati in area stack che puntano ad aree di memoria allocate nell’heap.
Inoltre, il contenuto di un riferimento non è costante, ma può essere variato a piacimento dal programmatore.
Supponiamo di istanziare due oggetti di tipo String contenenti entrambi il valore “Pippo”:
String s = new String("Pippo");
String s1 = new String("Pippo").
if(s == s1)_{_ ..._} else { _ ..._}
Quale dei due rami del costrutto if …else viene certamente eseguito?
Le istruzioni che verranno eseguite saranno quelle all’interno dell’else.
Come mai?
La ragione è legata al fatto che l’operatore “==” esegue la comparazione tra le variabili reference che tengono traccia dei due oggetti e non tra i valori contenuti dagli oggetti stessi.
Mentre tra variabili di tipi primitivi la comparazione è normalmente effettuata tramite l’operatore “==”, per la comparazione tra due istanze di oggetti della stessa classe, sarà necessario, a tale scopo, utilizzare il metodo equals().
String s = new String("Pippo");_String s1 = new String("Pippo");__if(s.equals(s1)
{_ ..._} else { _ ..._}
C++
int i;
int *pi;
int &ri=i;
pi=new int;
Java
//c è un tipo intero
int i;
I tipi primitivi (boolean, char, byte, short, int, long, float, double):
… per l’allocazione dinamica (in area heap) si ricorre a delle classi Wrapper (Boolean, Character, Byte, Short, Integer, Long, Float, Double).
Per istanziare un oggetto dinamicamente nell’area heap, si fa ricorso all’istruzione new:
String str = new String("stringa");
Come fare se vogliamo deallocare un oggetto? Esiste in Java una istruzione di delete come in C++?
In JAVA è stato implementato un’apposito “modulo” che, in maniera automatica, recupera la memoria degli oggetti non più utilizzati (cioè che non sono più riferiti)
garbage collector
Quindi in JAVA non esiste più il problema, tipico del C e C++, della “perdita di memoria” dovuto a…
…programmatori un po’ distratti!
Quando un oggetto non è più referenziato dal programma, lo spazio che occupa nell’heap deve essere liberato, così da renderlo disponibile per nuovi oggetti.
Il garbage collector deve rendersi conto di quali oggetti non sono più referenziati e liberarne lo spazio sotteso. Inoltre, deve combattere la frammentazione dovuta alle continue allocazioni e deallocazioni durante il ciclo di vita di un programma.
Vantaggio: Maggiore produttività del programmatore – Garanzie di integrità: un programmatore non può accidentalmente (o maliziosamente) causare un crash della JVM liberando incorrettamente della memoria.
Svantaggio: il garbage collector implica un considerevole overhead che può inficiare le performance del programma sviluppato.
A differenza degli altri linguaggi, in JAVA non è possibile scegliere il tipo di scambio di parametri ad una funzione o metodo.
Esiste, invece, una regola fissa:
…in effetti, molti puristi amano dire che in Java viene adottato solo lo scambio per valore.
Persona.java Mostra codice
Prova_Persona.java Mostra codice
Dove, nel codice Persona.java:
Persona (String n) {
nome=new String(n);
}
Costruttore della classe, notare che non è presente alcun distruttore. Se è necessario eseguire delle operazioni prima che l'oggetto sia liberato dal Garbage Collector si deve implementare un metodo finalize().
public String identita() {
return new String(nome);
}
Metodo per ottenere il valore della variabile ‘nome'. Notare che ci crea una copia della variabile.
Il codice Prova_Persona.java Mostra codice
è il metodo principale che la JVM esegue per lanciare l'applicazione.
static
Un metodo static non richiede l'istanziazione di un oggetto della classe per il suo utilizzo.
Che significare avere una variabile static?
Una variabile static è una variabile che allocata una sola volta indipendentemente dagli oggetti della classe e accessibile da tutti gli oggetti.
final int = 5
Una variabile final è una variabile il cui contenuto è costante.
static final int i = 2;
Una variabile può essere allo stesso tempo static e final... che significa?
E' una variabile allocata una sola volta per tutti gli oggetti, e il cui contenuto è costante
Una delle caratteristiche più importanti della programmazione orientata agli oggetti (OO, object-oriented) è la possibilità di riuso del codice.
Nei linguaggi procedurali come il C, il riuso è ottenibile copiando codice di precedenti programmi e modificandolo.
Nei linguaggi OO si usano tecniche più raffinate, che creano nuove classi a partire da quelle esistenti senza intaccarle:
La composizione si realizza inserendo nella nuova classe dei riferimenti agli oggetti delle classi esistenti.
Il compilatore non istanzia automaticamente gli oggetti per ogni riferimento definito nella classe Macchina. In Java attributi di tipi primitivi sono automaticamente inizializzati a 0, mentre i riferimenti di oggetti sono inizializzati a null.
System.out.println (Macchina()");
I riferimenti sono inizializzati prima che il costruttore viene invocato:
Carrozzeria carrozzeria = new Carrozzeria ();
String s = "Macchina Assemblata";
I riferimenti sono inizializzati nel costruttore:
motore = new Motore();
I riferimenti sono inizializzati prima di essere usati:
ruota1 = new Ruota();
ruota2 = new Ruota()
ruota3 = new Ruota();
ruota4 = new Ruota();
L’ereditarietà consente di definire nuove classi ereditando le caratteristiche offerte da classi esistenti. In altre parole si realizzano relazioni tra classi di tipo gen-spec: una classe base, realizza un comportamento comune ad un insieme di entità, mentre le classi derivate (sottoclassi) realizzano comportamenti specializzati rispetto a quelli della classe base.
NB: Nell’ereditarietà non siamo vincolati ad implementare gli stessi metodi della classe base, ma possiamo aggiungerne di nuovi.
Sintassi:
class Veicolo : public Oggetto {...};
class Veicolo extends Oggetto {...};
Il legame gen-spec tra due classi è rappresentato per mezzo della parola chiave extends, secondo la seguente sintassi:
{nome_classe_derivata} extends {nome_classe_base}
In Java, a differenza del C++, non è possibile esprimere la modalità di derivazione (public, protected, private). Inoltre, esiste un qualificatore final con cui si dichiara una classe non ulteriormente derivabile.
Es. final class Ferrari [extends Macchina] {..}
Un’altra differenza con il C++ è che Java implementa soltanto l’ereditarietà singola, pertanto ciascuna classe in Java può avere
una sola superclasse.
Inoltre, ogni classe in Java è automaticamente e implicitamente una specializzazione della classe Object, da cui eredita un insieme di funzionalità, che può ridefinire:
toString()
, fornisce una rappresentazione String dell’oggetto su cui viene invocato: l’implemen-tazione base è nome_classe@codice_hash_oggetto;clone()
, fornisce un clone dell’oggetto corrente. Il tipo di ritorno è Object quindi va sempre effettuato un casting;equals()
, nella sua versione base, verifica se due riferimenti sono uguali, non se due oggetti hanno attributi uguali.Quando si richiama un metodo su un oggetto, l’interprete ne cerca dapprima la definizione nella classe dell’oggetto stesso; se non la trova, cerca nella superclasse e risale la gerarchia fino a trovarla.
Nel caso esistano più metodi con lo stesso nome, tipo restituito e stessi parametri (firma) viene eseguito il metodo trovato per primo.
Consideriamo il seguente codice:
Mostra codiceTune ha come parametro di ingresso oggetti di tipo Instrument, come mai non ho un errore trasmettendo un tipo Wind?
UPCASTING
La classe Wind estende quella Instrument, quindi un oggetto di tipo Wind è anche di tipo Instrument.
Dove nel secondo codice
protected int esami;
protected int matricola;
protected String facolta;
rappresentano al visibilità dei metodi/attributi
super(nome,sesso,eta)
rappresenta l'inizializzazione della classe base
e super.ChiSei();
è l'invocazione metodi della classe base.
Che visibilità hanno i metodi/attributi di una classe base rispetto ad una classe derivata? Dipende dalla parola chiave che li precede:
Applicativi Java è organizzabile in compartimenti che si definiscono attraverso i “package”.
Solo all’inizio del file java si può specificare l’appartenenza ad un package:
package mypackage;
public class MyClass { // . . .
Per utilizzare MyClass dall’esterno del package si possono usare le due modalità:
mypackage.MyClass m = new mypackage.MyClass();
Oppure:
import mypackage.*;
// . . .
MyClass m = new MyClass();
i package sono strutturati in uno schema ad albero:
Es: com.sun.media.rtp
Senza definire uno specifico package una classe verrà inserito nel default package.
Quando si crea un oggetto della classe derivata, questo contiene al suo interno un sotto-oggetto della classe base.
Il sotto-oggetto va opportunamente inizializzato per mezzo della chiamata al costruttore (super(..)); Tale chiamata può essere:
Invece deve essere:
Quando non viene esplicitata l’invocazione al costruttore della classe base, il compilatore java automaticamente lo pone in testa al costruttore della classe derivata.
Quando si implementa una classe derivata, è possibile che un suo metodo abbia la stessa firma di un metodo della classe base, e questo viene implementato invocando il metodo della classe base ed aggiungendo altra logica applicativa.
All’interno del metodo chiSei() di studente non è possibile invocare lo stesso metodo di Persona scrivendo “chiSei()
“.
Per risolvere questo problema si ricorre alla parola chiave super, quindi in chiSei() di Studente troviamo correttamente:
super.chiSei()
per riferirci al chiSei()
di Persona.
È prassi comune di combinare composizione ed ereditarietà. Bisogna considerare che mentre il compilatore forza l’inizializzazione delle classi base, non lo fa per l’inizializzazione dei riferimenti delle classi componenti. Quindi bisogna ricordarsi di inizializzare sempre tali riferimenti.
Qual è la differenza tra le due tecniche e quando preferire una rispetto all’altra?
La composizione implementa la relazione “has-a”, ovvero una classe vuole solo incorporare le funzionalità di un’altra;
L’ereditarietà implementa la relazione “is-a”, ovvero una classe non solo vuole incorporare le funzionalità di un’altra, ma vuole presentarsi all’utente con lo stesso vestito (interfaccia).
Per polimorfismo si intende la proprietà di una entità di assumere forme diverse nel tempo.
A è un vettore di tipo Figura, composto di N istanze di Triangolo, Rettangolo, Quadrato;
Si consideri il seguente ciclo di istruzioni:
for i = 1 to N do
A[i].disegna();
L’esecuzione del ciclo richiede che sia possibile determinare dinamicamente l’imple-mentazione della operazione disegna() da ese-guire, in funzione del tipo dell’oggetto A[i].
Il collegamento tra la chiamata di un metodo con il corpo del metodo stesso prende il nome di binding.
Quando questa operazione si verifica prima che un programma viene eseguito si parla di early binding o statico. Quando avviene a tempo di esecuzione, si dice late binding o dinamico.
Come detto precedentemente, Java realizza sempre late binding a meno che al metodo non è anteposta a parola chiave final, il cui compito è prevenire che un utente possa realizzare l’”override” di un metodo.
Vantaggio del polimorfismo: supporto della proprietà di estensibilità di un sistema: si minimizza la quantità di codice che occorre modificare quando si estende il sistema, cioè si introducono nuove classi e nuove funzionalità.
Aggiungendo una nuova classe alla gerarchia …
… modificando Aeroporto …
… il codice esegue senza errori.
La classe Velivolo può fungere solo da interfaccia per specificare come gli utenti devono utilizzare le classi derivate, ma non il loro comportamento. Pertanto, Velivolo deve essere solo un’interfaccia, ovvero un insieme di funzioni dummy che poi ogni derivata deve implementare.
Velivolo è implementabile come una classe astratta: almeno uno dei suoi metodi sono astratti, ovvero hanno solo dichiarazioni ma non un corpo.
Non è possibile istan-ziare oggetti di classi astratte o con alcuni metodi astratti.
Le classi astratte non sono l’unica soluzione che Java mette a disposizione per realizzare interfacce comuni a un insieme di classi. “Interface” è a tutti gli effetti una classe astratta, con metodi astratti e attributi, che sono implicitamente static e final.
Usando delle classi astratte, il programmatore ha a disposizione solo l’eredità singola (una classe eredita solo da una classe astratta), con le inter-facce può realizzare anche un’implementazione multipla (una classe può implementare più interfacce).
I metodi di un’interfaccia hanno automaticamente visibilità public, e la classe che li implementa deve dichiarare una visibilità public, altrimenti per default sono “friendly”.
Dove implements
è la parola chiave per indicare che la classe elicottero implementa l'interfaccia Velivolo.
E' corretto indicare il codice così come è scritto?
string peso;
ERRORE!!!
Nelle interfacce gli attributi sono implicitamente static e final, quindi vanno inizializzati -> string peso="100"
È possibile derivare un’interfaccia a partire da un’altra interfaccia, ma questo legame di derivazione è solo a livello di specifica dei metodi, e non di implementazione (un’interfaccia non può estendere una classe con metodi concreti perché un’interfaccia deve avere tutti i suoi metodi astratti, anche se alcuni di essi sono ereditati da terze parti).
Esempio:
public interface Remote{...}
...
import java.rmi.Remote;
public interface ISquareRoot extends Remote{
double calculateSquareRoot(double aNumber);
}
In Java è possibile posizionare la definizione di una classe all’interno di un’altra, realizzando quello che prende il nome di classe interna. Ciò consente di raggruppare classi che sono logicamente correlate e di controllare la loro visibilità.
Mostra codiceNel comando
lass Destination {
private String label;
Destination(String whereTo) {label = whereTo;}
String readLabel() {return label;}
}
non c'è nessuna differenza nell'uso di Destination, bisogna solo ricordarsi che i nomi sono innestati nella classe Document, quindi se si vuole fare riferimento alla classe interna al di fuori di Document (o in metodi statici), la sintassi è Document.Destination
.
Ogni classe interna ha completa visibilità degli attributi e dei metodi della classe che la contiene.
Mostra codiceDove
doc.new Destination("Tanzania");
é un'istanza di una classe interna ed è ottenibile solo a partire da un'istanza della classe esterna.
Document.this.title; n
el corpo di un metodo di una classe interna si può fare esplicito riferimento ai membri della classe contenente mediante la notazione: NomeClasseContenente.this.nomeMembro.
Con private
possiamo disciplinare la visibilità della classe interna con le parole chiave public, private e protected.
Infine Document.Destination dec = doc.new Destinatio (Tanzania);
non rende possibile istanziare un oggetto della classe interna.
Le classi interne non possono dichiarare metodi statici. Ma è possibile che una classe interna sia static.
Un’istanza di una classe interna implicitamente mantiene un riferimento all’oggetto della classe esterna che lo ha creato. Questo non è vero per classi interne statiche:
Normalmente non è consentito inserire codice all’interno di una interfaccia, ma una classe interna statica può essere parte di una interfaccia.
Una classe può essere definita all’interno di un metodo, oppure di un blocco di codice (nel ramo di un costrutto if…else): classe interna locale.
Mostra codiceLe classi interne ai metodi hanno completa visibilità di tutte le variabili locali al metodo. Non è possibile specificare esplicitamente l'ambito di visibilità delle classi interne ai metodi – esse sono private ai metodi che le includono per definizione.
Dove:
final
serve per rendere visibile all'interno della classe interna il parametro passato al metodo, questo deve essere dichiarato final
, altrimenti si ha un errore a tempo di compliazione.
Perché usare classi interne?
Ogni classe interna può ereditare da un’altra classe o implementare un’interfaccia indipendentemente da quanto fatto dalla classe esterna.
Questo fornisce una ulteriore soluzione (oltre al ricorso alle interfacce) al problema dell’impossibilità della derivazione multipla presente in Java.
2. La modellazione a oggetti e il linguaggio UML (richiami)
3. Generalità su Java e la programmazione ad oggetti
6. Regole di traduzione da UML a Java/C++
7. Programmazione multi-thread
8. Sincronizzazione tra thread
9. Programmazione client-server con socket TCP/IP (Java networkin...
10. Programmazione di applicazioni client-server: il Pattern Proxy...
12. Design Patterns
13. Pattern architetturali - Esempi
14. Design pattern creazionali. Esempi
15. Design pattern strutturali. Esempi
16. Introduzione alle tecnologie middleware
17. Modelli di middleware: RPC, MOM, TP, TS
Bruce Eckel, “Thinking in Java” capitolo 6-7-8
J. Cohoon e J. Davidson, “Java – Guida alla Programmazione” paragrafo 7.4 e capitolo 9