| Index | Next |
    1. Il linguaggio Java

1.1 Introduzione a Java

"Immagina di essere uno sviluppatore di applicazioni software, il linguaggio di programmazione che usi è il C o il C++ ( o qualunque altro linguaggio che hai imparato).

Usi questo linguaggio da parecchio tempo ma il tuo lavoro non sembra diventare più facile, inoltre negli ultimi anni hai assistito alla crescita di molte architetture hardware incompatibili, ognuna supporta un proprio sistema operativo ed ha una propria interfaccia grafica. Ora tu dovresti affrontare tutto questo per creare le tue applicazioni di lavoro in un ambiente distribuito client-server.

La crescita di Internet, del World Wide Web, e del commercio elettronico hanno introdotto una nuova complessità nel processo di sviluppo del software.

Gli strumenti che usi per lo sviluppo di applicazioni non sembrano esserti di grande aiuto. Stai ancora affrontando le stesse problematiche; la nuova moda della tecnologia object-oriented sembra aver aggiunto nuovi problemi senza risolvere quelli vecchi. Dici a te stesso e ai tuoi amici, "Ci deve essere un modo migliore" !

Ora c’è un modo migliore - questo è Java ™ programming language environment della Sun Microsystems. Immagina, se vuoi, questo ambiente di sviluppo ...

  • Il tuo linguaggio di programmazione è orientato agli oggetti, ed è semplice.
  • Il tuo ciclo di sviluppo è molto veloce perché Java è interpretato. Il ciclo compile-link-load-test-debug è obsoleto, ora devi compilare ed eseguire.
  • Le tue applicazioni sono portabili attraverso più piattaforme. Scrivi le applicazioni una sola volta, e non avrai mai bisogno di "portarle", esse sono eseguibili senza modifiche su diverse piattaforme hardware e su diversi sistemi operativi.
  • Le tue applicazioni sono sicure perché il sistema run-time Java gestisce la memoria per te.
  • Le tue applicazioni grafiche interattive sono ad alte prestazioni perché più thread simultanei possono essere attivi, e le tue applicazioni supportano il multithreading incorporato nell’ambiente Java.
  • Le tue applicazioni sono adattabili ai cambiamenti di ambiente perché puoi scaricare dinamicamente moduli di codice da qualunque parte sulla rete.
  • I tuoi utenti possono fidarsi delle tue applicazioni esse sono sicure, anche se essi scaricano codice da Internet; il sistema run-time Java ha incorporato protezioni contro virus e altre manipolazioni.
Tu non hai bisogno di sognare tutto questo. E’ realtà. Il linguaggio di programmazione Java è portabile, interpretato, ad alte prestazioni, orientato agli oggetti e supporta l’ambiente run-time."
The Java Language Environment - A White Paper
James Gosling, Henry McGilton
Sun Microsystems Computer Company

Java ha avuto origine come parte di un progetto di ricerca volto a sviluppare programmi per dispositivi elettronici, lo scopo era quello di un software di sviluppo piccolo, riusabile, portabile e distribuito. 

All’inizio del progetto il linguaggio scelto fu il C++, ma poi una serie crescente di difficoltà portarono alla necessità di creare un linguaggio completamente nuovo. 

Il risultato è stato un linguaggio che si è dimostrato ideale per lo sviluppo di applicazioni sicure e distribuite. Adatto allo sviluppo di applicazioni in diversi ambienti , dal network al World Wide Web al desktop. 

La crescita massiccia di Internet e del World Wide Web sta portando ad un modo completamente nuovo di distribuire il software, lo schema tradizionale di distribuzione del software, rilascio di una versione, correzioni, aggiornamenti, e cosi via si stanno dimostrando non validi in un ambiente multipiattaforma ed eterogeneo quale è il network. Java con le sue caratteristiche di essere ad architettura neutrale, portabile, adattabile dinamicamente, si adatta a questo nuovo scenario meglio di altri linguaggi. 

Un linguaggio rivoluzionario
Java è stato progettato partendo da zero, nessuna compatibilità con il passato doveva essere rispettata, questo ha permesso ai progettisti di fare un linguaggio che rispondesse alle più moderne esigenze di programmazione. 

Esso ha un ambiente di sviluppo orientato agli oggetti pulito ed efficiente, gli è stato dato un aspetto simile al C++ per permettere ai programmatori che già utilizzano questo linguaggio di passare agevolmente a Java. 

Progettato per creare software altamente affidabile, fornisce ampi controlli in fase di compilazione, seguito da ulteriori controlli in fase di esecuzione. Il linguaggio rappresenta una guida ai programmatori verso l’abitudine a produrre programmi affidabili: gestione automatica della memoria, nessun puntatore da gestire, nessun codice "oscuro". 

