MokaPackages 
I packages del JDK
java.lang
di
Piergiuseppe
Spinelli
Iniziamo a tracciare questa guida all'esplorazione del JDK con il package usato da tutti i programmi Java




I questo package troviamo le classi e le interfaccie senza le quali Java, non funziona. Molte sono di uso frequente, altre vengono utilizzate internamente dal linguaggio e da altre classi standard
Object
Questa classe è la radice della gerarchia di classi Java essendo, per definizione, l’ancestor di tutte le altre, comprese quelle implicite comegli array. I metodi di Object sono fondamentali per un motivo banale: essi sono ereditati da tutte le altre classi; questo dovrebbe essere un buon motivo per familiarizzare con essi, ecco i principali:
Object.notify(), Object.notifyAll(), Object.wait(…)
Insieme alla keyword syncronized, alla classe Thread ed all’interfaccia Runnable costituiscono un set completo di primitive di multitascking con possibilità di implementare (c’è un teorema, da qualche parte…) tutte le altre primitive classiche (regioni critiche, semafori, monitors, messaggi etc…).

Object.equals(), Object.clone(), Object.hashCode()
Il metodo equals implementa la relazione di equivalenza tra due oggetti appartenenti alla stessa classe. E’ importante ricordare che, in Java, l’operatore uguale di confronto ‘==’ non determina oggetti distinti con il medesimo contenuto ma solo se due distinte variabili reference puntano al medesimo oggetto. Insieme all’interfaccia Comparable, questo metodo virtuale (che necessità di essere riscritto per la maggior parte delle nuove classi), è l’unica base per il confronto di contenuto tra oggetti.
Il metodo hashCode garantisce di ritornare un intero (calcolato con un algoritmo di hash) sempre uguale nel corso della stessa elaborazione. Se due oggetti sono uguali secondo il metodo equals, hashCode restituisce lo stesso intero per entrambi. La classe java.util.Hashtable utilizza questo metodo.
Il metodo virtuale clone è pensato per essere implementato in ogni classe che necessiti della funzione di duplicazione. Tale funzionalità deve essere esplicitata dichiarando che le classi interessate implementano l’interfaccia Cloneable. E’ necessario tener presente che l’operatore Java uguale di assegnazione ‘=’ non copia un oggetto ma si limita ad assegnare ad una variabile reference l’indirizzo dell’oggetto bassegnato. L’esempio seguente può chiarire l’uso di queste due metodi fondamentali:


MyClass a=new MyClass("X");
MyClass b=a;
if(a==b) System.out.println("A==B");                    //stampa: A==B
else System.out.println("A!=B");
if(a.equals(b)) System.out.println("A equals B"); 
else System.out.println("A not equals B");              //stampa: A not equals B
b=a.clone();
if(a==b) System.out.println("A==B"); 
else System.out.println("A!=B");                        //stampa: A!=B
if(a.equals(b)) System.out.println("A equals B");       //stampa: A equals B
else System.out.println("A not equals B");

 

Object.toString()
Ogni classe Java ha un metodo, ereditato da Object, che ritorna una sua rappresentazione sotto forma di stringa. Tale metodo viene spesso reimplementato per adattarsi alla specifica classe. L’operatore di concatenazione "+" utilizza implicitamente tale metodo.
 
 

Object.finalize()
Metodo chiamato dalla JVM prima che il garbage collector reclami la memoria di un oggetto non più referenziato. Si può implementare per le classi derivate da Object al fine di chiudere connessioni, file o sistemare qualsiasi altra questione lasciata in sospeso dall’oggetto moribondo.
 
 

Object.getClass()
Restituisce la classe di un oggetto. L’identificazione run-time delle classi di oggetti accumunati, ad esempio, dalla stessa interfaccia ha numerosi tipici utilizzi in sistemi Object Oriented.
 
 

String, Thread, ThreadGroup, Runnable
Queste classi sono accumunate dal fungere da sostegno per alcuni meccanismi nativi del linguaggio Java. Il tipo String, ad esempio, è usato quasi come un tipo elementare, tanto che esiste un operatore, il ‘+’ di concatenamento, che lavora su di esso. Tuttavia tale tipo è definito come una normale classe, al pari di tante altre, derivata da Object e la sua definizione nel package standard java.lang garantisce la sua presenza in ogni programma.
Stesso discorso vale per i thread che sono un meccanismo di base in Java ma che hanno bisogno dell’interfaccia Runnable e della classe Thread per poter essere acceduti dai programmi.
La classe ThreadGroup è una utilità per trattare un insieme di thread con caratteritiche comuni (ad esempio impostando setDaemonsu un gruppo equivale a farlo su tutti i thread che ne fanno parte).
Non è il caso, in questa sede, di elencare tutti i metodi applicabili alle stringhe: per questo ci si può affidare alla documentazione on-line del JDK. Vale invece la pena di dare qualche ragguaglio sulle principali funzionalità fornite per governare il multithreading.
 
 

