Java by Andrea Mantoni Ultima modifica: %%mtime(%d %B %Y) = Introduzione = Java è sia un linguaggio sia una piattaforma di calcolo. Per evitare ambiguità, la piattaforma di calcolo è spesso indicata con la sigla *JVM=Java Virtual Machine*. I programmi in Java non sono compilati direttamente nel codice binario di una specifica architettura ma in un codice intermedio, detto *bytecode*. Il bytecode viene poi interpretato dalla JVM e tradotto nel codice nativo della macchina host a runtime. (Java) "compile once, run anywhere" vs. (C/C++) "write once, compile anywhere" Inoltre la JVM fornisce diversi servizi che garantiscono la sicurezza dell'ambiente d'esecuzione ("sandboxing"). Tutto ciò causa un overhead nell'esecuzione del codice maggiore rispetto al C/C++. Infatti gli obiettivi progettuali che Sun si è posta nella realizzazione di Java sono: - elevata sicurezza, anche a scapito delle prestazioni; - network-oriented, per la realizzazione di applicazioni di rete cross-platform; - ampia libreria embedded. Semplificando, il linguaggio Java può essere considerato come una "versione ridotta" del C++, in cui sono stati rimossi tutti quegli aspetti che minavano la sicurezza del codice (puntatori, variabili non inizializzate, gotos, etc.) ed è stata ampliata la standard library, aggiungendo il supporto per il networking, multithreading, e molto altro. A differenza del C++, Java presenta un maggiore orientamento ad oggetti ed una maggiore controllo sui tipi. == Confronto con il C++ == Passaggio di parametri: I tipi primitivi sono passati per valore SEMPRE I tipi derivati / gli oggetti / sono passati per riferimento SEMPRE. method naming conventions: essendo case-sensitive, per evitare capitalization errors, seguire questa regola: - all methods and member variables in the Java API begin with lowercase letters - all methods and member variables use capitalization where a new word begins e.g - getDoubleValue() Overloading -> consentito (~C++) Funzioni inline -> ~ metodi final Tipo "void" come argomento -> NON consentito Valori di default degli argomenti -> NON consentiti Funzioni nested -> NON consentite Dichiarazione senza definizione -> NON consentito (abstract is required) Overload del solo tipo di ritorno -> NON consentito NO preprocessor-like macros - Java has no preprocessor NO comandi "not a statement" (che non modificano la memoria) ad es. "5+6;" NO dichiarazione e definizione separate, only definitions NO forward declarations NO externally define storage for static members - inizializzazione dei campi automatica (statici e non) NO structs or enumerations or unions, only classes NO pointer arithmetic NO copy constructors - All object arguments are passed by reference NO destructors NO goto NO multiple inheritance NO template classes NO need to call free, (almost) NO memory leaks (thanks to the garbage collector) ??? NO pointers to members, NO "callbacks" for nested/inner classes ??? NO private or protected inheritance ... http://www.javacoffeebreak.com/articles/thinkinginjava/comparingc++andjava.html http://www4.ncsu.edu/~kaltofen/courses/Languages/JavaExamples/cpp_vs_java/ = Tipi = == Tipi primitivi e costanti == A differenza del C/C++ sono sempre dei tipi "esatti" (architecture-indipendent). boolean -> 1-bit char -> 16-bit (unicode) Interi: byte -> 8-bit short -> 16-bit int -> 32-bit long -> 64-bit NOTA: NON esistono tipi interi unsinged! Reali (in virgola mobile): float -> 32-bit double -> 64-bit Costanti: null -> object reference true, false -> boolean 'a', '\n', '\013', '\u03c0' -> char 10, -1 -> int 17L -> long 19F, 19f -> float 19.0, 19D, 19d, Infinity, NaN -> double == Casting == Può essere di due tipi: - *promozione* (da un tipo di lunghezza minore ad uno di lunghezza maggiore) - *troncamento* (da un tipo di lunghezza maggiore ad uno di lunghezza minore) La promozione PUO' essere implicita. Il troncamento invece NON può essere implicito, deve SEMPRE essere fatto esplicitamente: esempi: int i = 32; short s = 16; i = s; // promozione -> OK s = (short) i; // troncamento -> RICHIEDE casting esplicito Per questo motivo Java è classificato come un linguaggio più "strongly typed" rispetto al C/C++. == Wrapper Classes == Alcune consentono la gestione dei tipi primitivi come oggetti. Ad es.: Character Byte, Short Integer, Long Float, Double Altre consentono l'aritmetica a precisione arbitraria: java.math.BigInteger java.math.BigDecimal == Tipi derivati == Tutto ciò che non è un tipo primitivo (oggetti, stringhe, array) NON viene allocato automaticamente, ma DEVE essere allocato esplicitamente con la direttiva "new". La dichiarazione di un oggetto comporta solo l'allocazione del *reference|riferimento* a quell'oggetto: String s; // riserva 4-byte per il reference a s Un reference è sostanzialmente una variabile che "punta" ad un oggetto (~ ai puntatori del C/C++, ma NON modificabili con l'aritmetica dei puntatori per motivi di sicurezza). L'oggetto vero e proprio deve essere allocato con l'operatore "new": s = new String(); Le variabili dei tipi primitivi e i reference vengono memorizzati nello stack: int i = 5; String s; Tutti gli oggetti invece sono allocati nell'heap con la "new": MyClass name = new Myclass(...); // "name" è un riferimento ad un oggetto della classe "MyClass" creato con il suo costruttore Myclass(...) NOTA: non è necessario effettuare la delete degli oggetti perché questi vengono ripuliti automaticamente dal *garbage collector*. Tuttavia per rilasciare risorse differenti dalla memoria associate ad un oggetto (ad es. un file descriptor) si può usare il metodo "finalize()" che viene invocato automaticamente prima del garbage collector. == Tipi primitivi e derivati - Dichiarazione e istanziazione == Dichiarazione di un tipo primitivo (come in C/C++): tipo_primitivo nome_vararibile [= valore_inizializzazione]; In java le istanze di una classe non possono essere allocate staticamente, ma sempre in memoria dinamica con l'operatore "new". Per usare gli oggetti si usano quindi i riferimenti (~ ai puntatori in C/C++, ma NON modificabili per motivi di sicurezza). Dichiarazione tipo derivato/oggetto: tipo_derivato nome_riferimento [= new tipo_derivato( parametri_costruttore ) ]; == Tipi primitivi e derivati - Dichiarazione e istanziazione - Modificatori == ... abstract final ~ C/C++ const native static transient synchronized (vedi paragrafo "Threads") volatile (vedi paragrafo "Threads") == Tipi primitivi e derivati - Operatori di assegnamento e uguaglianza == Gli operatori di assegnamento tra tipi primitivi comportano una copia FISICA del valore delle variabili, mentre tra riferimenti copiano solo l'indirizzo dell'oggetto puntato. int i = j; // copia FISICA del valore MyClass obj1 = obj2; // copia del riferimento Allo stesso modo, gli operatori di uguaglianza == e != confrontano solo se l'oggetto "puntato" è lo stesso e non il suo contenuto effettivo. == Tipi primitivi e derivati - Passaggio di parametri == Se il parametro è un tipo primitivo, avviene sempre per valore ( = copia fisica del valore del parametro attuale in quello formale). Se invece è un oggetto viene copiato il suo riferimento e non l'oggetto stesso per motivi di efficienza. esempi: ... = Statements = Le regole sintattiche sono grossomodo le stesse come in C/C++: - uno statement Java termina con il ";" - gli statements possono essere raggruppati in blocchi, funzioni, classi, etc. NOTA: per motivi di sicurezza non è possibile "mascherare" le variabili in blocchi nidificati. == Condizioni logiche == Sono vietate condizioni logiche "implicite" (!= C/C++): if ( a ) // ERRORE if ( a != 0 ) // OK == Strutture controllo di flusso (~C/C++) == if ( ... ) { ... } else { ... } switch ( int_var ) { case ( ... ): default: break; } while ( ... ) { ... } do { ... } while ( ... ) for ( type init; condizione; incremento ) { ... } enhanced for statement (Java exclusive): for ( type o: array|collection ) { // cicla tutti gli elementi dell'array nella variabile locale "o" ... } === Interruzioni di ciclo === break; // goto fuori dal blocco continue; // goto all'inizio del ciclo seguente A differenza del C/C++ non esiste il comando di salto incondizionato "goto" ma è possibile comunque assegnare delle etichette ai comandi di ciclo e saltarci con: break label_name; // goto fuori dal while etichettato continue label_name; // goto all'inizio del ciclo seguente del while etichettato esempio: loop: while( ... ) { //... while( ... ) { // ... if( ... ) break loop; // esce fuori da entrambi i while if( ... ) continue loop; // esce dal while + interno } } = Classi = Java è un linguaggio fortemente orientato ad oggetti: TUTTO il codice deve essere contenuto in classi, NON esistono funzioni/variabili "globali/top level" che non siano metodi/campi di qualche classe. L'equivalente della funzione "main" è definito come un metodo pubblico e statico di una classe avente lo stesso nome (case sensitive) del file sorgente che si stà compilando: public class NomeFile { // altri metodi, campi e classi public static void main( String[] args ) { // corpo del main } } Di solito in ogni file sorgente in .java è presente una sola classe omonima pubblica "top level" che contiene tutto il codice. NOTA: nello stesso file sorgente sono comunque ammesse altre classi top-level con visibilità "package-protected" (la default), ma NON public, protected o private, e anche classi nested (vedi sotto). In questi casi il compilatore creerà tanti file .class quante sono le classi contenute nel sorgente. La variabili locali in un metodo NON sono inizializzate, mentre quelle "globali" (i campi di una classe) sono inizializzate a 0 o a null automaticamente. esempio: class NomeClasse { int i; // inizializzato a 0 String s; // inizializzato a null int i = 5; // inizializzato esplicitamente a 5 -- NOTA: in C++ non è possibile int[] ai = { 1, 2, 3 }; // inizializzato esplicitamente -- NOTA: in C++ non è possibile Object o = new Object( ... ); // inizializzazione di un oggetto con costruttore -- NOTA: in C++ non è possibile void Metodo( ... ) { int v; // NON inizializzato automaticamente! } } A differenza del C++ è possibile inizializzate i campi della classi direttamente nella loro dichiarazione. In questo caso le inizializzazioni vengono eseguite IN ORDINE e PRIMA dell'invocazione del costruttore. == Note sui costruttori == Come in C++ se il costruttore di default senza argomenti è overloadato esso non può essere più referenziato. Se si vuole ancora usarlo occorre ridefinirlo esplicitamente: MyClass() {} Un costruttore può invocare esplicitamente un altro costruttore (constructor chaining, NON presente in C++) con this( ... ); NOTA: questo DEVE essere eseguito come 1° comando del costruttore derivato. == Modificatori di accesso (~ classi di memoria in C/C++) == Il Java prevede una granularità maggiore rispetto al C++ nell'assegnazione delle classi di memoria. Piuttosto che definire dei "blocchi" public o private in una classe, la specifica si applica ai singoli simboli referenziabili (siano essi classi, metodi o campi). unspecified / default / package-private / "friendly" -> l'entità è visibile solo all'interno del package corrente public -> l'entità è visibile a tutti / anche all'esterno del package corrente private -> l'entità è visibile solo all'interno della classe corrente protected -> l'entità è visibile solo all'interno del package corrente e nelle classi derivate anche fuori dal package (!= C++) private protected (deprecated) -> l'entità è visibile solo all'interno della classe corrente e di quelle derivate (~C++ "protected") final -> il campo è constante (~ const C/C++) NOTA: per convenzione in suo nome è tutto MAIUSCOLO -> il metodo non è overloadabile nelle classi derivate (!= const return value C++) -> la classe non è ereditabile (NEW: Java-only) static (~ variabili/funzioni globali in C/C++) -> il campo è condiviso tra tutti gli oggetti della classe può essere riferito senza alcun oggetto della classe con la sintassi: .. NOTA: i campi statici possono essere inizializzati con un costruttore apposito static { ... }, contenente anche istruzioni (NEW: Java-only) -> il metodo può essere invocato "direttamente", senza alcun oggetto della classe: ..( ... ) NOTA: può accedere SOLO a campi static NON può accedere ai campi dinamici e/o a "this" -> garantisce l'esistenza una sola istanza della classe NOTA: applicabile solo a classi nested, è RICHIESTO se nella classe è presente almeno un campo/metodo statico non-final NOTA: static => final (quindi non può essere overloadato/mascherato nelle classi derivate) NOTA2: è preferibile limitare il n° di campi/metodi statici ed usare il metodo del "singleton" perchè facilita l'ereditarietà (vedi sotto). abstract -> il metodo è privo di implementazione è compito di una classe derivata fornirne una implementazione -> definisce la classe come astratta (non può essere istanziata, può avere dei metodi astratti) NOTA: Top-level classes can only have default, abstract, and final modifiers. Private, protected, and static modifiers are not admitted. Only one public top-level class is admitted per source file. == Ereditarietà == Non può essere multipla. Effettuata con l'operatore "extends". Vengono ereditati tutti i metodi/campi pubblici e protected, e mantenuti tali (~ eredità pubblica ": public" in C++). class Derived extends Base { //... } Anche il polimorfismo e l'overriding dei metodi sono analoghi al C++. Unica differenza: "Java does not support return-type-based method overloading." = Non è valido un overloading basato SOLO sul tipo di ritorno. Il casting da un tipo derivato ad uno base (upcasting) è implicito, mentre il viceversa DEVE essere fatto esplicitamente. Come in C++ i costruttori e gli operatori NON si ereditano. Tuttavia un costruttore di una classe derivata può invocare un construttore della classe base con: super( ... ); NOTA: questo DEVE sempre essere eseguito come 1a istruzione del costruttore. Se non viene eseguito esplicitamente, viene SEMPRE invocato "super()" senza argomenti automaticamente (~ C++). NOTA2: se nella classe base non è presente il costruttore di default perchè overloadato, nella classe derivata DEVE essere invocato un altro costruttore con "super(...)". Un metodo può invocare un metodo della classe base che overloada con: super.(...) NOTA: Il "super" non ha equivalente in C++ perchè a causa dell'eredità multipla si creerebbero delle ambiguità sulla classe base referenziata. NOTA2: non è possibile risalire più di un livello nella gerarchia delle classi (super.super.... non è ammesso!) == Interfacce (new Java-only feature) == Le interfacce sono classi astratte "vuote", che possono contenere SOLO: - dichiarazione di metodi senza implementazione / astratti - campi static final / costanti sintassi definizione: interface NomeInterfaccia { public void metodo(); public static final int COSTANT; //... } uso: Le interfacce definiscono dei comportamenti / insieme di funzionalità che devono essere implementate dalle classi. Ogni volta che una classe implementa un'interfaccia "firma un contratto" e DEVE implementare TUTTI i metodi astratti contenuti in essa. (Se non lo fà DEVE essere dichiarata astratta.) Anche se una classe non può estendere più classi può però implementare un numero illimitato di interfacce. Quindi le interaccie suppliscono alla mancanza dell'ereditarietà multipla in Java. class NomeClasse implements NomeInterfaccia1, NomeInterfaccia2, ... { // definizione di TUTTI i metodi astratti delle interfacce // ... } Anche se non possono essere istanziate le interfacce possono essere usate per creare dei reference agli oggetti delle classi che le implementano. In ciò si comportano come classi ereditate. esempio: NomeInterfaccia o = new NomeClasse(); o.metodoInterfaccia( ... ); Al fine di limitare la riscrittura di codice, se si è certi che non verranno utilizzati tutti i metodi di un'interfaccia, è possibile usare delle classi astratte dette "Adapters" che implementano tutti i metodi di un'interfaccia con corpi vuoti. La classi che le ereditano possono quindi ridefinire solo i metodi che andranno ad utilizzare effettivamente. == Classi nested == Le classi nested / inner classes sono classi definite all'interno di una classe "esterna" / enclosing class. Possono accedere a tutti i membri privati della classe esterna, ma NON viceversa. Possono essere statiche o no / "d'istanza". The rule for deciding whether a nested class should be static or non-static is simple: If the class needs to use any external instance variable or instance method, make it non-static. Otherwise, it might as well be static. Dichiarare una nested class equivale a dichiarare un nuovo tipo nello scope della classe esterna. Quindi un istanza della classe esterna NON include automaticamente anche una istanza della classe interna, a meno che quest'ultima non sia statica. Outer o = new Outer(); Outer.Inner i = o.new Inner(); // notare il reference "composto" // oppure, più concisamente: Outer.Inner i = new Outer().new Inner(); TRICK: In alternativa è possibile usare un campo esplicito della classe esterna per tenere una reference ad una istanza della classe interna. La reference può essere inizializzata con un costruttore della classe esterna o anche direttamente. public class Outer { private class Inner { ///... } public Inner innerIstance = new Inner(); //... } In questo modo quando viene istanziata Outer class viene creata automaticamente anche un'istanza di Inner. L'accesso alla istanza della classe interna avverrà quindi attraverso il campo "innerIstance" di Outer. === Classi nested speciali === Due tipi "particolari" di nested classes sono: - local inner classes Definite all'interno di un metodo. Possono accedere a TUTTI i membri della classe esterna. Possono accedere SOLO alle variabili locali del metodo dichiarate final. - classi "anonime" / anonymous classes Vengono istanziate e definite in loco usando un solo comando (che può trovarsi OVUNQUE!). Utilizzano il nome della classe che estendono o della interfaccia che implementano. BaseClass i = new BaseClass() { // BaseClass extension... }; // NOTA: è RICHIESTO! InterfaceName i2 = new InterfaceName() { // InterfaceName implementation... }; Le anonymous classes hanno cmq diverse limitazioni: - NON possono avere costruttori, utilizzano sempre e solo il costruttore di default della classe base - NON possono avere "membri propri", possono solo overloadare membri già esistenti nella classe/interfaccia di base - NON sono consentiti modificatori d'accesso (public, private, etc.) - DEVONO essere concrete, devono implementare TUTTI i metodi astratti della classe/interfaccia che estendono/implementano - ovviamente ne esiste una sola istanza ?? NON possono accedere all'istanza "esterna" == La classe speciale Object - metodi comuni a tutte le classi == In ultima analisi tutte le classi derivano da "java.lang.Object". Questi sono i suoi metodi (overloadabili): String toString(); // di default ritorna una stringa "nome classe@indirizzo this", // può essere overloadata per fornire informazioni di debug relative all'oggetto // viene invocata ad es. dalla println(Object ...) boolean equals(Object obj); // invocato per il confronto del CONTENUTO di 2 oggetti // NOTA: è diverso dal "==" che testa se 2 reference "puntano" allo stesso oggetto. // NOTA: può necessitare della ridefinizione anche del metodo "hashCode()" Object clone(); // ritorna una copia di un oggetto in una nuova allocazione // NOTA: può essere usato solo con classi che implementano l'interface "Cloneable" void finalize(); // invocato dal garbage collector prima di fare la delete di un oggetto. // Può essere overloadato per effettuare cleanups aggiuntivi alla cancellazione di un oggetto (~distruttore C++). == Un'altra classe speciale: java.lang.Runtime == Consente di interagire con l'OS. exec(String command); // esegue un comando esterno addShutdownHook(Thread hook); // registra uno shutdown hook // che sarà invocato automaticamente alla terminazione dell'applicazione (~ atexit C/C++) exit(int status); (Runtime) getRuntime(); // ritorna l'oggetto Runtime associato all'applicazione corrente load(String filename); // carica una libreria dinamica loadLibrary(String libname); // carica una libreria dinamica dal sistema == Singleton == Il sigleton è un design pattern per le classi. Per garantire l'esistenza di un UNICA istanza di una classe, piuttosto che rendere tutti campi e metodi statici, è preferibile usare la tecnica del singleton in quanto preserva la possibilità dell'ereditarietà. Consiste nell'ottenere l'unica istanza della classe invocando un metodo statico e non un costruttore. regole: 1. tutti i costruttori si rendono "private" 2. si crea una istanza della classe NELLA classe stessa come "private static", inizializzata a null 3. si crea un metodo pubblico per avere l'unica istanza della classe convenzionalmente firmato "public static NomeClasse getNomeClasse()" (oppure getInstance) che inizializza l'unica istanza della classe una sola volta ("inizializzazione pigra") 4. tutti gli altri metodi di accesso ai campi sono "public static" esempio: class Dadi { // 1. private ciao(...) { } // costruttore privato // 2. private static ciao i; // private unique istance // 3. public static ciao getCiao(...) // unique istance getter { if( i == null ) i = new ciao(...); // lazy inizialization return i; } // 4. altri metodi pubblici e statici public static Metodo1(); public static Metodo2(); [...] } esempio utilizzo: ciao c = ciao.getciao(); // ottengo l'unica istanza della classe invocando un metodo statico della classe e non un costruttore c.Metodo1(); // uso l'oggetto normalmente c.Metodo2(); // uso l'oggetto normalmente == JavaBean == E' un modo di scrivere le classi che ne facilita il riuso. Segue le seguenti convenzioni: - DEVE essere presente un costruttore pubblico senza argomenti - i campi della classe DEVONO essere accessibili con metodi getters e setters pubblici nominati covenzionalmente: getNomeCampo(), setNomeCampo(...) - implements java.io.Serializable = Package = Un package è un contenitore di classi. Ha il medesimo scopo dei namespace in C++: serve ad evitare conflitti di nome ed il name decorating. Ogni classe appartiene ad un package, ed il suo nome completo è: . NOTA: i nomi dei package sono case-sensitive come per gli altri simboli Nel referenziare le classi, di norma il package corrente viene omesso. Un package corrisponde ad una directory. I files sorgenti .java DEVONO quindi essere organizzati in subdirectories in modo da riflettere il package di appartenenza. (Ad ogni punto "." corrisponde una subdirectory) == Importazione (~ using C++) == Per importare una classe in un package si usa la direttiva: import .; E' possibile specificare "*" in luogo di per importare tutte le classi (subpackages esclusi!). NOTA: in OGNI file sorgente .java devo importare TUTTI i package che mi occorrono per la classe che sto definendo, non basta importarli in uno solo nel package corrente (ovvero le import non sono "ereditabili" come le include in C/C++) NOTA1: java.lang.* è importato automaticamente Esiste una facility "import static" che consente di "importare" uno o più membri statici di una classe e di referenziali come se fossero nella classe corrente. NOTA: importa SOLO MEMBRI STATICI DI CLASSI, NON CLASSI esempio: import static java.lang.Math.*; // importa tutte le costanti matematiche import static java.lang.Thread.*; == Definizione == Un package si definisce con la direttiva package ; posta all'inizio del file sorgente .java. Similmente ai namespace in C++, la definizione di un package può essere "espansa" in più file. Per limitare i conflitti di nome si usa per convenzione il dominio internet dell'autore rovesciato. Ad es.: ... http://www.domain.com/dept/user... -> com.domain.dept.user... NOTA: in fase di _compilazione_, per creare automaticamente le sotto-directory si DEVE specificare l'opzione "-d ". Ad es.: javac -d . MiaClasse.java -> crea il file "./com/domain/dept/user/MiaClasse.class" NOTA2: in fase di _esecuzione_ si farà SEMPRE riferimento alla classe con il suo nome completo (usando i punti come separatori). Il CLASSPATH punterà SEMPRE alla "directory base". Ad es.: java -cp . com.domain.dept.user.MiaClasse -> esegue il file "./com/domain/dept/user/MiaClasse.class" Di default, se non è specificato alcun package tutti i file nella stessa directory costituiscono un package a se stante "senza nome" (non importabile dall'esterno). Per questo motivo è buona norma, soprattutto per i progetti complessi, includere ogni classe in un package. == Java Archives == E' possibile raccogliere tutte le classi di uno o più package in un file .jar compresso comando di creazione: jar cvf MyJarName.jar * NOTA: di default opera ricorsivamente NOTA2: il basepath è SEMPRE la directory corrente (conviene invocarlo senza percorsi) E' anche possibile specificare un manifest file: jar cvfm MyJarName.jar "META-INF\MANIFEST.MF" * Se nel manifest è specificato l'attributo "Main-Class" il jar può essere mandato in esecuzione con l'opzione "-jar" senza specificare la classe contente il main. Altrimenti si DEVE SEMPRE specificare come CLASSPATH il file .jar ed anche la classe. = Eccezioni (~C++) = Le eccezioni in Java sono gestite come oggetti della classe "Exception". Ogni eccezione rappresenta una situazione di errore che può essere generata / lanciata / sollevata dal programmatore stesso o dalla JVM. Come in C++ il lancio dell'eccezione causa lo "stack unwinding": a partire dalla funzione corrente vengono abortite e rimosse dallo stack tutte le funzioni finchè l'eccezione non viene catturata (ciò può comportare l'interruzione del programma). Il main di default cattura tutte le eccezioni e le gestisce con printStackTrace(). OSS.: Le eccezioni possono essere o PROPAGATE/REITERATE o CATTURATE/GESTITE da un metodo, ma NON possono essere ignorate. == Cattura == Per catturare un'eccezione si utilizza il blocco "try ... catch ... finally". try { // metodi che potrebbero lanciare eccezioni } catch( TipoEccezione e ) { // cattura solo 1 eccezione specifica // gestione dell'eccezione } catch( Exception e ) { // cattura le eccezioni di tutti i tipi // gestione dell'eccezione e.printStackTrace(); } finally { // codice eseguito SEMPRE dopo il blocco try e dopo le catch // anche se si sono verificate eccezioni non gestite, prima dell'inizio dello stack unwinding // NOTA: "If the finally clause executes a return statement, it overides a thrown exception (so the exception will not be thrown; instead the return will occur)" // se finally esegue una return diventa equivalente ad una catch di una eccezione generica } I blocchi catch e finally sono mutualmente opzionali (ma almeno uno deve sempre esserci). Possono esserci più catch di eccezioni differenti e/o un solo finally. I catch vengono esaminati nell'ordine in cui sono specificati. Per questo motivo occorre specificare PRIMA i catch delle eccezioni PIU' SPECIFICHE e POI quelli delle eccezioni PIU' GENERICHE. == Lancio == Per lanciare/reiterare un'eccezione un metodo deve dichiararla nella sua firma con la clausola "throws". Quindi può sollevarla direttamente in qualsiasi punto con "throw new TipoEccezione(...)", oppure può invocare un metodo che lo fa al posto suo. esempio: void f() throws TipoEccezione, ... { //... throw new TipoEccezione(...); g(); // metodo con "throws TipoEccezione" } NOTA: Un eccezione è come un tipo di ritorno aggiuntivo: essa fa parte della firma del metodo e ne è caratterizzante. == Tipi di eccezioni == generica Exception(...) implicite/ non dichiarabili -> tutte i metodi possono lanciarle: NullPointerException (riferimento a NULL, ...) IndexOutOfBoundsException ClassCastException (errore di conversione) ArithmeticException (divisione per 0, ...) ArrayIndexOutOfBoundsException (argomenti non esistenti) ArrayStoreException ClassNotFoundException CloneNotSupportedException IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException InstantiationException InterruptedException NegativeArraysizeException NoSuchFieldException NoSuchMethodException NumberFormatException RuntimeException SecurityException StringIndexOutOfBoundsException UnsupportedOperationException ... dichiarabili / esplicite: FileNotFoundException IOException ... == Definizione di eccezioni custom == Si realizzano con classi specifiche che estendono "Exception" o che implementano l'interfaccia Throwable. ... esempio: public class NomeErrore extends Exception { NomeErrore() { // gestione dell'errore // ... super("messagio d'errore"); // stampa a schermo un messaggio d'errore } } == Stringhe == Le stringhe sono implementate come oggetti della classe "String". Per facilitarne l'uso, a differenza delle altre classi, possono essere usate senza l'uso di costruttori e con gli operatori algebrici: // dichiarazione ed inizializzazione con 1 costante String s = "hello"; // concatenazione frase = parola1 + parola2; // accodamento s += 5; // metodo che ritorna la lunghezza della stringa s s.length()(); // confronto tra 2 stringhe // NOTA: "==" è diverso! // Returns: true if the String are equal; false otherwise. (boolean) s.equals(String anotherString); (boolean) s.equalsIgnoreCase(String anotherString); (boolean) s.contentEquals(StringBuffer sb); // Returns: the value 0 if the argument string is equal to this string; a value less than 0 if this string is lexicographically less than the string argument; and a value greater than 0 if this string is lexicographically greater than the string argument. (int) s.compareTo(String anotherString); // confronto con espressione regolare // NOTA: seguono la sintassi Perl (boolean) s.matches(String regex); // estrazione di una sottostringa (d inizio INCLUSO a fine ESCLUSA) s.substring(0, n-1); // controlla presenza pre/postfissi s.startsWith( String ); s.endsWith( String ); // ricerca di sottostringhe (ritorna -1 se nn trovato) s.indexOf( String ); // legge un intero da una stringa (~ atoi() C) static int Integer.parseInt(String s); // converte qualsiasi oggetto in una stringa o.toString(); String.valueOf( Obj o ); Gli oggetti di tipo "String" sono immutabili / read-only, non possono essere modificati direttamente. Le stringe editabili sono fornite dalle classi StringBuffer (thread-safe) StringBuilder (not thread-safe) Esse non godono dello "zucchero sintattico" previsto per le String "read-only" (costanti, operatori, etc.). Devono quindi essere istanziate e manipolate invocando esplicitamente gli appositi metodi: costruttori: StringBuffer(); // costruisce una stringa vuota di 16 chars StringBuffer(int length); // costruisce una stringa vuota di length chars StringBuffer(String str); // costruisce una copia modificabile della stringa costante str metodi principali: (char) charAt(int index); // legge il char nell'i-esima posizione setCharAt(int index, char ch); // modifica 1 char // NOTA: index parte SEMPRE da 0 insert(int offset, Object ...) ; // aggiunge uno o più char append(Object ...); // aggiunge uno o più char in coda String toString(); ... == Tipi Enum == Sono delle classi speciali utilizzate per rappresentare insiemi di costanti. Definizione: enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } Un tipo enum può assumere SOLO i valori specificati nella sua definizione. E' possibile includere anche metodi e campi non costanti con una sintassi leggermente differente: enum Planet { MERCURY (3.303e+23, 2.4397e6), VENUS (4.869e+24, 6.0518e6), EARTH (5.976e+24, 6.37814e6), MARS (6.421e+23, 3.3972e6), JUPITER (1.9e+27, 7.1492e7), SATURN (5.688e+26, 6.0268e7), URANUS (8.686e+25, 2.5559e7), NEPTUNE (1.024e+26, 2.4746e7); // NOTA: richiesto ";" final double mass; final double radius; Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } // ... // other methods and fields // ... } In questo caso le costanti vengono create invocando il costruttore della classe ed hanno 2 campi associati. NOTE: All enums implicitly extend java.lang.Enum. Since Java does not support multiple inheritance, an enum cannot extend anything else. Per iterare tra i valori di un tipo enum è possibile usare il metodo statico values(): for ( Planet p : Planet.values() ) { // ... } = Array = Gli array sono implementati in modo particolare in Java: non sono un tipo derivato / delle istanze di una classe specifica, ma vengono dichiarati e gestiti come oggetti. La JVM esegue automaticamente l'inizializzazione ed il controllo del range sugli array, e lancia delle eccezioni in caso di errore. Come in C/C++ la dimensione dell'array una volta istanziato è FISSA. Comunque: "The array is the most efficient way that Java provides to store and randomly access a sequence of object references/primitives." // dichiarazione reference int[] nome; // dichiarazione reference alternativa (~C/C++) int nome[]; // dichiarazione e istanziazione int[] nome = new int[ size ]; // NOTA: viene inizializzato automaticamente a 0 o null. // NOTA2: size può essere anche un'espressione non costante // dichiarazione e istanziazione implicita con inizializzazione (~C/C++) int[] nome = { 1, 2, 3 }; // NOTA: nome.lenght = 3; String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; // dichiarazione e istanziazione di un array bidimensionale di 4 righe e 3 colonne // NOTA: le celle vengono inizializzate automaticamente al valore di default (0) double[][] nome = new double[4][3]; // dichiarazione e istanziazione implicita con inizializzaizone (~C/C++) // di un array bidimensionale di 2 righe e 3 colonne double [][] nome = { { 20.0, 19.3, 23.4 }, { 3.2, -5.4, 2.12 } }; // dichiarazione di un array di (references) ad arrays int[][] frequencies = { frequenciesRed, frequenciesGreen, frequenciesBlue }; // lettura/scrittura con indice (partono da 0 ~C/C++) nome[ 1 ]; // campo che ritorna la lunghezza dell'array (0 se è vuoto) - NOTA: è l'UNICO campo degli array nome.length == La classe java.util.Arrays == Contiene alcune utilities per manipolare gli array: fill( arrayRef, (type)value ); // fill an array with a constant value fill( arrayRef, startIndex, endNotInclusive, (type)value ); // fill a sub-array with a constant value arraycopy( srcArrayRef, srcArrayStartIndex, destArrayRef, destArrayStartIndex, noElements2Copy ); // copy 2 arrays contents equals( arrayRef1, arrayRef2 ); // compare two arrays for equality sort( arrayRef ); // sort the array binarySearch( ... ); // find an element in a sorted array - ritorna la posizione o -1 se l'elemento non è trovato [...] NOTA: l'indice di'inizio è SEMPRE inclusivo, mentre quello di fine NO. = Contenitori generici = Sono delle sotto-interfacce di "java.util.Collection". Non sono implementati con i template/non hanno dei parametri come in C++. Essi ospitano references della classe Object, quindi: - quando si INSERISCE un oggetto si perde l'informazione del tipo. - quando si ESTRAE un elemento da essi BISOGNA castarlo esplicitamente. TRICK: è possibile "wrappare" il contenitore generico con una classe derivata che accetti e ritorni solo oggetti di un determinato tipo. NOTA: J2SE 5.0 ha aggiunto il supporto per i Generics (~template C++). == Metodi comuni == Derivano da "java.util.Collection": add(Object o) ; // aggiunge un oggetto nel contenitore // NOTA: si perde l'informazione del tipo remove(Object o); // rimuove un oggetto dal contenitore // ??? NOTA: viene invocato il metodo "equals(Object obj)" per la ricerca (boolean) contains(Object o); // ricerca / controlla se l'oggetto o è presente size(); // ritorna il n° di elementi all'interno clear(); // svuota il contenitore / rimuove tutti gli elementi isEmpty(); // testa se è vuoto Object[] toArray(); // converte il contenitore in un array T[] toArray(T[] a); // converte il contenitore in un array dello STESSO TIPO dell'array passato come argomento === Iteratori === Rappresentati dall'interfaccia "java.util.Iterator". Vengono creati con il metodo "iterator()" del contenitore d'interesse: Iterator i = c.iterator(); // crea un iteratore che // punterà al 1° elemento dopo la prima chiamata a next() (boolean) i.hasNext(); // testa se c'è un elemento nella posizione seguente (Object) i.next(); // legge l'elemento seguente ed incrementa l'iteratore // NOTA: ritorna un oggetto generico, quindi necessita di casting esplicito remove(); // rimuove l'elemento correntemente puntato e NON incrementa l'iteratore In Java gli iteratori hanno meno funzionalità rispetto al C++, ad es. non esistono gli operatori di incremento e decremento. uso tipico: while( i.hasNext() ) { Obj o = (Obj) i.next(); //... } == Modifica dell'ordinamento / overloading degli operatori di confronto == ... == Liste == E' un contenitore ORDINATO di elementi (anche ripetuti). In Java è un'interfaccia e non una classe. Ne esistono le seguenti implementazioni: LinkedList - inserimento in O(1), accesso in O(n) ArrayList - inserimento in O(n), accesso in O(1) Vector = come ArrayList + thread-safe, see http://www.javaworld.com/javaworld/javaqa/2001-06/03-qa-0622-vector.html NOTA: i tipi Stack e Queue si possono facilmente ricavare wrappando una List. esempio utilizzo: List l; List l = new LinkedList(); List l = new ArrayLists(); l.add( el ); // inserimento in coda l.add( n, el ); // inserimento in posizione "n" (NOTA: gli indici partono da 0) l.addFirst( el ); // inserimento in testa l.get( n ); // lettura "n"-esimo elemtno l.getFirst(); // lettura testa l.remove( n ); // rimozione "n"-esimo elemtno l.removeFirst(); (boolean) l.contains( el ); // ricerca / verifica se un elemento è presente (int) l.indexOf( el ); // ricerca e ritorna la posizione di un elemento (NOTA: ritorna -1 se non presente) l.toString(); // stampa il contenuto della lista l.toArray( new Tipo[0] ); // conversione in array "Tipo" l.listIterator() ; // ritorna un iteratore speciale per le liste == Set / Insieme == Come in matematica, è un contenitore NON ORDINATO di elementi NON ripetuti. E' implementato da: HashSet - inserimento e cancellazione in O(1), ricerca in O(n) TreeSet - inserimento, cancellazione e ricerca in O(logn) ... == Map / Tabella == La map è una struttura dati associativa che mette in correlazione una chiave (univoca) con un valore/dato (non univoco). Anch'essa è un'interfaccia. Le implementazioni sono: TreeMap - ordinata -> inserimento, cancellazione e ricerca in O(logn) HashMap - non ordinata -> inserimento e cancellazione in O(1), ricerca in O(n/b) Hashtable - come HashMap + thread-safe esempio utilizzo: Map m; Map m = HashMap(); Map m = TreeMap(); m.put( chiave, elemento ); m.get( chiave ); m.containsKey(); m.containsValue(); m.keySet(); // ritorna il Set delle chiavi (può essere usato per iterare) m.values(); // ritorna il Set dei valori (può essere usato per iterare) = I/O = Il meccanismo base per l'I/O in Java sono gli *streams*. Gli streams possono essere di diversi tipi: - di base / top-level - derivati / intermediate / filtered e - byte-oriented -> Streams "normali", derivati da Input/OutputStream - char-oriented -> Character streams, derivati da Reader/Writer e - Read-only, derivati da InputStream o Reader - Write-only, derivati da Outputstream o Writer NOTA: Input/OutputStream sono classi astratte, le seguenti no. == Streams byte-oriented top-level == Uno stream "top-level" rappresenta un estremità di un canale di comunicazione tra 2 entità (di cui una è sempre il processo/thread corrente): - 1 processo ed 1 file -> *FileInputStream / FileOutputStream* costruttori: FileInputStream(String filename); FileInputStream(File filename); FileOutputStream(String filename); FileOutputStream(File filename); - 1 processo ed 1 buffer in memoria -> *ByteArrayInputStream / ByteArrayOutputStream* costruttori: ByteArrayInputStream( byte[] buf ); ByteArrayOutputStream( ); // dynamic size - 2 processi remoti NOTA: non vengono costruiti direttamente, ma ottenuti da oggetti Socket (vedi paragrafo). Tutti questi streams top-level sono byte-oriented, ovvero utilizzano come unità di trasferimento il byte. == Metodi principali di *InputStream == int read(); // legge e ritorna 1 byte int read(byte[] buf); // legge fino a riempire il buffer, ritorna il n° di bytes letti entrambe ritornano -1 quando lo stream viene chiuso int available() // ritorna il n° di byte disponibili per la lettura void close(); // chiude lo stream == Metodi principali di *OutputStream == write(int b); // scrive gli 8 bit meno significativi dell'intero b write(byte[] buf); // scrive un intero array void flush(); // flusha il buffer locale associato allo stream void close(); // chiude lo stream (e flusha) == Metodi di Input/OutputStream - caratteristiche comuni == - byte-oriented - FIFO = consegnano i dati in ordine - bloccanti - throws IOException == Streams intermediate/filtered == Non possono essere utilizzati autonomamente ma necessitano di uno stream top-level al quale appoggiarsi. Forniscono delle funzionalità aggiuntive agli streams di base: - buffering -> BufferedInputStream / BufferedOutputStream - trasmissione di tipi primitivi -> DataInputStream / DataOutputStream { metodi: readBoolean/Byte/Short/Int/Long/Float/Double/Char(); writeBoolean/Byte/Short/Int/Long/Float/Double/Char(...); (String) readLine(); // DEPRECATED -> usare BufferedReader/Writer (String) readUTF(); // DEPRECATED -> usare BufferedReader/Writer writeChars/UTF(String s); // DEPRECATED -> usare BufferedReader/Writer } - trasmissione di stringhe -> PrintStream (Write-only) // DEPRECATED -> usare PrintWriter { metodi: void print(String s); void println(String s); void format(...); } - trasmissione di oggetti / serializzazione -> ObjectInputStream / ObjectOutputStream { (Object) readObject(); (void) writeObject(Object obj); // ... + tutti i metodi di DataInput/OutputStream } NOTA: Gli oggetti DEVONO essere istanze di classi che implementano l'interfaccia java.io.Serializable. http://en.wikipedia.org/wiki/Serialization#Java http://java.sun.com/docs/books/tutorial/essential/io/objectstreams.html NOTA2: Per garantire la "long-term persistence" degli oggetti serializzati su file si può usare la java.beans.XMLEncoder/XMLDecoder http://java.sys-con.com/node/37550 http://java.sun.com/products/jfc/tsc/articles/persistence4/ http://java.sun.com/docs/books/tutorial/javabeans/ (richiede che l'oggetto segua le convensioni di JavaBean). Tutti i loro costruttori richiedono che venga specificato come argomento a quale stream di base vengono "attaccati": BufferedInputStream/DataInputStream( InputStream in ) BufferedOutputStream/DataOutputStream/PrintStream( OutputStream out ) Dopo la loro costruzione, i campi protetti .in e .out conterranno la reference a tali streams. == Character streams: Reader/Writer == A differenza degli streams precedenti che sono byte-oriented, le classi derivate da Reader/Writer sono character-oriented. Facilitano quindi l'I/O di dati testuali. La loro logica di funzionamento è analoga agli streams byte-oriented. Character streams top-level: - 1 processo ed 1 file -> *FileReader/Writer* - 1 processo ed 1 buffer in memoria -> *CharArrayReader/Writer* - 2 processi remoti -> NIENTE! Character streams filtered: - buffering -> BufferedReader/Writer { (String) readLine(); } - trasmissione di stringhe -> PrintWriter (Write-only) { metodi: void print(String s); void println(String s); void format(...); } - bridging con I/OStream -> InputStreamReader/Writer [...] Una particolare classe derivata è InputStreamReader/Writer che consente il bridging con uno stream byte-oriented. [...] == Streams speciali == Vengono aperti automaticamente dalla JVM all'avvio. Consentono l'I/O su console. Sono dichiarati come campi statici di java.lang.System: InputStream System.in ~= stdin C/C++ PrintStream System.out ~= stdout C/C++ PrintStream System.err ~= stderr C/C++ NOTA: To use Standard Input as a character stream, wrap System.in in InputStreamReader: InputStreamReader cin = new InputStreamReader( System.in ); Esiste anche la classe speciale java.io.Console per interagire direttamente con la console (utile ad es. per la richiesta di passwords). Per ridirezionare gli streams di default si usano i seguenti metodi: setIn( InputStream ); setOut( PrintStream ); setErr( PrintStream ); == java.io classes summary == oggeti di base per la manipolazione dei file: FileInputStream / FileReader FileOutputStream / FileWriter oggetti di base per la memoria: ByteArrayInputStream / CharArrayReader ByteArrayOutputStream / CharArrayWriter oggetti derivati per il buffering: BufferedInputStream / BufferedReader (fornisce readLine(), per leggere stringhe) BufferedOutputStream / BufferedWriter oggetti derivati per il filterning: DataInputStream / DataOutputStream (solo per tipi primitivi, per scrivere/leggere dati "grezzi") PrintStream / PrintWriter (per scrivere stringhe, forniscono print() e println(), consentono la scrittura di tutti gli oggetti usando toString()) NOTA: PrintStream ricodifica i char da 16 a 8-bit, mentre PrintWriter li scrive in Unicode (preferibile) casi d'uso: manipolazione file binari generici: FileI/OStream + BufferedI/OStream manipolazione file di dati semplici / primitivi: FileI/OStream + BufferedI/OStream + DataI/OStream manipolazione file di testo per caratteri: FileReader/Writer + BufferedReader/Writer manipolazione file di testo per linee: FileReader/Writer + BufferedReader/Writer (+ PrintWriter, write-only) manipolazione array di byte in memoria: ByteArrayI/OStream ? manipolazione array di tipi primitivi: ByteArrayI/OStream + DataI/OStream (NON SI PUò FARE?) manipolazione array di chars: CharArrayReader/Writer ? manipolazione array di stringhe: (NON SI PUò FARE?) ? comunicazione con un socket binaria: I/OStream (ottenuto dal socket) ? comunicazione con un socket per linee: I/OStream (ottenuto dal socket) + DataInputStream/PrintStream == Manipolazione di stringhe mediante gli streams == === Scanning === Per tokenizzare una stringa in Java si possono usare degli oggetti appositi detti "Scanner" (da java.util.Scanner). costruttori: Scanner(String source); Scanner(InputStream source); Scanner(Reader source); Il loro uso è analogo agli iteratori: Scanner s = new Scanner( "stringa_da_tokenizzare" ); // creazione s.useDelimiter("_"); // setup while( s.hasNext() ) // iterazione sui tokens { string n = s.next(); // estrazione del prossimo token // ... } s.close(); // chiusura === Formatting === Effettuato mediante oggetti della classe PrintWriter (di solito) o PrintStream (più raramente). metodi di base: .print( ) .println( ); .format("", ...objects... ); NOTA: la è analoga a quella usata dalla printf in C codici di escape più usati: %d -> int %f -> decimal %n -> newline / platform-specific line separator (NEW) %tx, %Tx -> date and time conversion characters == Parsing == [...] Per parsare i file XML Java fornisce org.xml.sax.XMLReader e javax.xml.parsers.SAXParser [...] == Manipolazione del file-system == La classe java.io.File consente l'accesso e la manipolazione del file-sytem, ma NON del contenuto dei files. Metodi più usati: File(String pathname); // costruttore // NOTA: pathname può essere sia un file che una directory (String) File.separator // costante che rappresenta il simbolo usato come // separatore nel path ("\" su Windows, "/" su Linux) boolean createNewFile() throws IOException; // crea il file vuoto, se non esiste già (File) File.createTempFile(prefix, null); (throws IOException) boolean exists(), canExecute(), canRead(), canWrite() boolean isDirectory(), isFile(), isHidden() long length(), lastModified() String getName(), getParent(), getPath(), getAbsolutePath() File getParentFile(), getAbsoluteFile() boolean delete() boolean renameTo(File dest) boolean mkdirs() String[] list() // returns an array of strings naming the files and directories in the directory denoted by this File[] listFiles() // come list, ma ritorna un array di Files NOTA: ritornano tutte true = success, false otherwise NOTA2: molti metodi lanciano "SecurityException" se non si hanno le autorizzazioni necessarie == File ad accesso casuale == Mediante la classe "java.io.RandomAccessFile" è possibile creare e manipolare file ad accesso casuale. ... http://java.sun.com/docs/books/tutorial/essential/io/rafs.html == Networking == Il package "java.net" fornisce varie classi per la gestione di connessioni di rete. === Connessioni TCP === Per creare una connessione TCP tra due peer entities della arch TCP/IP si utilizzano i socket. L'indirizzo di un socket è composto da: : Una connessione TCP di norma si articola in 3 fasi: 1. apertura della connessione: - il server crea un oggetto ServerSocket e si mette in ascolto invocando la accept() su di esso - il client crea un oggetto Socket e apre la connessione 2. dialogo per mezzo della connessione - il server genera 1 o + I/OStream dal Socket ritornato dalla accept() - il client genera 1 o + I/OStream dal Socket precedentemente creato 3. chiusura della connessione - il server chiude ordinatamente: l'InputStream, l'OutputStream, il Socket della connessione - il client chiude ordinatamente: l'InputStream, l'OutputStream, il Socket della connessione === Connessioni TCP - Classi e metodi principali === Socket( String host, int port ); // costruttore di Socket (client-side) ServerSocket( int port ); // costruttore di ServerSocket (server-side) (Socket) ServerSocket.accept(); // il server si mette in attesa (bloccante) di una connessione // ritorna il socket della connessione stabilita (InputStream) .getInputStream(); (OutputStream) .getOutputStream(); void close(); (InputStream) .getInputStream(); (InputStream) .getInputStream(); == Connessioni UDP == == Connessioni SSL == = Thread = Un thread rappresenta un "flusso d'esecuzione" indipendente dell'applicazione. Ogni processo può avere più threads in esecuzione interleaved. Java fornisce supporto a livello di linguaggio per la creazione e la gestione dei threads. La classe "java.lang.Thread" è la rappresentazione runtime di un thread. Essa ha vari costruttori: Thread(); Thread(Runnable target); Metodi statici (con effetto sul thread corrente/che li invoca): Thread.currentThread(); // ritorna una reference al thread corrente Thread.sleep(millis) throws InterruptedException; // dorme per il tempo specificato o finchè arriva un interruzione Thread.yield(); // says: "end my timeslice prematurely, look around for other threads to run. If there is nothing better than me, continue" (boolean) Thread.interrupted(); // controlla se si è ricevuta un interruzione E la rimuove ??? (boolean) Thread.holdsLock(Object obj); // controlla se il thread possiede il lock su un certo oggetto Metodi d'istanza / non-statici: void run(); // corpo del thread void start(); // avvia l'esecuzione del thread void stop(); // termina manualmente il thread DEPRECATED: void suspend/resume(); boolean isAlive(); // ritorna SEMPRE true dopo lo start, anche se è blocked void join() throws InterruptedException // attende la terminazione del thread (bloccante) void join(long millis) throws InterruptedException // attende la terminazione del thread per il tempo specificato (non bloccante) void interrupt(); // invia un interruzione boolean isInterrupted(); // controlla se ha ricevuto un interruzione E NON la rimuove String getName(); void setName(String name); int getPriority(); void setPriority(int newPriority); NOTA: i metodi/campi d'istanza possono anche essere usati sul thread corrente con Thread.currentThread().... Ogni programma java ha almeno un thread chiamato "main". Da questo è possibile creare nuovi thread e controllarne l'esecuzione. == Creazione di nuovi thread == E' necessario istanziare un oggetto di tipo thread ed invocare "start()" su di esso. E' necessario creare una classe che: o estende Thread (non sempre possibile) class NomeClasse extends Thread { ... } o implementa l'interfaccia Runnable class NomeClasse ... implements Runnable { ... } L'interfaccia Runnable ha un solo metodo astratto: run(); Nel primo caso basterà creare un'istanza della classe ed invocare start() su di essa. Thread t = new NomeClasse( ... ); t.start(); più concisamente: new NomeClasse( ... ).start(); Nel secondo caso bisognerà creare un oggetto Thread passando al costruttore un'istanza della classe che implementa Runnable. Runnable r = new NomeClasse( ... ); Thread t = new Thread( r ); t.start(); più concisamente: new Thread( new NomeClasse( ... ) ).start(); La JVM assegna automaticamente un nome ad ogni thread: - "main" è il thread iniziale di ogni programma - "Thread-N" è l'N-esimo thread creato è possibile specificare un nome mettendo una stringa come ultimo argomento del costruttore. Inoltre ad ogni thread è associata una priorità void setPriority(int newPriority); int getPriority(); Thread.NORM_PRIORITY // predefinita Thread.MIN_PRIORITY Thread.MAX_PRIORITY ??? La priorità influenza il tempo della CPU assegnato al processo e ... E' anche possibile impostare un tread come "daemon thread", ??? ovvero la sua esecuzione continuerà all'esterno del processo corrente (utile per servers) con setDaemon(boolean on); == Stati == Come i processi, anche i thread durante la loro esecuzione possono transitare in diversi stati: - New = thread creato, ma non avviato in esecuzione - Runnable = eseguibile, è stato invocato start() su di esso - Running = attualmente in esecuzione (1 solo alla volta su una CPU single core) - Blocked = in pausa, ad es. se il thread esegue una sleep() o è in attesta di I/O - Dead = il thread termina quando si invoca "return" dal metodo run() o avviene un eccezione. NOTA: Quando un thread termina la sua esecuzione, lo stesso thread non può essere eseguito nuovamente (ovvero la "memoria interna" del thread è perduta). == Interruzioni == Sono un meccanismo di comunicazione tra thread. L'invio avviene con il metodo d'istanza ".interrupt()". Il thread ricevente DEVE controllare la ricezione dell'interruzione. Ciò può essere fatto in due modi: - se si invocano molti metodi che lanciano InterruptedException, si può catturare l'eccezione try { // methods that throws InterruptedException //... } catch (InterruptedException e) { // ... } - altrimenti si può controllare manualmente con if (Thread.interrupted()) { // ... } // NOTA: l'interruzione viene rimossa == Sincronizzazione == Tutti i thread condividono le stesse "risorse" (memoria, files aperti, etc.) assegnate al processo, per questo possono sorgere dei problemi di concorrenza. E' necessario quindi un meccanismo di sincronizzazione. In java ne esistono 2 tipi: - syncronized methods class ... { //... public synchronized void method() { //... } } Garantiscono: - che l'esecuzione del metodo non può essere interrotta / interleaved - nessun altro thread può invocare altri metodi sincronizzati sullo stesso oggetto. NOTA: i metodi statici hanno un lock indipendente da qualsiasi oggetto della classe - synchronized statements | instruction blocks syncronized( objectname ) { //... } Ad ogni oggetto la JVM associa un "lock flag" ed un thread che ha eventualmente il lock su di esso. I metodi sincronizzati sono analoghi a public void method() { synchronized( this ) { //... } } Quindi richiedono l'acquisizione del lock sull'oggetto di invocazione. TRICK: Nel caso in cui la sincronizzazione coinvolga i campi della classe A GRUPPI è possibile istanziare degli oggetti generici per il controllo dei locks: public class ... { private int c1 = 0; private int c2 = 0; private int c3 = 0; private int c4 = 0; private Object lock1 = new Object(); // restrict access to c1, c2 private Object lock2 = new Object(); // restrict access to c3, c4 public void method1() { synchronized(lock1) { c1++; c2--; } synchronized(lock2) { c4++; } } public void method2() { synchronized(lock2) { c3++; c4--; } } } === Guarded Blocks === Un altro meccanismo di sincronizzazione tra threads sono i guarded blocks: public synchronized void method() throws InterruptedException { while( !condition ) { try { obj.wait(); } catch InterruptedException { //... } } // condition verified } Con l'invocazione della .wait() il thread corrente rilascia il suo lock sull' oggetto di invocazione e potenzialmente fa entrare un altro thread nel monitor. Dopo l'invocazione della wait il thread diventa blocked. Tornerà running quando l'altro thread che nel frattempo ha ricevuto il lock invocherà notify() o notifyAll() e/o uscirà dal blocco syncronized. NOTA: il thread può essere risvegliato anche con l'invio di una InterruptedException NOTA2: è possibile invocare wait() e notify() SOLO in blocchi syncronized === Variabili volatile === La sincronizzazione è richiesta solo per classi "complesse" come i contenitori, etc. Per i tipi primitivi la JVM garantisce automaticamente l'accesso in mutua esclusione. Tuttavia, per migliorare le performance d'esecuzione spesso le variabili globali sono bufferizzate localmente nei thread. Per disattivare questa bufferizzazione con i problemi di concorrenza che ne derivano è necessario dichiarare le variabili condivise con il modificatore "volatile". = JDBC = Java DataBase Connectivity = JDBC è l'API standard di Java per l'accesso ai database. JDBC può essere usata in qualsiasi tipi di applicazione Java (standalone, applets, servlets, etc.) JDBC fornisce le seguenti funzionalità: - connessione - query / interrogazione / ricerca - modifica - gestione delle transazioni ... Per ottenere l'indipendenza dai vari database, JDBC si appoggia ai dei *vendor-specific drivers* e per dialogare con essi. esempio d'uso: // 1. prova a caricare un driver try { Class.forName("connect.microsoft.MicrosoftDriver"); Class.forName("oracle.jdbc.driver.OracleDriver"); Class.forName("com.sybase.jdbc.SybDriver"); } catch(ClassNotFoundException cnfe) { System.err.println("Error loading driver: " + cnfe); } // 2. costruisce l'indirizzo del server a cui connettersi String host = "dbhost.yourcompany.com"; String dbName = "someName"; int port = 1234; String oracleURL = "jdbc:oracle:thin:@" + host + ":" + port + ":" + dbName; String sybaseURL = "jdbc:sybase:Tds:" + host + ":" + port + ":" + "?SERVICENAME=" + dbName; String msAccessURL = "jdbc:odbc:" + dbName; // 3. stabilisce la connessione String username = "jay_debesee"; String password = "secret"; Connection connection = DriverManager.getConnection(oracleURL, username, password); // 4. crea un oggetto "Statement" per l'invio di statements SQL al database Statement statement = connection.createStatement(); // 5. esegue una query SQL String query = "SELECT col1, col2, col3 FROM sometable"; ResultSet resultSet = statement.executeQuery(query); // stampa il risultato while(resultSet.next()) { System.out.println(resultSet.getString(1) + " " + resultSet.getString(2) + " " + resultSet.getString("firstname") + " " resultSet.getString("lastname")); } // ... (esegue altri statements SQL) // 6. chiude la connessione connection.close(); == Gestione della connessione e delle transazioni == apertura connessione: Connection DriverManager.getConnection(String URL, String username, String password); "URL" deve essere formattato... Di default, tutti gli statements sono inclusi in un unica transazione che termina con la chiusura della sessione. Per delimitare la fine di una sessione bisogna inviare: ?? Connection.commit() = // conferma le modifiche della sessione corrente ?? Connection.rollback() = // annulla modifiche della sessione corrente == Statements == == Prepared statements == I prepared statements sono degli statements SQL parametrizzati. I parametri sono indicati con dei "?" nel codice SQL. I parametri sono quindi impostati prima dell'esecuzione dello statement con i metodi di Statement: statement.setInt(int parameter_position, int parameter_value); statement.setFloat(int parameter_position, float parameter_value); ... OSS.: i parametri sono indicizzati da 1. = GUI = In Java sono disponibili diverse interfacce per la realizzazione di GUIs: - *AWT=Abstract Window Toolkit* -> si appoggia all'engine grafico nativo della piattaforma PROs: disponibile su tutte le implementazioni di Java, più veloce, look'n'feel nativo della piattaforma CONs: alcune funzioni potrebbero non essere disponibili su alcune piattaforme - *Swing* (since J2SE 1.2) PROs: totalmente indipendente dalla piattaforma, il rendering è effettuato direttamente da Java2D, look'n'feel personalizzabile CONs: più lento, non disponibile su tutte le implementazioni (maggiore complessità per il porting) Un altro toolkit non ufficiale è lo *SWT=Standard Widget Toolkit* dal progetto Eclipse. = AWT = Abstract Window Toolkit = I package java.awt = Abstract Window Toolkit, e la sua evoluzione javax.swing mettono a disposizione del programmatore una serie di classi e interfacce per la creazione di GUI. Una GUI in Java è realizzata mediante: - istanzazione di oggetti Component; - la gestione degli eventi mediante Listeners da associare ai componenti. I listener consentono la comunicazione tra la GUI e l'applicazione vera e propria. == I componenti == Tutti i componenti grafici derivano dalla classe astratta java.awt.Component e dispongono dei seguenti metodi: void setName(String name); // associa un nome interno/logico void setToolTipText(String); // associa un suggerimento al componente int getHeight(); int getWidth(); Point getLocation(); int getX(); int getY(); String getName(); Container getParent() ; String paramString(); // returns a string representing the state of this component void setLocation(int x, int y); // associa una posizione assoluta in pixel (meglio usare i layout manager) void setSize(int width, int height); // (meglio usare i layout manager) void setBounds(int x, int y, int width, int height); // imposta posizione e dimensioni assolute void setPreferredSize(Dimension preferredSize); void setMin/MaximumSize(Dimension min/maximumSize); void setBackground(Color c); // imposta il colore di sfondo void setForeground(Color c); void setFont(Font f); void setVisible(boolean b); // rende l'oggetto visibile/invisibile NOTA: DEVE essere invocato sui contenitori top level (vedi sotto) creati invisibili, per tutti gli altri componenti no. void show(); // shortcut deprecated di setVisible(true); void hide(); // shortcut deprecated di setVisible(false); void paint(Graphics g); // metodo invocato automaticamente per il rendering void repaint(); // metodo invocato manualmente per il re-rendering void update(); TODO: see http://java.sun.com/products/jfc/tsc/articles/painting/index.html add*Listener( java.util.EventListener e ); // associa un Listener di un evento (vedi gestione eventi) processEvent(AWTEvent e); // metodo invocato automaticamente alla ricezione di un evento Alcune classi derivate direttamente da java.awt.Component forniscono i principali controlli / "componenti atomici" / usati per l'interazione diretta con l'utente: Button = pulsante cliccabile Label = etichetta di testo statica TextComponent TextField = campo di testo TextArea = area di testo multi-linea Checkbox Choice List Scrollbar = barra di scorrimento Canvas = area d'interazione generica MEMO: see http://it.wikipedia.org/wiki/Widget == I contenitori == Altre classi derivate invece forniscono i cosiddetti *Containers*, ovvero oggetti contenitori di componenti. Component | |--- Container | |--- Panel | |--- Applet | |--- Window = finestra generica senza bordi e senza menu | |--- Frame = finestra con controlli completi | |--- Dialog = finestra di dialogo senza menu | | |--- FileDialog = finestra di selezione file | |--- ScrollPane [...] I contenitori sono di due tipi: - Top-level (Frame, Dialog e Applet) - Intermediate-level (Window e Panel) Ogni componente per poter essere visualizzato DEVE essere aggiunto ad un contenitore top-level. ( A top level container must appear at the root of any containment heirarchy because only top level containers have the necessary behaviour to draw themselves on a screen - all other components can only be drawn on other containers. ) Tipicamente il contenitore "base" top level che tutte le GUI estendono è un Frame o un Applet. (Gli altri possono essere creati solo dopo attaccandoli ad un contenitore top level.) I metodi più usati della classe Container: add(Component comp); // aggiunge un componente al container add(String name, Component comp); // aggiunge il componente al contenitore e gli da un nome add(Component comp, int index); // aggiunge il componente nella posizione indicata del contenitore NOTA: "index" di solito è una costante dipendente dal Layout Manger (vedi sotto). Per BorderLayout le costanti disponibili sono: BorderLayout.NORTH, SOUTH, EAST, WEST, e CENTER. remove(Component comp); // rimuove un componente dal container Component[] getComponents() ; // ritorna tutti i componenti nel container La disposizione grafica dei componenti in ogni container è determinata dal layout manager. AWT supporta i seguenti layout: BorderLayout (default per Window e Frame) = i componenti sono disposti lungo i bordi CardLayout -> to display an area that contains different components at different times FlowLayout (default per Panels e Applets) -> dispone tutti i componenti su una singola riga GridBagLayout GridLayout(m,n) = tutti i componenti hanno le stesse dimensioni e sono disposti in una griglia m x n MEMO: http://java.sun.com/docs/books/tutorial/uiswing/layout/visual.html Metodi per la gestione del layout: setLayout(LayoutManager mgr); LayoutManager getLayout(); doLayout(); // applica il layout corrente == Window e Frame == Sono particolari classi derivate di Container. Forniscono alcuni metodi di uso comune nelle inizializzazioni delle finestre: dispose(); // cancella la finestra dallo schermo e libera le risorse utilizzate dai suoi componenti pack(); // ridimensiona la finestra in modo da contenere tutti gli oggetti (se necessario li ridisegna) toFront(); // da il focus alla finestra toBack(); setAlwaysOnTop(boolean alwaysOnTop); setTitle(String title); // imposta il titolo della finestra setLocationRelativeTo(Component c); // centra la finestra rispetto al componente specificato setMenuBar(MenuBar mb); //(vedi sotto) setIconImage(Image image); setResizable(boolean resizable); // default: true setExtendedState(int state); NOTA: state = bitmask: ICONIFIED, MAXIMIZED_HORIZ, MAXIMIZED_VERT, MAXIMIZED_BOTH == Applet == see also: http://java.sun.com/docs/books/tutorial/deployment/applet/index.html Contrazione di "application -let" = applicazione ridotta. Sono delle applicazioni che girano nel "contesto" ridotto e controllato di un altra applicazione (ad es. il browser). Nelle applet il "main thread" è rimpiazzato dal metodo senza argomenti public void init() che viene invocato al caricamento dell'applet. Solitamente viene ridefinito anche il metodo public void paint(Graphics g) per effettuare il rendering di immagini. Oltre a questi sono talvolta presenti altri metodi speciali: public void start() // viene invocato quando l'utente sta visualizzando la pagina contenente l'applet public void stop() // viene invocato quando la pagina contenente l'applet NON è più visibile sul terminale dell'utente public void destroy() // complementare a init(), viene invocato alla chiusura della pagina, se necessario rilascia altre risorse associate all'applet ~TODO: elencare differenze... ~Per il resto si comportano in maniera analoga alle applicazioni GUI viste finora. Possono essere incluse in una pagina HTML con il seguente codice: Your browser does not support the applet tag. E' anche possibile passare degli argomenti all'applet mediante ... Questi argomenti possono essere letti in qualsiasi punto del codice con: public String getParameter(String parameterName); Infine, la java.applet.Applet fornisce anche questi metodi: Image getImage(URL url); // carica un immagine AudioClip getAudioClip(URL url); // carica un file audio play(URL url); // riproduce direttamente un file audio (impossibile stoppare?) NOTA: Currently, the Java API supports only one sound format: 8 bit, µ-law, 8000 Hz, one-channel, Sun ".au" files. == Menu == Derivano tutti da java.awt.MenuComponent. A differenza dei componenti la loro posizione non è regolata dal layout manager. Possono essere aggiunti SOLO ai Frame. Per creare una barra dei menu: - si crea 1 oggetto MenuBar - si creano tanti Menu(String label) (es. "File", "Edit", etc.) - si creano tanti MenuItem(String label) = voci di menu (es. "Nuovo", "Apri..", "Salva", etc.) - si aggiungono i MenuItem ai Menu - si aggiungono i Menu alla MenuBar - si aggiunge la MenuBar al Frame con setMenuBar(MenuBar mb); Metodi di MenuItem: addActionListener(ActionListener l); // setta l'evento da scatenare alla selezione della voce di menu (vedi sotto - Gestione eventi) setEnabled(boolean b); // attiva-disattiva la voce di menu setShortcut(MenuShortcut s); Metodi di Menu: add(MenuItem mi); addSeparator(); // aggiunge un vero e proprio separatore al menu, ovvero una linea add(String label); // aggiunge un'etichetta non clickabile tipo separatore Metodi di MenuBar add(Menu m); setHelpMenu(Menu m); == Popup menu == Sono dei menu visualizzabili in qualunque punto dello schermo (di solito associati al tasto destro del mouse). La classe java.awt.PopupMenu estende Menu ed in puù fornisce: show(Component origin, int x, int y); == Gestione degli eventi == L'interazione dell'utente con la GUI causa il verificarsi di eventi asincroni con l'esecuzione dell'applicazione. La gestione degli eventi consente di "catturare" questi eventi e di notificarli all'applicazione in esecuzione. Di default questa gestione è demandata al SO. Tuttavia l'applicazione può gestire autonomamente alcuni eventi e demandare al SO le gestione dei rimanenti. Gli eventi delle GUI vengono rappresentati in Java come oggetti della classe astratta java.awt.AWTEvent (java.awt.Event è considerata obsoleta). ( Essa ha i seguenti metodi di uso comune: int getID(); // ritorna il TIPO di evento -> possibili usi? [...] ) Le classi derivate concrete identificano il tipo di evento od il componente atto a recepirlo: java.awt.AWTEvent | |---ActionEvent = per i componenti come pulsanti, è stata eseguita l'azione di default associata al compoenente |---AdjustmentEvent |---AncestorEvent |---ComponentEvent | |---ContainerEvent | |---FocusEvent | |---InputEvent | | |---KeyEvent = pressione di un tasto sulla tastiera | | |---MouseEvent | |---PaintEvent | |---WindowEvent |---HierarchyEvent |---InputMethodEvent |---InternalFrameEvent |---InvocationEvent |---ItemEvent |---TextEvent La gestione degli eventi in Java >1.0 si basa sul delegation model. Avviene mediante oggetti denominati Listeners che devono essere registrati presso il componente. I Listener sono implementati come interfacce derivate da java.util.EventListener e appartengono al package java.awt.event. Esiste un listener per ogni tipo di evento visto in precedenza: java.awt.AWTEvent | |---ActionEvent -> ActionListener |---AdjustmentEvent -> AdjustmentListener |---AncestorEvent |---ComponentEvent -> ComponentListener | |---ContainerEvent -> ContainerListener | |---FocusEvent -> FocusListener | |---InputEvent -> InputListener | | |---KeyEvent -> KeyListener | | |---MouseEvent -> MouseListener, MouseMotionListener, MouseWheelListener | |---PaintEvent | |---WindowEvent -> WindowListener, WindowFocusListener, WindowStateListener |---HierarchyEvent -> HierarchyListener, HierarchyBoundsListener |---InputMethodEvent -> InputMethodListener |---InternalFrameEvent |---InvocationEvent |---ItemEvent -> ItemListener |---TextEvent -> TextListener I listener possono essere realizzati implementando direttamente le interfacce appena viste oppure estendendo gli adapter associati (metodo più usato). In entrambi i casi è possibile istanziarli da classi anonime. == Esempio applicazione GUI in AWT == import java.awt.*; import java.awt.event.*; public class FinestraPrincipale extends Frame { // dichiarazione componenti della finestra Button ... Label ... //... public FinestraPrincipale() // costruttore della finestra principale { setTitle("Titolo"); // ... // aggiunta dei componenti add( a ); add( b ); add( c ); // ... // registrazione dei listeners addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { //hide(); // nasconde temporaneamente la finestra //dispose(); // rimuove la finestra System.exit(0); // esce dal programma quando viene chiusa la finestra principale } // ... } ); pack(); // ridimensiona la finestra in modo da contenere tutti gli oggetti setVisible( true ); } public static void main(String[] arg) { new FinestraPrincipale(); // crea un'istanza della finestra principale } TRICK: è cmq possibile simulare la gestione degli eventi centralizzata "tipo Java 1.0" facendo implementare i listeners direttamente alla classe che estende il container top-level: import java.awt.*; import java.awt.event.*; class ActionDemo extends Frame implements ActionListener, ... { public ActionDemo() { // ... // tutti i componenti aggiungono "this" come listener a.addActionListener( this ); b.addActionListener( this ); c.addActionListener( this ); // ... } public void actionPerformed( ActionEvent e ) { // identificazione del componente che ha generato l'evento if( e.getActionCommand() == "..." ) // ... } // ... public static void main(String[] args) { new ActionDemo().setVisible(true); } } == Esempio Applet in AWT == import java.awt.*; import java.applet.*; public class ClassName extends Applet { public void init() {