Java è nato per operare in ambiente distribuito, ciò significa che l’argomento sicurezza e di grande importanza. E’ stata dedicata particolare attenzione alla sicurezza sia a livello di linguaggio sia a livello di sistema run-time. Java permette di costruire applicazioni che possono difficilmente essere invase da altre applicazioni. 

Java supporta applicazioni che saranno adoperate in ambienti eterogenei di rete. In tali ambienti le applicazioni devono essere capaci di eseguirsi su architetture hardware diverse e sistemi operativi diversi. Per risolvere questa diversità di ambienti operativi, il compilatore Java genera i bytecode, un formato di codice intermedio tra il codice ad alto livello e quello macchina, progettati per essere efficientemente trasportati su piattaforme hardware e software diverse. 

L’architettura neutrale è solo una parte di un sistema veramente portabile. Java fa fare alla portabilità un passo avanti, precisando e specificando la grandezza dei tipi di dati e il comportamento degli operatori aritmetici, i programmi sono gli stessi su ogni piattaforma, non ci sono incompatibilità di tipi di dati attraverso diverse architetture hardware e software. L’ambiente architettura neutrale e linguaggio portabile e conosciuto come Java Virtual Machine (JVM), esse sono le specifiche di una macchina astratta per la quale il compilatore Java genera il codice. Una specifica implementazione della JVM per piattaforme hardware e software specifiche provvede alla realizzazione concreta di questa macchina virtuale. L’implementazione della Java Virtual Machine per una nuova architettura sarà relativamente semplice. 

Le prestazioni sono sempre da considerare: Java ottiene ottime prestazioni adottando uno schema attraverso il quale l’interprete può eseguire i bytecode alla massima velocità senza necessariamente controllare l’ambiente run-time. Una applicazione automatica, garbage collector, eseguita in background assicura con elevata probabilità che la memoria richiesta sia disponibile. Applicazioni particolari o applicazioni che richiedono grossa potenza di calcolo possono essere scritte in codice nativo della macchina ed interfacciate con l’ambiente Java. 

L’interprete Java può eseguire i bytecode Java su ogni macchina alla quale l’interprete e il sistema run-time è stato portato. In un ambiente interpretato come il sistema Java la fase di link di un programma è semplice e leggera, il ciclo di sviluppo del software diventa molto più rapido. 

Le moderne applicazioni di rete come i browser del World Wide Web, hanno bisogno di fare più cose contemporaneamente, la capacità multithreading di Java dà i mezzi per costruire applicazioni con più attività concorrenti: Java supporta il multithreading a livello di linguaggio con aggiunta di sofisticate primitive di sincronizzazione. La libreria del linguaggio contiene la classe Thread e il sistema run-time fornisce i monitor e condizioni di lock. A livello di libreria, in più , il sistema Java è stato scritto per essere sicuro nella gestione del multithreding: delle funzionalità provvedono che le librerie siano disponibili senza conflitti tra thread concorrenti in esecuzione. 

La compilazione avviene con controlli severi, il linguaggio Java è dinamico nella fase di link; le classi sono collegate solo quando occorre, nuovi moduli di codice possono essere collegati a richiesta da una varietà di sorgenti, anche da sorgenti disponibili attraverso la rete ciò permette l’aggiornamento trasparente di applicazioni. 

Il sistema base di Java
La Sun Microsystems, società che ha sviluppato il linguaggio, ha incluso nel Java Developer’s Kit (JDK) una libreria di classi e metodi utili per creare applicazioni multi-piattaforma, questa libreria è organizzata in package di cui i principali sono: 

java.lang

Una collezione di tipi base che sono sempre importate in ogni unità compilata. Qui si trova la dichiarazione di Object (la radice della gerarchia delle classi) e Class, oltre ai thread, eccezioni, include i tipi di dati primitivi e una varietà di classi fondamentali. java.io Package che fornisce le classi per gestire l’input e l’output streams per leggere e scrivere dati da file, stringhe, e qualunque altra sorgente (es. network). java.util Package che contiene un insieme di utility, include una generica struttura dati, manipolazione delle stringhe, proprietà di sistema, generatore di numeri casuali ed altro.     java.net
     Package che fornisce le classi per il supporto in rete, sockets TCP, sockets UDP, indirizzo IP, URL, convertitore binario - testo.