Runnable.run()
Unico metodo dell’interfaccia Runnable, contiene il codice eseguito nelle classi derivate da Runnable. Anche la classe Thread implementa Runnable e, per default, il suo metodo run chiama il methodo run dell’oggetto di tipo Runnable con cui viene inizializzato il thread:
 

class myCode implements Runnable {
        public void run(){
                …
        }
}

Thread myThread = new Thread(new myCode());
E’ comunque possibile sovrascivere direttamente il metodo run della classe Thread:
 
Class myCode extends Thread{
        Public void run(){
                …
        }
}
MyThread myThread=new MyThread();

 

Controllo dei thread
Un processo leggero, in Java, inizia la propria esecuzione solo dopo essere stato attivato con start, può cedere CPU ad altri task con yield, sospendersi per un certo tempo con sleep, venire marcato come interrotto con interrupt, attendere la fine esecuzione di un altro thread chiamando il metodo join di quest’ultimo, impostare una priorità che, nella maggior parte delle JVM, non è bloccante e dichiararsi daemon. Il comportamento dei thread demoni è assolutamente analogo a quello degli altri thread tranne che la JVM termina la propria esecuzione quando tutti i thread ancora vivi sono di tipo daemon.
Molti metodi, inizialmente parte di questo gruppo, sono stati deprecati nelle versioni successive in quanto portavano possibili problemi di sincronizzazione, di pulizia o addirittura di blocco critico. Tra questi ricordiamo stop, suspend e resume. Un possibile metodo per governare la terminazione di un thread ad esecuzione ciclica (ovvero il classico task che contiene una grande ciclo while nel proprio metodo run) è il seguente:
 

class MyThread extends Thread{
        private Thread t=null;
        boolean inChiusura=false;
        public synchronized boolean Inizia(){
                if(inChiusura) wait();
                if(t!=null) return false;
                t=this;
                stillRunning=true;
                start();
                return true;
        }
        public synchronized boolean Termina(){
                if(t==null) return false;
                inChiusura=true;
                t=null;
                return true;
        }
        public void run(){
                //Inizializzazioni
                synchronized(this){
                        ...
                }
                //Loop principale del thread
                while(Thread.getCurrentThread()==t){
                        …
                }
                //Finalizzazioni
                syncronized(this){
                        ...
                        inChiusura=false;
                        notifyAll();
                }
        }
}

 

Variabili con visibilità locale ai thread
Una delle carenze del meccanismo standard di visibilità delle variabili è che esso non tiene affatto presente il concetto di thread. E’ quindi possibile dichiarare una variabile locale ad un oggetto oppure ad un’intera classe ma non a tutti gli oggetti che sono eseguiti nello stesso thread. Per raggiungere questo scopo è stata creata la class ThreadLocal, con i metodi inizialize, get e set. Le variabili dichiarate di questo tipo (normalmente delle static di classe) gestiscono internamente un oggetto diverso per ogni diverso Thread, indipendentemente dalla classe in cui sono dichiarate.
 
 
 

Comparable, Cloneable
Queste interfacce consentono lo svolgimento di alcune operazioni basilari per ogni linguaggio: ordinamento e copia. Un oggetto di una classe che non implementa tali interfacce può essere testato per l’eguaglianza (mediante il metodo equals di Object) ma non è possibile determinare se è maggiore o minore di un altro oggetto né creare una copia identica dello stesso.
 
 

java.io.Serializable
Attenzione: questa interfaccia è un infiltrato in quanto non appartiene a java.lang e, per poterla usare, è necessario importare il package java.io di cui si parlerà prossimamente. Il motivo per cui ne parlo qui è che anche Serializable appartiene ai meccanismi di base di Java e, infatti, esiste una keyword del linguaggio, transient, il cui uso ha senso solo per le classi che implementano Serializable per dichiarare la prorpia disponibilità a generare oggetti permanenti, ovvero registrabili su un qualsiasi supporto persistente (p.e. file o database) o anche ad essere inviate ad un sistema remoto tramite socket. E’ importante capire bene il meccanismo di serializzazione in quanto esso è alla base delle tecniche Java più avanzate per l’esecuzione in ambienti distribuiti, prima fra tutte RMI che consente di vedere come locali oggetti che, in realtà, sono eseguiti su altri computer.
 
 
 

