Java: il linguaggio Docente: Gabriele Lombardi [email protected] © 2010 - CEFRIEL The present original document was produced by CEFRIEL and the Teacher for the benefit and internal use of this course, and nobody else may claim any right or paternity on it. No right to use the document for any purpose other than the Intended purpose and no right to distribute, disclose, release, furnish or disseminate it or a part of it in any way or form to anyone without the prior express written consent of CEFRIEL and the Teacher. © copyright Cefriel and the Teacher-Milan-Italy-23/06/2008. All rights reserved in accordance with rule of law and international agreements. © 2010 - CEFRIEL Sommario SLIDE CONTENUTO Sintassi base Poco più del procedurale Interfacce e classi Strumenti per l’OOP Ereditarietà e implementazione Strumenti di riutilizzo del codice.. e di astrazione Enums e annotations Da Tiger in avanti (jdk >= 1.5) Gestione delle eccezioni In sostituzione ai controlli di codici di errore Generics Per ADT type-safe.. con type-erasure Altro Zucchero sintattico © 2010 - CEFRIEL Intro: codice compilato VS interpretato Codice compilato: – veloce.. ma non portabile. Codice interpretato: – portabile.. ma lento. virtual machine (JVM): – – – – codice compilato per una macchina inventata (bytecode); interpretato da una virtual machine; portabile (dove una JVM sia presente); veloce (meno del codice compilato nativo); JIT (massimo della vita): – – – – – come precedente, ma … … traduzione a run-time in codice nativo; sparisce una vera e propria interpretazione; viene eseguito codice nativo; portabile (ove esista la JVM).. Ma veloce quanto (o più) del codice compilato per la macchina reale (perché più veloce?). © 2010 - CEFRIEL Sintassi base declarations: – stessa sintassi del C: <tipo> <nome> [= valore]; • int pippo, pluto = 1; float paperino = 1E-6f; – non esistono i tipi senza segno (tipo “unsigned long”). statements & constructs: – stessa sintassi del C; – assegnamenti: <variabile> = <espressione>; • senza puntatori gli L-values di un assegnamento possono essere solo variabili, attributi o indicizzazione di array. – costrutti di selezione: • if(){} if(){}else{} – costrutti di iterazione: • while(){} – aggiunti: switch(){case 1: default:}. do{}while(); for(;;){} • for (int elemento: arrayDiInteri) { /* Uso “elemento”. */ } operators: gli stessi del C. tipi builtin: boolean, char, short, int, long, float, double modificatori: static, transient boxing/unboxing: – per ogni tipo builtin esiste una classe di boxing, istanziata in automatico all’occorrenza e “spacchettata” automaticamente se serve; – esempio: int java.lang.Integer © 2010 - CEFRIEL Sintassi base dotted notation: reference-types VS value-types: – dato un oggetto, gli attributi (campi) ed i metodi vengono identificati come <oggetto>.<membro>; – esempio: System.out.getClass().getName() – ogni oggetto è un tipo riferimento, quindi: • assegnamento non clona ma copia il riferimento; • side effects possibili se l’oggetto viene passato; – ogni tipo builtin è un valore, quindi: • assegnamento per copia e side effects assenti. – Simulare i value-types: oggetti immutabili si evitano i side effects. semantica del passaggio dei parametri: operatori particolari: – sempre per copia, ciò che viene copiato però può essere un riferimento, consentendo quindi la modifica dell’oggetto da parte del metodo. – new: creazione di nuove istanze tramite costruttore: • new String(123) la classe String contiene un metodo particolare capace di creare una stringa da un intero: – public String(int num) {…} – instanceof: RTTI, Run-Time Type Identification: • if (myVar instanceof MyClass) controllo il tipo di “myVar”. © 2010 - CEFRIEL Sintassi base dichiarazioni e gestione dello stack: – nella JVM ogni elemento occupa una locazione dello stack (con il proprio valore per i value-types, con il proprio riferimento (puntatore) per i reference-types. – un record di attivazione viene creato per ogni chiamata a metodo, eventualmente crescendo e decrescendo durante l’esecuzione (dichiarazioni in mezzo al codice). – blocchi possono essere dichiarati per gestire la creazione e distruzione parti di record di attivazione in mezzo a un metodo: { // inizio del blocco // dichiarazioni nuove var. nel record di att. } // fine del blocco gestione della memoria: – richiesta solo l’allocazione tramite costruttori e operatore “new”; – garbage collector di tipo mark-and-sweep per la deallocazione. strutture dati: – array di tipi builtin e di oggetti; • un array è un oggetto array di array. – classi, enum, annotazioni. © 2010 - CEFRIEL Classi descrivono delle tipologie di “entità”: – informazioni (attributi) che definiscono le caratteristiche di ogni istanza: • esempio: class Colore { float red,green,blue; } – operazioni (metodi) per manipolarle accedendo agli attributi: • esempio, mescolare un colore in un altro: • class Colore {float red,green,blue; … void mescola(Colore c) { red = red*0.5+c.red*0.5; … } …} • Colore c = new Colore(0,0,1); c.mescola(new Colore(1,0,0)); … nonché il loro concreto funzionamento: possono essere utilizzate per realizzare: che ruolo fargli avere sta a noi (vedasi nel Domain Driven Design). – contengono la desc. di “come” un’entità si comporta (metodi astratti?); – “cosa” un’entità è in grado di fare è meglio definirlo in un’interfaccia. – – – – value objects: mantenendo l’immodificabilità degli attributi (immutables); entities: evoluzione dello stato interno e mantenendo l’identità; servizi: metodi che svolgono operazioni su richiesta, non su di sé; factory: classi con lo scopo di creare istanze di altre classi (fabbriche). © 2010 - CEFRIEL Interfacce descrivono solamente il contratto di “messaggistica”: – gerarchia di tipi.. e non di classi/oggetti: • utilizzata per il controllo di tipo a run-time; – chi le implementa dichiara “cosa sa fare”: • dichiarazione dei metodi implementati da una determinata tipologia di classi; • una classe può implementare più interfacce (simile all’ereditarietà multipla). separano behavior da implementation: – rendono inconsapevole il client: • utilizza un oggetto di natura ignota conoscendone solamente le capacità o ruolo da esso coperto; – permettono di definire punti di astrazione: • non dipendendo dalla classe di implementazione, si possono definire entità operanti su tipologie astratte di entità distinte per le loro capacità. © 2010 - CEFRIEL Ereditarietà e implementazione Ereditarietà (tra classi): – – – – – – – solamente singola in Java (no ereditarietà multipla); è uno strumento di riutilizzo diretto del codice; specializzazione del funzionamento di una classe; astrazione tramite classi astratte e polimorfismo; definisce la relazione “è un”; principio di sostituibilità di Liskov; occhio all’esempio classico “rettangolo ⊲⊦ quadrato”. Implementazione (di interfacce): – anche multipla e accoppiata all’ereditarietà; – è uno strumento di astrazione e validazione a compile-time (vedere prossimo punto); – definisce la relazione “rispetta la specifica di un”. © 2010 - CEFRIEL Esempio fino a qui Quali i vantaggi di quest’architettura? Gli svantaggi? Astrazioni mancanti? Realizziamolo e “giochiamoci”! Value object Servizio Inconsapevole «type» Punto -x : double -y : double +getX() : double +getY() : double +add(in p : Punto) : Punto +mean(in p : Punto) : Punto +dist(in p : Punto) : double «interfaccia» Terminale +disegna(in p : Punto) +fatto() Client Astrazione Main «classe implementazione» TerminaleTestuale Immutable «instance» «classe implementazione» TerminaleGraficoAscii Classi concrete Mediator «instance» © 2010 - CEFRIEL Enums Cosa sono? – entità esistenti in numero finito e noto a priori; – spesso definiscono opzioni di funzionamento; – possono rappresentare entità più complesse: • unità di misura, costanti, comandi predefiniti, descrizioni di pacchetti, … Come funzionano: – sono dichiarati similarmente a classi; – prevedono una lista di chiamate ai propri costruttori; – vengono istanziati (teoricamente) prima dell’esecuzione dell’applicazione (sperimentare); – NON possono essere istanziati esplicitamente; – permettono una sintassi ricca: • specificazione di attributi e metodi; • specializzazione per singolo valore. © 2010 - CEFRIEL Annotations Cosa sono? – annotazioni allegate ad entità semantiche presenti nel codice (tipi, attributi, metodi, parametri,…); – possono trasportare informazione anche strutturata in maniera complessa (un albero per tipo di annotazione); – devono essere completamente definiti a compile-time; – possono essere “osservati” a run-time (reflection); Come funzionano? – “non funzionano”, semplicemente ci sono, e offrono accesso ai dati che contengono; – chi li usa si aspetta che vengano “letti” da qualche altra parte del SW, o da un framework utilizzato; – chi li legge offre funzionalità dichiarative a chi li utilizza; – consentono principalmente la metaprogrammazione; – si analizzi JUnit come esempio interessante. © 2010 - CEFRIEL Gestione delle eccezioni Prima delle eccezioni: – [server] rilevato l’errore restituito codice descrittivo; – [client] controllato il codice presi dei provvedimenti; – un esempio: • server: • client: Con le eccezioni: se BAD allora restituisci ERR; chiama server; controlla codice di errore; se ERR provvedi; chiama server; controlla codice di errore; se ERR provvedi; … – [server] rilevato l’errore generata eccezione da gestire; – [client] svolge il proprio compito; “cattura” le eccezioni che gli interessano e provvede; “lascia passare” le altre così che qualcuno provveda. Risultato: codice più pulito e comprensibile. © 2010 - CEFRIEL Generics Problema di partenza: – strutture dati internamente non tipizzate: • utilizzo di “Object” come “void*” in C: – nessun check di tipo effettuabile a compile-time; • ADT semplici non (banalmente) definibili: – Non basabili correttamente sulle API standard; – impossibile propagare informazione di tipo tra strutture dati, se non utilizzando la reflection; • più lenta ed error-prone; – valutata a run-time; – richiede molti controlli per offrire robustezza; • non validabile a run-time: – con controlli del tipo “Mi dicono che classe vogliono, ma deve essere per forza figlia di X perché il tutto funzioni.”; – esplosione di errori a run-time evitabili tramite check automatico. Soluzione: offrire uno strumento di manipolazione di tipi: – funzionante (solo) a compile time (non modifica le performance); – comprendente generalizzazione e vincoli verificabili dal compilatore durante la generazione del codice. © 2010 - CEFRIEL Altro Package: – migliore granularità sulla visibilità; – organizzazione gerarchica degli aspetti/funzionalità; – ORDINE.. prima di tutto! Classi anonime (innestate, …): – permettono una immediata applicazione dell’abstract method pattern (GoF, vedasi gestione eventi); – riducono il tempo di sviluppo; – MIGLIORANO la leggibilità del codice (se ben usate). Collections e iteratori: – concetti generali, entrati nella semantica.. e anche nella sintassi del linguaggio (non solo comodità). Varargs: – utili.. ma solo per semplificare la scrittura del codice. © 2010 - CEFRIEL Esempio fino a qui Per ripassare tramite esempi in Code\01_Syntax: – EsempiSintassi\BaseSyntax.java • sintassi di base del linguaggio. – – – – EsempiSintassi\Exceptions.java EsempiSintassi\Enums.java EsempiSintassi\Enums\*.java EsempiSintassi\Generics.java EsempiSintassi\Annotations.java • sequenza consigliata per il ripasso. – EsempioTerminali • esempio di cui si sono mostrati i diagrammi UML. – EsempioGruppi • soluzione (parziale?) dell’esercizio seguente: Costruire un package di manipolazioni di gruppi finiti (in senso algebrico) di value-objects; – – – – occhio a cosa il gruppo deve contenere; modellare prima dal punto di vista astratto; occhio alle operazioni astratte; usare lo “zucchero sintattico” visto in precedenza. © 2010 - CEFRIEL