java.awt Package che fornisce un integrato set di classi per il controllo di interfacce grafiche utente come windows, dialog boxes, buttons, checkboxes, menus etc... . (AWT = Abstract Window Toolkit). java.awt.image Package che fornisce classi per gestire immagini, include un modello di colori, filtri del colore e manipolazione dei pixels. java.awt.peer Package che connette i componenti AWT alle loro realizzazioni relative alle specifiche piattaforme. java.applet Package che permette la creazione applets attraverso la classe Applet. Sun Packages

sun.tools.debug

Package che contiene interfacce e classi per il debug. I package costituiscono lo strumento impiegato da Java per la progettazione e l’organizzazione delle classi, essi possono essere strutturati in una gerarchia, per certi versi analoga alla gerarchia ereditaria tra le classi, dove ogni livello rappresenta un gruppo più specifico e ristretto di classi. 

La stessa libreria Java è organizzata in questo modo, il livello superiore e denominato java quello successivo comprende nomi come io, net, util, e awt. L’ultimo di questi ha altri sottolivelli che includono il package image e il package peer

Per convenzione il primo livello della gerarchia specifica il nome dell’azienda che ha sviluppato il package. Così i nomi delle classi della Sun Microsystems, che non fanno parte dell’ambiente standard di Java, iniziano tutti con il prefisso sun

Il package standard, java, costituisce un’eccezione a questa regola perché rappresenta la componente centrale del sistema. 

La Sun Microsystems ha specificato una procedura più formale per la denominazione dei package, destinata ad essere utilizzata in futuro. Il nome del package di livello più alto si riserva ai nomi dei domini, in maiuscolo, di alto livello di Internet (EDU, COM, GOV, IT e così via); questi nomi riservati, formano la prima parte di tutti i nuovi nomi di package; così secondo questa procedura il package sun dovrebbe diventare COM.sun. Nel caso di un’università, si avrebbe un nome simile al seguente: 

IT.unisa.diiie.nomePackage

Poiché viene già garantita l’univocità dei nomi di dominio, questo sistema risolve il problema dei conflitti tra i nomi e in più gli applet e i package creati dai programmatori Java, potenzialmente milioni, sarebbero automaticamente inseriti in una gerarchia. 

Poiché ogni classe Java si trova solitamente in un file separato, il raggruppamento di classi fornito da una gerarchia di package è analogo al raggruppamento di file in una gerarchia di directory. Il compilatore Java rafforza questa analogia, poiché richiede di creare una gerarchia di directory al di sotto della propria directory di classi, che corrisponda esattamente alla gerarchia dei package, e di inserire ogni classe in una directory con lo stesso nome del package in cui è definita, in questo modo l’interprete Java in fase di esecuzione di un programma cerca le classi nella directory che corrisponde alla gerarchia dei package, che sarà univoca. 