Throwable, eccezioni ed errori di sistema
Il lancio di un oggetto è una caratteristica interna di un linguaggio. Un oggetto viene scagliato con, allegata, una istantanea della situazione dello stack scattata in modo sincrono (esecuzione dell’istruzione throw o errore di esecuzione di un’istruzione) o asincrono dalla JVM per cause esterne. Il lancio deve essere acchiappato da apposite istruzioni nel blocco in esecuzione. Se la funzione in esecuzione non ha istruzioni per la cattura di un lancio (try{…}catch(…){…}), essa viene terminata forzatamente e il controllo torna alla funzione chiamante; se anche questa non ha istruzioni di cattura, il lancio si propaga risalendo tutto lo stack delle chiamate fino ad incontrare una funzione predisposta per la cattura o, in caso negativo, terminare il programma.
La classe Throwable non è utilizzata direttamente molto frequentemente mentre le due classi derivate, Error ed Exception sono, a loro volta, la radice di tutte le eccezioni ed errori generati da tutte le classi Java.
Eccezioni
Le eccezioni sono anomalie di esecuzione o semplici condizioni al di fuori del normale flusso di un programma. E’ normale per un buon programma cercare di catturare tutte le eccezioni che si presume possano venir sollevate dai metodi chiamati e, a volte, può essere considerata una buona pratica lasciare che l’eccezione venga lanciata piuttosto che controllare preventivamente il codice, come nell’esempio seguente:
 
 
Conteggio con controllo a condizione
Conteggio con controllo ad eccezione
final int max=100;
int[] arr=new int[max];
for(int i=0;i<max;i++){
        arr[i]=generaNumero(i);
}
stampaArray(arr);
int[] arr=new int[100];
try{
        for(int i=0;;i++){
                arr[i]=generaNumero(i);
        }
}catch(ArrayIndexOutOfBoundsException e){}
stampaArray(arr);

Quello che segue è l’elenco di tutte le eccezioni definite in java.lang che riguardano tutte le situazioni anomale che possono venire generate dalla JVM:
 

ArithmeticException
ArrayIndexOutOfBoundsException
ArrayStoreException
ClassCastException
ClassNotFoundException
CloneNotSupportedException
Exception
IllegalAccessException
IllegalArgumentException
IllegalMonitorStateException
IllegalStateException
IllegalThreadStateException
IndexOutOfBoundsException
InstantiationException
InterruptedException
NegativeArraySizeException
NoSuchFieldException
NoSuchMethodException
NullPointerException
NumberFormatException
RuntimeException
SecurityException
StringIndexOutOfBoundsException
UnsupportedOperationException


Errori
Gli errori sono anomalie talmente gravi che, normalmente, non ha senso cercare di catturarle. Gli errori sono spesso causati dall’hardware, dall’esaurimento di risorse come la memoria, dalla corruzione dei file di classe o da strane situazioni, come classi modificati dopo essere state collegate ad altre.
Ecco tutti gli errori riconosciuti in Java e definiti in java.lang:
 

AbstractMethodError
ClassCircularityError
ClassFormatError
Error
ExceptionInInitializerError
IllegalAccessError
IncompatibleClassChangeError
InstantiationError
InternalError
LinkageError
NoClassDefFoundError
NoSuchFieldError
NoSuchMethodError
OutOfMemoryError
StackOverflowError
ThreadDeath
UnknownError
UnsatisfiedLinkError
VerifyError
VirtualMachineError
Wrappers
In Java, è risaputo, non esistono puntatori. Se questo fa piacere ad un primo pensiero, potrebbe creare difficoltà all’atto pratico: visto che che tutti i parametri sono passati per valore e non posso passare il puntatore ad un intero, come posso far sì che un metodo ritorni i propri parametri numerici modificati?
Ecco dove entrano in scena le classi wrapper, ovvero classi corrispondenti ai tipi di base. Oltre che fungere da pacchetti di trasporto per i parametri in uscita, i wrapper forniscono metodi per la formattazione e la conversione di tipo. Tutti i wrapper sono definiti in java.lang:
Boolean
Byte
Character
Double
Float
Integer
Long
Number
Short
Void

 