Nel seguito verranno illustrate le caratteristiche di Java e la ragione della loro importanza.

    1.2 Un linguaggio semplice
      Java rappresenta un nuovo punto di vista nell’evoluzione dei linguaggi di programmazione - rappresenta la creazione di un linguaggio sufficientemente comprensivo, tale da indirizzare una ampia varietà di sviluppatori di applicazioni software verso di esso. 

      Mentre mantiene un aspetto simile al C/C++, Java acquista in semplicità dalla rimozione sistematica di alcuni particolari "dubbiosi" del C/C++; il team che ha progettato Java esaminando gli aspetti "moderni" del linguaggio C/C++ ha determinato le caratteristiche che dovevano essere eliminate in un contesto di linguaggio di programmazione orientato agli oggetti. 

      Java segue il C/C++ fino ad un certo punto, ciò porta i benefici di essere familiare a molti programmatori, ma si diversifica per altre cose, al di la dei tipi di dati primitivi discussi qui di seguito, tutto è un oggetto. Anche i tipi di dati primitivi possono essere incapsulati in oggetti. Ci sono solo tre gruppi di tipi primitivi di dati: numerico, booleano e array

      Tipi di Dati Numerici

        I tipi di dati numerico intero sono byte a 8-bit, short a 16-bit, int a 32-bit e long a 64-bit. In Java non c’è lo specificatore unsigned per i tipi di dato intero. 

        I tipi di dati numerico reale sono float a 32-bit e double a 64-bit. I valori letterali in virgola mobile sono considerati double per default; bisogna eseguire esplicitamente il "cast" a float se si desidera assegnarli a variabili float

        Il tipo di dato carattere di Java si sposta dai linguaggio tradizionali. Il tipo char di Java è definito come carattere Unicode a 16-bit. Il set di caratteri Unicode sono valori a 16-bit senza segno definiti nell’intervallo 0 - 65535, adottando lo standard Unicode per questo tipo di dati le applicazioni Java sono disponibili per supportare caratteri internazionali.

      Tipi di dati Booleani
        Java ha il tipo di dato boolean come tipo primitivo, una variabile Java boolean può assumere i valori true o false, il tipo boolean e distinto dagli altri tipi primitivi e non può essere convertito in altro tipo numerico.
      Gli Array
        Gli array sono implementati in modo particolare in Java, nonostante essi possono essere allocati come degli oggetti e sono dotati di variabili istanza non c’è una classe Array. Si può dire che un array in Java è un oggetto con una rappresentazione run-time. La classe Array è costruita automaticamente durante l’esecuzione dei programmi. Inoltre, per ogni tipo primitivo e per ogni oggetto esiste una sottoclasse implicita Array che ne rappresenta gli array. Un oggetto array si comporta come se appartenesse a una classe, anche se questa non esiste propriamente.
      Le stringhe
      Nel linguaggio Java le stringhe sono degli oggetti. Ci sono due tipi di oggetti stringa: la classe String per oggetti stringa di sola lettura (immutabili), la classe StringBuffer per oggetti stringa che possono essere manipolati. Anche se in Java le stringhe sono degli oggetti veri e propri, il linguaggio prevede una sintassi conveniente a trattare le stringhe come se fossero dei tipi primitivi, così quando un letterale stringa compare in un programma, Java crea automaticamente un’istanza della classe String con il valore indicato, inoltre sono comprese nel linguaggio alcune facilitazioni sintattiche per aiutare i programmatori a fare comuni operazioni sulle stringhe, concatenazioni di oggetti String, conversione da altri tipi possono essere fatte esplicitamente, ma Java effettua gran parte di queste operazioni automaticamente per permettere una maggiore chiarezza nella scrittura dei programmi. 

      Gestione della memoria e Garbage Collection
      Il linguaggio Java rimuove completamente il compito ( e la responsabilità) di gestione della memoria da parte del programmatore. Un’automatica garbage collection fa parte integrante del sistema run-time di Java, mentre c’è un operatore new per allocare un oggetto in memoria, non c’è nessun operatore esplicito per "liberare" la memoria allocata. Una volta allocato un oggetto, il sistema run-time mantiene il controllo dello stato dell’oggetto recuperando la memoria quando non è più in uso e rendendola libera per usi futuri. 

      Il modello della gestione della memoria di Java si basa su oggetti e riferimenti ad essi, Java non ha puntatori, tutti i riferimenti alla memoria allocata, in pratica tutti i riferimenti a oggetti, sono "memorizzati" dal sistema Java, quando un oggetto non ha più riferimenti questa è candidato per la garbage collection

      Il garbage collector di Java ottiene alte prestazioni sfruttando il comportamento degli utenti che interagendo con le applicazioni software hanno molte pause naturali, il sistema run-time di Java sfrutta queste pause per eseguire il garbage collector in un thread a bassa priorità, il garbage collector riunisce e compatta la memoria non più in uso, aumentando la probabilità che adeguate risorse di memoria siano disponibile quando occorrono. 

      Infine Java è semplice grazie anche alle sue dimensioni contenute; l’interprete Java occupa circa 40 kByte di memoria RAM, escludendo il supporto per il multithreading e le librerie standard, che richiedono altri 175 kByte. La semplicità di Java consente di risparmiare tempo nella codifica e nella ricerca degli errori, dedicandosi maggiormente all’analisi dei problemi e delle soluzioni e alla soddisfazione del cliente. 

      Tale semplicità, inoltre, consente ai programmi Java di funzionare su computer che offrono modelli di memoria limitata, la quantità di memoria dell’interprete Java con le sue librerie standard risulta insignificante se confrontata con altri ambienti e linguaggi di programmazione.
       

    1.3 Un linguaggio orientato agli oggetti
La tecnica della programmazione orientata agli oggetti permette di ottenere dei risultati impossibili con la tecnica di programmazione procedurale. 

Un linguaggio di programmazione per essere considerato "object oriented" dovrebbe soddisfare almeno le seguenti caratteristiche :

  • Incapsulazione: implementa informazioni modulari e nascoste (astrazione).
  • Polimorfismo: lo stesso messaggio spedito a oggetti diversi risulta avere comportamenti diversi che dipendono dalla natura dell’oggetto che riceve il messaggio.
  • Ereditarietà: si possono definire nuove classi e comportamenti basati su classi esistenti; questo per ottenere codice riusabile e codice organizzato.
  • Binding dinamico: gli oggetti potrebbero venire da qualunque parte, anche dal network. Si può avere il bisogno di spedire messaggi a oggetti senza dover conoscere il loro specifico tipo al momento in cui si scrive il codice. Il binding dinamico fornisce la massima flessibilità mentre il programma è in esecuzione.
Java soddisfa questi requisiti perfettamente, inoltre aggiunge un considerevole supporto run-time per rendere più facile il compito di sviluppo del software.
      Gli oggetti
      Un oggetto è un modello di programmazione software, esso ha uno stato definito nelle variabili istanze e un comportamento definito dai metodi. I metodi manipolano le variabili istanze per creare nuovi stati o anche nuovi oggetti. 

      La Figura 1.1 è la comune rappresentazione di un oggetto, essa ne illustra la struttura software concettuale. Un oggetto è come una cella con un involucro esterno che si interfaccia con il mondo, è un nucleo protetto dall’involucro.

        fig1-1

         Figura 1.1 Un oggetto in Java
         
         
         

      Le variabili istanza sono impacchettate nell’oggetto e circondate dai metodi. Tranne alcune eccezioni i metodi rappresentano il solo mezzo con cui è possibile modificare le variabili istanza. 

      Le classi
      La classe è un costrutto software che definisce le variabili istanza e i metodi di un oggetto. Essa è una rappresentazione di un modello per diversi oggetti con caratteristiche simili. Un oggetto concreto lo si ottiene istanziando una classe precedentemente definita. 

      Da una classe è possibile creare una sottoclasse, che eredita tutte le caratteristiche della classe madre (o superclasse) e in genere ne fornisce altre. 

      Per creare una gerarchia si utilizza il meccanismo di eredità singola, il che significa che una classe ha una sola superclasse

      Uno dei vantaggi principali del linguaggio Java è che ciascuno dei suoi oggetti è auto-contenuto, pertanto ogni modulo è intrinsecamente riutilizzabile. Inoltre, ciascun modulo è estensibile e ciò significa che i programmatori possono aggiungere nuove procedure e nuove sottoclassi a qualunque oggetto.

         
Controllo dell’accesso a variabili e metodi
Quando si dichiara una nuova classe in Java, si possono specificare i livelli di accesso permessi alle variabili istanza e ai metodi. Esistono quattro livelli di accesso. Tre di questi livelli devono essere specificati esplicitamente se si vogliono usare, essi sono public, protected e private

Il quarto livello non ha un nome, esso è chiamato "friendly" ed è il livello di accesso predefinito quando non si specifica nessun livello esplicitamente. 

Il livello di accesso "friendly" indica che le variabili istanza e i metodi sono accessibili a tutti e soli agli oggetti dello stesso package.

  • Public: questa relazione si basa sulla distinzione tra l’interno e l’esterno di una classe, ogni metodo o variabile e visibile alla classe in cui e definita, per renderlo visibile a tutte le classi esterne occorre dichiarare il metodo o la classe con il modificatore public.
  • Protected: La seconda relazione è quella tra una classe e le sue sottoclassi presenti o future, questo livello fornisce l’accesso completo a variabili e metodi solo alle sottoclassi di una determinata classe. In realtà questo livello di protezione viene definito dal modificatore private protected, mentre protected da solo consente l’accesso sia alle sottoclassi sia alle classi dello stesso package, offrendo così un ulteriore livello di protezione.
  • Private: L’ultima relazione è definita dal modificatore private, che consente l’accesso a variabili e a metodi soltanto alla classe in cui sono definiti.
Variabili e metodi di classe
In alcuni casi si desidera creare una variabile condivisa che sia visibile e utilizzabile da tutte le proprie istanze, si tratta di una variabile di classe il cui modificatore deve essere la parola static, di questa variabile ne esiste un’unica copia e tutte le istanze della classe la condividono. Utilizzando static è possibile dichiarare anche metodi di classe. 