Dynamic loading
Il caricamento dinamico delle classi è una delle caratteristiche principali di Java. Buona parte del lavoro viene svolto dal compilatore (javac), e dall’esecutore (java). In molti casi, comunque, le classi da caricare sono conosciute solo a tempo di esecuzione e per questo motivo java.lang mette a disposizione le primitive per caricare classi, anche da sistemi remoti, interrogarle per conoscerne i contenuti e accedere metodi e proprietà pubbliche.
Anche un semplice elenco di tutti i metodi che concorrono a questa gestione richiederebbe un’intero articolo. Basti sapere che le classi Package, Class, ClassLoader, Array, ed i package java.lang.reflect e java.lang.ref, consentono di fare qualsiasi cosa ci venga in mente, se consentita dal securityManager attivo, anche creare una classe nuova partendo dal suo byte code!!
La cosa importante da notare è che questo meccanismo di caricamento dinamico fa sì che, implicitamente, ogni classe ed applicazione Java sia un componente utilizzabile da altri programmi. La specifica JavaBean, infatti, si limita a fornire una convenzione sui nomi da utilizzare per far riconoscere le proprietà pubbliche ai tool di sviluppo visuale e a dare un insieme di classi di utilità per realizzare agevolmente ciò che SUN ha denominato introspezione: queste classi sono scritte in pure Java e, quindi, non è stato aggiunto alcun nuovo meccanismo del linguaggio per la creazione di componenti.
 
 
 

Gestione dell’ambiente operativo
Tutti i linguaggi hanno bisogno di interfacciarsi al sistema operativo ospite. Java, in particolare, offre un set di classi che cercano di mascherare, per quanto possibile, le differenze tra gli ambienti più eterogenei in cui un programma Java può essere eseguito.
 
 
 

System e Runtime
Le classi System e Runtime contengono proprietà e metodi per gli scopi più diversi. Ecco elencate alcune delle principali funzionalità offerte:

    System:
    Copia veloce di array con arraycopy
    Lettura del clock di sistema, in millisecondi, con currentTimeMillis
    Interazione con le variabili di ambiente: setProperty, setProperties, getProperty, getProperties
    Accesso all IO standard di console con le proprietà in, out e err e loro redirezione con setIn, setOut e setErr
    Impostazione dei livelli di sicurezza con setSecurityManager
    Runtime:
    Esecuzione di processi nativi con exec
    Controllo della memoria con freeMemory, totalMemory, waitForMemoryAdvice
    Supporto al debug ed al profiling con traceInstructions e traceMethodCalls
    Condivisi da System e Runtime:
    Terminazione della JVM con exit
    Forzatura del garbage collector con gc e della finalizzazione con runFinalization
    Supporto alle classi con metodi nativi tramite load e loadLibrary


Process
E’ una classe ritornata dai metodi Runtime.exec e serve a rappresentare un’applicazione (normalmente non Java) lanciata in un processo dell’OS ospite. Consente di redirigere l’IO standard del processo rappresentato e di sincronizzarsi sulla sua terminazione.
 
 
 

Gestione della Sicurezza
La nascita di Java è coincisa con il boom dell’elaborazione distribuita, in particolare quella basata sul paradigma INTERNET, tanto che per un lungo periodo il linguaggio della SUN è stato identificato un po’ da tutti come IL linguaggio di INTERNET. E’ normale che, trattando di ambienti distribuiti, il problema della sicurezza sia centrale in ogni aspetto del linguaggio. Ciò è confermato dal fatto che le classi ed i metodi inerenti la Security sono stati cambiati praticamente in tutte le successive release del linguaggio, evolvendo da un modello fortemente restrittivo a quello del JDK 1.2, estremamente potente e configurabile ma, proprio per questo, tanto complesso da richiede interi documenti dedicati solo a questo argomento (vedi Risorse).
Il package java.lang contiene alcuni meccanismi basilari per il controllo di sicurezza.
Come si è detto, la classe System consente di impostare una classa derivata da SecurityManager; l’applicazione potrà poi chiamare il metodo checkPermission per testare la fattibilità delle sensitive operations tenenedo anche presente il ClassLoader con cui ogni classe è stata caricata (classi scaricate da alcuni siti, per esempio, potrebbero avere meno privilegi di quelle caricate in locale), ed il SecurityContext che può dipendere da molti fattori (come, ad esempio, il thread in esecuzione).
I tipi di permosso testabili possono appartenere ad una delle seguenti categorie, ognua delle quali corrisponde ad una particalare classe:

    File : java.io.FilePermission
    Socket : java.net.SocketPermission
    Net : java.net.NetPermission
    Security : java.security.SecurityPermission
    Runtime : java.lang.RuntimePermission
    Property : java.util.PropertyPermission
    AWT : java.awt.AWTPermission
Il Class Tree dei vari tipi di permesso basilari è il seguente:
    java.security.Permission
    java.io.FilePermission
    java.net.SocketPermission
    java.security.BasicPermission
      java.net.NetPermission
      java.security.SecurityPermission
      java.lang.RuntimePermission
      java.util.PropertyPermission
      java.awt.AWTPermission
    java.security.AllPermission