Il modificatore final
Concludiamo con un accenno al modificatore final, esso è molto versatile, infatti presenta le seguenti caratteristiche.

  • Quando è applicato ad una classe indica che non è possibile creare delle sottoclassi di questa.
  • Quando è applicato a una variabile, indica che si tratta si una costante.
  • Quando è applicato a un metodo, indica che questo non può essere ridefinito da sottoclassi.
    1.4 Architettura neutrale
      Con la fenomenale crescita del network, gli sviluppatori di oggi devono "pensare distribuito". Le applicazioni, o parti di esse, devono poter migrare facilmente su un’ampia varietà di architetture hardware e di sistemi operativi, e devono operare con diverse interfacce grafiche user. 

      Le applicazioni devono poter essere eseguite da ovunque sul network senza precedente conoscenza della piattaforma hardware e software, se i programmatori sviluppano software per una specifica piattaforma, la distribuzione di codice binario attraverso il network che è eterogeneo diventa impossibile. 

      La soluzione che il sistema Java adotta per risolvere il problema della distribuzione del codice è un formato di codice binario indipendente dall’architettura hardware e software, il formato di questo codice binario è ad architettura neutrale. Se il sistema run-time di Java viene reso disponibile per determinate (o tutte) piattaforme hardware e software, le applicazioni scritte in Java possono essere eseguite senza il bisogno di eseguire il "porting" su quelle piattaforme. 

      Byte Code
      Il compilatore Java non genera codice macchina nel senso di istruzioni hardware native, piuttosto genera i bytecode: un codice macchina indipendente, per un’ipotetica macchina che è implementata dal sistema run-time di Java (interprete). I bytecode Java sono stati progettati per essere facilmente interpretati su ogni macchina e per poter essere tradotti dinamicamente in codice macchina nativo. 

      L’approccio ad architettura neutrale si dimostra utile non solo nelle applicazioni di rete, ma anche nella distribuzione del software su singoli sistemi soprattutto oggigiorno dove il mercato dei computer si diversifica per architetture hardware e software. 

      Usando Java, con l’Abstrach Windows Toolkit, la stessa versione di applicazione può eseguirsi su tutte le piattaforme per le quali e stato reso disponibile l’interprete Java ed avrà l’aspetto interfaccia grafica tipico di quella piattaforma.

         
      Portabile
      Il fatto che Java sia neutro rispetto all’architettura contribuisce notevolmente alla portabilità del linguaggio; ma c’è di più, esso specifica la dimensione di tutti i tipi di dati primitivi, per cui il comportamento di questi sarà lo stesso su tutte le piattaforme: 

      byte 8 bit, complemento a due 

      short 16 bit, complemento a due 

      int 32 bi, complemento a due 

      long 64 bit, complemento a due 

      float 32 bit, standard IEEE 754 per i numeri in virgola mobile 

      double 64 bit, standard IEEE 754 per i numeri in virgola mobile 

      char 16 bit, carattere Unicode 

      Queste scelte sono state fatte tenendo presente le architetture dei microprocessori moderni, che essenzialmente condividono queste caratteristiche. 

      Lo stesso ambiente Java è facilmente portabile su nuove architetture e sistemi operativi, in compilatore Java è scritto anch’esso in linguaggio Java, mentre il sistema run-time di Java è scritto in ANSI C con un chiaro riguardo alla portabilità nel senso che non ci sono implementazioni dipendenti.

         
      Robusto
      Java è stato progettato per sviluppare software che deve essere robusto, affidabile e sicuro

      Il compilatore Java effettua particolari e severi controlli in fase di compilazione, in modo che ogni errore possa essere individuato prima che il programma sia eseguito, in Java tutte le dichiarazioni di tipo devono essere esplicite e non supporta lo stile delle dichiarazioni implicite. Il linker di Java ripete tutte le operazioni di controllo di tipo per evitare incongruenze di interfaccia o di metodi.
       

    1.5 Dinamico
      La nozione di una fase separata di "link" dopo la compilazione è assente nell’ambiente Java. Il "linking", che è il processo di caricamento di una nuova classe, è un processo incrementale e leggero. La natura interpretata e portabile del linguaggio Java produce un sistema altamente dinamico ed estensibile dinamicamente. Le classi sono collegate solo quando richiesto e possono provenire dal file system locale così come dalla rete, inoltre il codice viene verificato prima di essere passato all’interprete per l’esecuzione. E’ possibile aggiungere nuovi metodi e variabili di istanza ad una classe, senza dover ricompilare l’intera applicazione. 

      Il compilatore Java non compila utilizzando riferimenti a valori numerici, esso passa riferimenti simbolici al verificatore di bytecode e all’interprete. L’interprete Java esegue la risoluzione dei nomi quando la classe viene effettivamente collegata, quando il nome è risolto, il riferimento viene scritto come un offset numerico permettendo all’interprete Java di procedere velocemente. La posizione di un oggetto in memoria non è stabilita dal compilatore ma è definita dall’interprete in fase di esecuzione. Nuove variabili istanze e nuovi metodi possono essere aggiunti a una classe senza il bisogno di ricompilare l’intera applicazione. 

      L’esecuzione dinamica in pratica, consente di ottenere veri e propri moduli software plug-and-play. 

      Rappresentazione Run-Time
      Una classe Java possiede una rappresentazione di run-time che consente ai programmatori di eseguire la ricerca sul tipo di una classe e di linkare dinamicamente la classe in base al risultato della ricerca. Tali rappresentazioni run-time consentono, inoltre, di controllare i cast della struttura di dati durante la compilazione così come durante l’esecuzione, fornendo così un ulteriore controllo sugli errori.
       

    1.6 Multithreading in Java
      Il linguaggio Java fornisce ai programmatori il supporto per i thread, un potente strumento che permette di migliorare le applicazioni interattive e le applicazioni grafiche. 

      I thread rappresentano un modo veloce per ottenere elaborazioni simultanee in un ambiente a singolo processo, essi sono anche chiamati processi leggeri o contesti di esecuzione. La libreria di classi Java fornisce la classe Thread che supporta una ricca collezione di metodi per avviare un thread, eseguirlo, fermarlo e per tenere traccia dello stato di un thread. Il supporto per i thread di Java include un sofisticato set di primitive di sincronizzazione basate sull’uso dei monitor. La gestione dei thread in genere e di tipo preemptive, anche se il più delle volte dipende dalla piattaforma sulla quale Java è in esecuzione. Nei sistemi in cui la gestione è non preemptive può accadere che un solo thread acquisisca il controllo del processore, impedendo così ad altri thread di essere eseguiti, in tal caso Java fornisce il metodo yield() che dà, ad un altro thread, la possibilità di essere comunque eseguito. 

      Java supporta il multithreading a livello di linguaggio, attraverso il sistema run-time e attraverso gli oggetti thread. A livello di linguaggio, i metodi dichiarati synchronized, all’interno di una classe, non possono essere eseguiti simultaneamente da più thread; tali metodi vengono eseguiti sotto il controllo di un monitor per assicurare che le variabili rimangano in uno stato "consistente". Ogni classe e ogni oggetto istanziato ha il suo proprio monitor che entra in gioco quando richiesto. Quando un metodo synchronized entra in un thread, esso acquisisce un monitor sull’oggetto corrente, il monitor preclude dall’esecuzione qualsiasi altro metodo synchronized dichiarato in quella classe; quando il metodo synchronized ritorna in qualsiasi modo, il suo monitor è rilasciato, allora altri metodi synchronized dello stesso oggetto sono liberi per poter essere eseguiti. I monitor Java sono rientranti: un metodo può acquisire lo stesso monitor più di una volta, inoltre la libreria Java run-time è thread-safe nel senso che ogni metodo che potrebbe cambiare il valore di una variabile istanza è stato dichiarato synchronized, questo assicura che solo un metodo può cambiare lo stato di un oggetto in un determinato momento. 
       
       

    1.7 Sicurezza in Java
      L’ambiente Java fa la supposizione che nulla sia di fiducia, così il compilatore e il sistema run-time implementano dei strati di difesa contro il codice che può essere potenzialmente pericoloso. 

      Uno dei primi livelli di difesa del compilatore Java è il modello di allocazione della memoria. Prima di tutto, le decisioni in riguardo all’allocazione della memoria non sono prese dal compilatore del linguaggio Java, piuttosto sono rinviate al sistema run-time che è diverso a secondo della piattaforma hardware e software su cui il sistema Java è in esecuzione. 

      Secondariamente, Java non ha più il concetto esplicito di puntatore, il codice Java compilato ha dei riferimenti alla memoria simbolici, riferimenti che sono risolti in indirizzi di memoria reali in fase di esecuzione, dall’interprete Java. I programmatori Java non devono più preoccuparsi di eventuali riferimenti fuori dalla memoria, anche perché l’allocazione della memoria e il modello dei riferimenti è completamente opaco al programmatore ed è controllato completamente da sistema run-time di Java.
       

Processo di verifica dei bytecode
Anche se il compilatore Java assicura che il codice Java non viola le regole di sicurezza, quando una applicazione (per esempio un browser del Web) importa dei pezzi di codice Java da qualunque parte, essa non sa se il codice segue le regole di sicurezza imposte da Java: il codice può non essere stato prodotto da un compilatore Java conosciuto. Il sistema run-time di Java parte dal presupposto di non avere fiducia nel codice e sottopone i bytecode a una serie di processi di verifica. 

I test vanno dalla semplice verifica che il formato del codice sia corretto, a tutta una serie di controlli che verificano se:

  • non ci sono puntatori "falsi"
  • non sono violate le restrizioni di accesso.
  • gli accessi agli oggetti siamo come ciò che essi sono (un oggetto InputStream è usato come un InputStream e mai in altri modi).
        La verifica dei bytecode
Più parti compongono il sistema run-time di Java, questo per garantire che il codice eseguito sia sicuro. Nella Figura 1.2 è illustrato il flusso dei dati e i controlli del sistema Java, controlli fatti attraverso il compilatore Java, il caricatore di bytecode, il verificatore di bytecode e quindi l’interprete Java. 
 
 

Figura .2 Il sistema di controllo e verifica dei bytecode in Java 

Il verificatore dei bytecode esamina un bytecode per volta, costruendo informazioni riguardanti lo stato dei tipi e verifica che il tipo di ciascun parametro, argomento e risultato sia corretto. 