Alcune classi derivate da Permission hanno specifici metodi per controllare i permessi di particolari operazioni, come la lettura e/o scrittura di file.
A questo meccanismo, già di per sé non semplice, si aggiungono livelli assai più articolati di controllo, come liste di ACL, Chiavi pubbliche e private, certificati ed altro, gestiti dal package java.security e dai suoi sotto-package: acl, cert, interfaces e spec.
 
 
 

Gestione della memoria dinamica
Abbiamo già dato un’occhiata alle classi Runtime e System ed ai loro metodi per la gestione della memoria. Uno dei più pubblicizzati punti di forza di Java è il meccanismo automatico di gestione della memoria dinamica, basato sul lavoro del Garbage Collector. E’ certamente vero che la maggior parte degli errori nel software professionale è dovuto all’uso dei puntatori ed alla mancata restituzione della memoria allocata (ma questo, se lavorate sotto Windows, già lo sapete!!!). Tuttavia, anche se il meccanismo di garbage collection è comodissimo, esso porta a dei naturali abusi: non solo i programmi utente, ma tutto il JDK e pieno di righe come la seguente:
 

for(i=0;i<100000;i++)
        x[i] = new ClasseX(new ClasseY(new String("ABC")));
Anche se non ci si presenterà il problema di liberare la memoria allocata, il nostro programma andrà soggetto a frequenti mancamenti dovuti alla necessità del JVM di riordinare la memoria non più referenziata per renderla riutilizzabile. Tali blocchi momentanei possono essere solo fastidiosi nel programmi interattivi ma assolutamente inaccettabili in altri contesti, come ul riproduttore di filmati o un programma di controllo di processi industriali.
In un mio precedente articolo, PERC, la via dura per Java Real-Time, riportavo i risultati di una piccola ricerca svolta per confrontare le performances di un normale programma che si affidava al rilascio automatico della memoria con un’altra versione che creava una sorta di heap privata ed un meccanismo simile alle alloc/free del C. Era tutto ciò che si poteva fare con il JDK dell’epoca e, a fronte di un effettivo aumento della velocità e dell’omogeneità di esecuzione, si ritornava, di fatto, ad una gestione manuale della memoria.
Il problema doveva esser ben noto alla SUN che, nell’ultimo JDK, ha aggiunto il package java.lang.ref che dovrebbe consentire di creare gestioni personalizzate dell’allocazione e rilascio della memoria conservando gli automatismi nativi di Java. In particolare, la nuova classe SoftReference consente di mantenere delle cache di oggetti, che possono essere riutilizzati senza riallocazione ma anche reclamati dal Garbage Collector in caso di necesità.
 
 

Supporto alla Compilazione JIT
La classe Compiler, infine, è (incredibile) final di nome ed abstract di fatto!
In realtà essa contiene dei metodi che non fanno nulla ma fungona da interfaccia per il meccanismo di compilazione al volo usata dai Just In Time compiler (JIT).
Se la JVM, al proprio lancio, trova la proprietà di sistema java.compiler, la utilizza per individuare una libreria dinamica (p.e. una DLL Windows) che implementa le funzioni per la compilazione delle classi Java nel linguaggio macchina del sistema ospite. Tali funzioni verranno chiamate attraverso i metodi esposti dalla classe Compiler.
Conclusioni
Nei moderni linguaggi Object Oriented esiste un labile confine tra il linguaggio stesso ed alcune Classi di base senza le quali il linguaggio stesso non potrebbe funzionare. In Java la maggior parte di tali classi sono racchiuse nel package java.lang.
Prossimamente parleremo di altri due package, java.io e java.util, che, se non basilari, sono altrettanto importanti nell’offrire al programmatore quei meccanismi di base che ricorrono in quasi tutte le applicazioni.
 
 

Risorse

    JDK Documentation (JavaSoft website). Il sito ufficiale di documentazione per il JDK 1.2
    Changes and Release Notes for the JDK 1.2 Software. Cambiamenti e novità nell'ultima versione del JDK.
    1.1 Packages- java.lang, java.net, java.text, java.util, java.math
    The Java Class Libraries
    The Java Class Libraries-Second Edition, Vol. 1-1.2 Supplement
    Security Enhancements
    Security in JDK 1.2
    Security and Signed Applets
    Fundamentals of Java Security
    Serialization Enhancements
    Reference Objects, including weak references
    Reference Objects
    Reflection
    Essential Java Classes
    Concurrent Programming in Java

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it