Sia il caricatore di che il verificatore non fanno nessuna assunzione sulla sorgente primaria del codice che può essere un sistema locale o il network; il verificatore di bytecode agisce come un "portinaio": esso assicura che il codice passato all’interprete Java sia in buono stato e può essere eseguito senza pericoli di interruzione. 

Quando il codice viene importato non è permessa l’esecuzione finché non ha superato tutti i testi del verificatore. Una volta che il verificatore ha terminato un importante numero di proprietà sono conosciute:

  • Non ci sono operandi nello stack in overflow o underflow.
  • I tipi e i parametri di tutte le istruzioni dei bytecode sono noti e sono corretti.
  • Accessi ai campi di un oggetto sono legali.
Questi controlli che possono sembrare tormentosi, servono a fare in modo che quando il verificatore ha terminato il suo lavoro, l’interprete Java, sapendo che il codice è sicuro, possa procedere velocemente senza effettuare nessun ulteriore controllo. 

Il verificatore, quindi, rappresenta la parte cruciale del sistema di sicurezza di Java e il suo buon funzionamento dipende dalla corretta implementazione del sistema di esecuzione. Inizialmente solo la Sun Microsystems ha prodotto i sistemi di esecuzione di Java e questi sono sicuri. Successivamente altre società hanno stretto accordi con la Sun Microsystems è produrranno una loro versione dell’ambiente di esecuzione di Java. 

In futuro la Sun Microsystems ha intenzione di implementare tecniche di convalida per i sistemi di esecuzione, compilatori e così via, in modo da verificare la sicurezza e il corretto funzionamento. 

Sicurezza nel caricatore di bytecode
Mentre un programma Java è in esecuzione, potrebbe accadere che una classe o un set di classi devono essere caricate, magari attraverso il network. Quando una classe viene caricata, essa viene inserita in uno tra i diversi name spaces del sistema Java. Ci sono diversi name spaces, uno per le classi che vengono dal sistema locale, un altro per le classi che vengono dal network e un altro ancora per le classi che magari vengono dalla rete locale; in realtà il numero dei name spaces può variare a secondo del livello di sicurezza che si intende raggiungere. Questo perché il caricatore delle classi è sotto il controllo di chi lo possiede, ogni programmatore può creare un proprio caricatore di classi che implementi un tipo peculiare di sicurezza. 

In particolare, il caricatore delle classi non consente mai a una classe proveniente da un name spaces "meno protetto" di sostituire una classe di un name spaces più protetto. 

Quando una classe è importata dal network essa è messa in un name spaces privato associato con la sua origine. Se fa riferimento a un’altra classe, essa è cercata prima nel name spaces del sistema locale, poi nel name spaces successivo più esterno e così via , così si garantisce anche che in nessun modo una classe proveniente dall’esterno possa sostituire o "ingannare" una classe del sistema Java stesso. In più, le classi di un name spaces non possono richiamare i metodi delle classi di altri name spaces, a meno che queste classi non abbiano dichiarato esplicitamente i metodi come public. Per esempio, le primitive di input/output del sistema di gestione dei file, per le quali occorre essere molto attenti, sono tutte definite in una classe locale di Java, per cui, le classi al di fuori del computer locale non possono nemmeno vedere i metodi di input/output del sistema di gestione dei file. 

Il caricatore delle classi in sostanza ripartisce il mondo delle classi di Java in gruppi piccoli e protetti, sui quali è possibile fare, in tutta sicurezza, delle assunzioni che saranno "sempre" vere. 

 l gestore della sicurezza
In precedenza si è detto che un programmatore può implementare un proprio caricatore delle classi, di fatto non è necessario, perché tutte le decisioni relative alla sicurezza che il sistema deve adottare durante l’esecuzione dei bytecode sono state riunite nella classe astratta SecurityManager che permette di eseguire la maggior parte delle personalizzazioni al riguardo. 

Risulta sempre installata un’istanza di una certa sottoclasse di SecurityManager come gestore della sicurezza corrente. Essa ha il controllo completo in riguardo a quali metodi, all’interno di un gruppo ben definito di metodi "rischiosi", possano essere richiamati da una certa classe. In questo gruppo ben definito di metodi sono contenute le operazioni di input/output su file, metodi che creano e utilizzano connessioni di rete sia in ingresso che in uscita e i metodi che consentono a un thread di accedere, controllare, e manipolare altri thread.


 | Index | Next