MokaPackages 
I packages del JDK
java.util
di
Piergiuseppe
Spinelli
Il buon vecchio kit del programmatore Java è cresciuto fino a comprendere un framework per le collezioni e altre aggiunte



Gli strumenti più utili sono quelli per la mente. Non solo pattern, astrazioni e modelli, ma anche più semplicemente classi predefinite per paradigmi ed algoritmi di uso frequente che, insieme, formano un bagaglio che senza esagerazioni definirei culturale.

Introduzione
 

l Culturale nel senso che un tempo, con gli strumenti messi a disposizione dai linguaggi in voga negli anni settanta/ottanta, si ragionava in termini di array, indirizzi, record, indici e via dicendo, mentre ora è naturale, in fase di design e codifica, pensare a collezioni, stack, insiemi di proprietà astratte, eventi, interfacce ed altro. Nessun concetto nuovo, in fondo, ma il solo fatto di avere tali strumenti a portata di mano al pari degli oggetti di base di un linguaggio, consente di operare da una prospettiva più ampia, maggiormente focalizzata sui problemi da risolvere piuttosto che sull'implementazione degli algoritmi e delle strutture dati. Per il programmatore moderno, forse, l'equazione di Nicolas Wirth, "Algortimi + Strutture Dati = Programmi", sta diventando obsoleta e potrebbe essere sostituita da qualche cosa del tipo "Pattern + Componenti = Sistemi".
C'è un altro grande vantaggio nell'usare dei package standard, almeno quelli ben organizzati: è possibile scegliere in fase di prima implementazione (o, se preferite, di prototipo) delle classi ad un livello base (p.e. HashSet) e riferirsi ad esse mediante un'interfaccia generica (p.e. Collection) e, in un secondo tempo eseguire delle ottimizzazioni mirate al particolare programma semplicemente sostituendo la dichiarazione di tali oggetti con l'adozione di classi più specializzate (p.e. TreeSet) o appositamente create, lasciando intatto il resto del codice e seguendo il vecchio pattern di codifica che recita "prima fallo funzionare, poi fallo più piccolo e veloce".
Vediamo, quindi, cosa ci offre la cassetta degli attrezzi di Java: il package java.util.
 
 
 

Java.Util
In questo package sono raccolti strumenti di uso generico, come un tokenizzatore, un array di bit, un generatore di numeri casuali e strutture dati con gestione LIFO e FIFO, classi per la manipolazione di date ed orari e delle proprietà di localizzazione che servono per far funzionare un programma sotto le convenzioni diverse che vigono in diversi paesi (formati delle date, separatori decimali etc...).
Vengono fornite inoltre le classi da cui derivano i vari modelli ad eventi utilizzati in Java così come un'implementazione di base del pattern Observer/Observable che viene ritrovato, in varie salse, in molte altre parti del JDK.
Infine, nella versione 1.2, viene dato un framework di collezioni dati di vario genere sulla cui base sono state reimplementate anche le vecchie collezioni (Vettori, Hashtable etc...) che non sono state deprecate ma il cui uso, però, viene scoraggiato in favore delle nuove e meglio strutturate classi collection.
 
 
 

Notifiche ed Eventi
Il pattern Observer/Observable è utile per sincronizzare diversi oggetti passibili di variazioni di stato con altri oggetti che devono ricevere notifica di tali cambiamenti. Si tratta di una relazione dinamica di tipo N a N, nel senso che un osservatore può registrare il proprio interesse contemporaneamente presso diversi oggetti osservabili ognuno dei quali può, a sua volta, notificare i propri cambiamenti a vari osservatori registrati su di esso. Questo pattern è, ad esempio, alla base del paradigma model-view-controller che consente di separare la rappresentazione dei dati da quella delle operazioni e dalle possibili presentazioni degli stessi.
Da notare che Observerè un'interfaccia con un solo metodo, update, che deve essere implementato per ricevere le notifiche dalla classi derivate dalla classe astratta Observableche possono auto-marcarsi come modificate con il metodo setChanged e richiamare in sequenza, senza garantire alcun ordinamento, tutti gli osservatori registratisi tramite addObserver, eseguendo notifyObservers. Contrariamente al meccanismo di notifica di tipo semaforico offerta dalla classe Object con i metodi notify e wait, gli osservatori sono visti come oggetti passivi (non Runnable) ed i metodi update sono, a tutti gli effetti, della call-back richiamate nel contesto del thread che esegue il codice dell'oggetto osservato (Vedi [1]).
Il pattern Observer/Observable, come implementato in Java, invia al metodo update un reference all'oggetto osservato, la sorgente della notifica, ed un Object opzionale per eventuali informazioni accessorie. I progettisti di AWT e, in seguito, di SWING, così come quelli della specifica JavaBeans, hanno ritenuto che tale implementazione fosse troppo destrutturata e mal si prestasse alla costruzione di un framework di interfaccia event-driven. Essi hanno optato per un albero di oggetti Ascoltatori che implementassero una semplice interfaccia tagging: EventListener. Un'interfaccia tagging è caratterizzata dall'assenza di metodi ed ha l'unico scopo di marcare le classi (o le interfaccia) da essa derivate come appartenenti ad una determinata famiglia (un esempio e dato da Serializable). Tutti gli eventi, invece, sono stati fatti derivare dalla classe EventObject con l'unico metodo getSource. Credo che uno dei motivi principali di tale scelta progettuale, in un linguaggio con ereditarietà singola, sia stata la necessità di liberare i vari oggetti componenti i package come AWT dal giogo di dovere tutti derivare da Observable. Inoltre viene definita una maggiore specializzazione per cui ogni evento può essere ricevuto solo dalle intefacce appositamente progettate per ricevere quell'evento, e non ogni tipo generico di notifica.
EventObject è dichiarata Serializable (pur essendo transient l'unico campo in essa contenuto: source); questo può far pensare che tale classe sia stata pensata avendo in mente di far derivare da essa anche Oggetti-Evento da diramare in remoto, ad esempio nel contesto di rmi.
 
 
 

Date, orari ed internazionalizzazioni
La classe Daterappresenta un istante di tempo con la precisione di un millisecondo. La maggior parte dei metodi di questa classe sono stati deprecati dal JDK 1.1 e le loro funzioni affidate a classi più specialistiche come Calendar o text.DateFormat. Date è sempre utilie per eseguire confronti ed ordinamenti tra date ed orari.
Calendarè una classe astratta pensata allo scopo di eseguire tutte le consuete conversioni ed estrazioni sul tipo data (ad esempio suddividere una data in componenti interi rappresentanti anno, messe oppure settimana, giorno della settimana e così via) tenendo conto delle regole di un particolare calendario e della località. Ad esempio GregorianCalendarè una implementazione di Calendar per il formato attualmente più diffuso nel mondo.
Se è necessario trattare con cambi di fuso orario (cosa sempre più frequente programmando su INTERNET) possiamo utilizzare le classi derivate da TimeZonee, in particolare, SimpleTimeZoneche usa il calendario Gregoriano. I metodi di tali classi consento di effettuare rapide conversioni anche utilizzando abbreviazioni standard per le zone con convenzioni temporali comuni (le abbreviazioni riconosciute sono ritornate, sotto forma di array, dal metodo getTimeZone. Il metodo getDefault si basa sulla località e le convenzioni della piattaforma ospite, compresa l'ora legale.
I problemi di internazionalizzazione non riguardano solo fusi orari e calendari, ma una insieme di differenze su formati, rappresentazione e convenzioni che sono la croce (una delle tante, per la verità) del programmatore globalizzato. La classe Localeserve proprio da identificatore per tutte queste peculiarità e molte altre classi Java, disseminate in vari package, sono abilitate a ricevere un oggetto Locale come parametro per adeguare il proprio comportamento a tali regole (p.e. le classe Calendar e tutte le derivate da text.Format, come DateFormat, NumberFormat etc...). Un oggetto Locale implementa alcune regole ma non contiene tutte le risorse necessarie per l'adattamento linguistico (localizzazione) del software. Allo scopo di far girare lo stesso codice (proprio lo stesso, compilato una volta per tutte) in diverse versioni per pesi diversi, è possibile salvare le risorse sensibili alla localizzazione in dei bundle esterni, ovvero delle classi serializzabili, derivate da ResourceBundle, contenenti degli array di coppie (Chiave, Oggetto), dove chiave è una stringa e Oggetto appartiene ad una classe qualsiasi (solitamente una stringa tradotta nella lingua del particolare bundle). Il programma decide quale Locale utilizzare, carica dinamicamente il relativo ResourceBundle, al posto delle costanti di tipo stringa adoperera statement del tipo getString("chiave1").
La classe ListResourceBundle, derivata da ResourceBundle, consente anche di ottenere l'intero array delle coppie (chiave, Oggetto) per poterle managgiare in modo più personalizzato. Tuttavia il modo più semplice è quello di adoperare direttamente la classe PropertyResourceBundleper leggere un file esterno di risorse in formato .class o anche in formato testo, come il seguente:
 

File: RisorseProgramma_it
Titolo=Titolo
Cognome=Cognome
Nome=Nome
...
o, ad esempio, la sua versione americana

 

File: RisorseProgramma_en_US
Titolo=Title
Cognome=Last name
Nome=First name
...


L'utilizzo è banalizzato nell'esempio:
 

ResourceBundle locRes =
    ResourceBundle.getBundle("RisorseProgramma",
Locale.getDefault());
Varie
Queste due classi sono state schiaffate quì in quanto considerate di utilità generale. Personalmente le avrei viste meglio, rispettivamente, in java.math e java.text:
  • Random: generatore di numeri pseudo-casuali.
  • StringTokenizer: tokenizzatore di stringhe. Stabiliti i caratteri di punteggiatura e spaziatura suddivide una stringa in tokens (letteralmente gettoni. il termine indica le singole parole di un linguaggio, come variabili, costanti e keywork, che vengono poi passate ad un parser per l'effettiva traduzione). Un tokenizzatore completo, al pari di un parser, è un automa a stati finiti ma, per molte occasioni, questo mini-tokenizer si rivela adatto allo scopo, pur non potendo, ad esempio, ritornare un numero reale come un'unica entità controllando che il punto decimale non sia presente più di una volta.

 
 
 

Collezioni
Una collezione rappresenta un insieme di oggetti detti elementi. Tipi diversi di collezione implementano particolari caratteristiche come l'ordinamento, la non modificabilità, l'unicità degli elementi oppure una particolare organizzazione interna che ne ottimizzi l'accesso sotto determinate condizioni come, ad esempio, l'accesso per chiave.
Una generica collezione dovrebbe provvedere sempre due tipi di costruttore: uno senza parametri per la creazione di una collezione vuota ed uno avente per argomento una collezzione pre-esistente per utilizzare i suoi elementi come insieme iniziale.
Nel framework offerto dal JDK 1.2 Esistono due tipi di insiemi:

  • Collection: collezioni di singoli oggetti organizzati per sequenze ordinate (List) che consentono elementi uguali (rispetto al metodo equals) o per insiemi di elementi univoci (Set), eventualmente ordinati (SortedSet).
  • Map: collezioni di coppie chiave/valore (Map.Entry), eventalmente ordinate (SortedMap). 
Il concetto di ordinamento implica l'esistenza di altre due interfacce: Comparator ed Iterator.
Un Comparator è un "oggetto funzione" che implementa una determinata regola di ordinamento tra due oggetti con il metodo
 
int compare(Object o1, Object o2) 
che restituisce i valori:
 
  • 1 se o1>o2
  • -1 se o1<o2
  • 0 se ((Object)o1).equals((Object)o2)
La consistenza della relazione di eguaglianza con il metodo Object.equals deve essere garantita per un buon funzionamento delle collezioni ordinate. Quando si crea una collezione ordinata (List, SortedSet,SortedMap) si applica una determinata classe di tipo Comparatoroppure ci si affida all'ordine naturale degli oggetti se questi implementano l'interfaccia Comparable. Un esempio elementare di comparatore è la seguente classe:
 
public class OrdinaStringhe implements Comparator { 
        public int compare(Object o1, Object o2) { 
                String s1 = (String)o1; 
                String s2 = (String)o2; 
                return s1.toLowerCase().compareTo(s2.toLowerCase()); 
        }
}
Un Iterator consente di scandire sequenzialmente tutti gli elementi di una collezione e di compiere eventualmente operazioni di rimozione su alcuni di essi in modo controllato (anche in situazioni di accesso concorrente), questo non possibile utilizzando la vecchia interfaccia Enumeration che è mantenuta per compatibilità con il codice esistente. Un iteratore, inoltre, lancia subito un'eccezione ConcurrentModificationException se la collezione scandita viene modificata direttamente o tramite un'altro iteratore evitando così risultati impredicibili.
Alcuni metodi sono comuni a tutti i tipi di collection:
  • add: aggiunge un nuovo elemento (o anche un doppione, se la collezione supporta questa possibilità)
  • contains: testa l'esistenza di un elemento
  • isEmpty: testa se una collezione è vuota (priva di elementi)
  • iterator: ottiene un iteratore per la collezione
  • remove: rimuove un elemento
  • size: ritorna il numero di elementi attualmente contenuti
  • toArray: restituisce un array (Object[]) con tutti gli elementi della collezione
Altri metodi sono propri di particolari interfacce:
  • List:
    • get: restituisce l'elemento presente ad un certo indice
    • indexOf: cerca l'indice di un determimato elemento

    •  
  • Map
    • entrySet(): restituisce un Set di tutte le entry (coppie chiave/valore) nella Map
    • put(): crea una entry e la inserisce in una Map
Finora abbiamo parlato di interfacce ma, al fine di semplificare l'implementazione delle proprie classi collezione, vengono fornite delle classi astratte che realizzano buona parte del lavoro; ne esiste una per ogni interfaccia principale: AbstractCollection, AbstractList, AbstractMap, AbstractSequentialList (utile per implementazioni ad accesso sequenziale come le liste collegate), AbstractSet.
Il JDK offre anche un insieme di implementazioni specifiche che potranno essere usate nella maggior parte delle occasioni senza bisogno di realizzare classi apposite. L'idea è quella di fornire al programmatore uno scaffale di componenti software, in parte interscambiabili, ognuno con particolare predisposizione ad essere utilizzato in determinate circostanze. Ad esempio potremmo iniziare un prototipo utilizzando LinkedList e, in un secondo tempo, renderci conto di aver bisogno di un accesso non strettamente sequenziale e sostituire la collezione, con il minimo sforzo, con l'implementazione basata sugli array. Vediamo quali sono le collezioni del JDK direttamente utilizzabili:
  • ArrayList: implementazione di List tramite array ridimensionabili.
  • BitSet: implementa un insieme indicizzato di bit di dimensione dinamica con possibilità di leggere/impostare ogni singolo valore e di applicare gli operatori booleani tra diversi insiemi.
  • HashMap: implementazione base di una Map. Simile alla vecchia HashTable tranne che consente l'uso di valori null sia come oggetti che come chiave.
  • HashSet: implementazione base di un Set fatta "sulle spalle" di una HashMap.
  • LinkedList: implementazione di List derivata da AbstractSequencialList. Si tratta di una lista doppiamente collegata, il cui accesso è quindi altrettanto rapido sia dalla testa che dalla coda. Si presta all'implementazione di stack e code di vario tipo.
  • Properties: collezione serializzabile di coppie di stringhe chiave-valore.
  • PropertyPermission: collezione di permessi per compiere determinate azioni implementata sotto forma di Properties.
  • TreeMap: implementazione di base di una SortedMap basata su una struttura ad albero. L'ordinamento garantito è quello della chiave secondo l'ordine naturale se questa implementa Comparable o secondo il Comparator passato in fase di creazione della collezione.
  • TreeSet: implementazione di base di un SortedSet basata su una SortedMap. L'ordinamento garantito è quello delgli elementi secondo l'ordine naturale se questa implementa Comparable o secondo il Comparator passato in fase di creazione della collezione.
  • WeakHashMap: si tratta di una realizzazione per situazioni in speciali. Utilizza le nuove capacità di controllo sulla memoria del JDK 1.2 per realizzare una HasMap le cui chiavi possono essere comunque reclamate dal garbage collector se queste non hanno riforimenti forti da qualche altra parte.
Sono state mantenute le classi legacy (presenti dagli arbori del JDK): Dictionary, Hashtable, Vectore Stack; queste classi non sono state deprecate ma sono state re-implementate sulla base del nuovo framework: sembra comunque una buona idea evitarne l'uso futuro in favore delle nuove Map, HashMap, ArrayList e LinkedList.
Che si utilizzi una delle implementazioni prefabbricate o piuttosto una propria classe derivata, è comunque buona norma utilizzare reference del tipo adatto più generale. Utilizzare quindi:

       List lst = new ArrayList();

è meglio di: 

        ArrayList lst = new ArrayList();

questo rende più agevole posticipare ottimizzazioni dovute a test fatti sull'uso di un prototipo funzionante. Nel precedente esempio potremmo renderci conto che la quantità di editazione sulla lista è preponderante rispetto alla ricerca e, quindi, decidere di modificare la dichiarazione con:
 

        List lst = new LinkedList();
il che, adoperando una generica List come reference, non dovrebbe richiedere ulteriori modifiche al codice.
Un altro consiglio è quello di prediligere le classi già esistenti ad implementazioni custom (se proprio non dovete fare cose strane) e, quando una classe apposita debba porprio essere creata, attenersi alla seguente scaletta di priorità:
  1. Derivare da un'implementazione standard: la maggior parte delle volte che si rende necessaria una nuova classe collezione è per ottimizzare il compertamento di una classe esistente rispetto a determinate condizioni d'uso.
  2. Se, per qualche strano motivo, nessuna classe standard fornisse una buona base, partire da una classe astratta
  3. Solo nei casi più disperati implementare direttamente dalle interfacce Set, List o Map
  4. Non datemi un dolore ed evitate di partire implementando direttamente Collection!
Infine esistono due classi di utilità, con metodi statici:
  • Arrays: per operazioni sugli array come ordinamento, ricerche binarie, conversioni in liste etc...
  • Collections: contiene vari metodi per la manipolazione di intere collection. In particolare funge da factory per alcune classi wrapper che vengono create sulla base di collezioni esistenti per aggiungere particolari caratteristiche, come:
    •  
    • Collections.unmodifiableInterface: rende una collezione non modificabile.
    • Collections.synchronizedInterface: rende sincronizzati gli accessi ad una collezione. E' stata un'importante innovazione quella di rendere le classi di base di questo framework non sincronizzate: questo permette un grande aumento di performances ove non avvengano accessi concorrenti o dove la sincronizzazione viene controllata in modo specifico dagli oggetti che utilizzano le collezioni.

    • Inoltre questa classe implementa gli algoritmi principali sui vari tipi di collezione:
      • sort: ordinamento di una lista mediante l'algoritmo di merge-sort
      • binarySearch: ricerca (dicotomica) di un oggetto in una lista ordinata
      • reverse: rovesciamento dell'ordine degli elementi in una lista
      • shuffle: mescolamento casuale degli elementi di una lista
      • fill: sovrascrittura con un oggetto dato di tutti gli elementi di una lista
      • copy: copia di una lista
      • min: restituisce il valore minimo in una lista (secondo l'ordine naturale)
      • max: restituisce il valore massimo in una lista (secondo l'ordine naturale)
Questa è stata una breve introduzione alle potenzialità offerte dal framework di collezioni del JDK 1.2, secondo lo stile di questa rubrica. In risorse sono riportati vari link per approfondire ogni aspetto dell'argomento. Bisogna aggiungere che le collezioni, oltre ad costituire un utile paradigma di programmazione, sono anche propedeutiche all'uso dei DB ad oggetti, ma questo è un discorso che avremo modo di riprendere in futuro.
 
 
 

Package java.util.zip 
Sapere di avere questo strumento nella propria cassetta degli attrezzi renderà felice ogni programmatore. Si tratta di un sotto-package dedicato al trattamento dei file compressi con i diffusissimi formati standard ZIP e GZIP (in Java, in realtà, non esiste il concetto di annidamento tra package. Il fatto che questo sia collocato sotto java.util è solo un modo di classificazione per attitudine comune. Tuttavia, ad esempio, una variabile con visibilità package in java.util.zip non sarà accessibile alla classi di java.util e vice versa).
Ecco l'elenco delle classi e la loro funzione:
 
Adler32 Computa la somma di controllo (checksum) di un data stream con l'algoritmo Adler-32.
CheckedInputStream Un input stream che mantiene la checksum dei dati letti.
CheckedOutputStream Un output stream che mantiene la checksum dei dati scritti.
CRC32 Computa la somma di controllo (checksum) di un data stream con l'algoritmo CRC-32.
Deflater Fornisce il supporto generico per la compressione con ZLIB.
DeflaterOutputStream Implementa un output stream filter per comprimere dati nel formato "deflate".
GZIPInputStream Implementa un input stream filter per leggere dati compressi nel formato GZIP.
GZIPOutputStream Implementa un output stream filter per scrivere dati compressi nel formato GZIP.
Inflater Fornisce il supporto generico per la decompressione con ZLIB.
InflaterInputStream Implementa un input stream filter per decomprimere dati nel formato "deflate".
ZipEntry Rappresenta una entry in un file ZIP.
ZipFile Usata per leggere le entry di un file ZIP.
ZipInputStream Implementa un input stream filter per decomprimere dati nel formato ZIP.
ZipOutputStream Implementa un output stream filter per scrivere dati compressi nel formato ZIP.

 
 

Package java.util.jar 
I file JAR (Java ARchive), come i file CAB di ActiveX, sono un formato di distribuzione del software basato sullo standard ZIP. Un classico uso di un file JAR è la distribuzione di applet internamente a pagine HTML:

   <applet code=Animator.class 
      archive="classes.jar ,  images.jar ,  sounds.jar"
      width=460 height=160>
      <param name=foo value="bar">
    </applet>

Le seguenti classi sono derivate direttamente dalle corrispettive in java.util.zip:
 
JarEntry Rappresenta una entry in un file JAR. (Derivata da ZipEntry)
JarFile Usata per leggere le entry di un file JAR ed accedere all'eventuale file manifest (Derivata da ZipFile, può usare ogni file aperto con java.io.RandomAccessFile).
JarInputStream Implementa un input stream filter per decomprimere dati nel formato JAR.
JarOutputStream Implementa un output stream filter per scrivere dati compressi nel formato JAR.

Un file JAR può includere un file MANIFEST contenente una lista di file presenti nel medesimo archivio JAR. Non tutti i file nell'archivio debbono essere listati nel manifest; l'obbligo vale solo per i file che debbono essere segnati. Il file manifest stesso non deve essere listato. Il manifest può contenere informazioni di policy e di parametrizzazione dell'applicazione distribuita, segnature digitali per la validazione del contenuto, sezioni con informazioni per ogni file listato e, nel JDK 1.2, ha delle nuove feature per supportare le Java Extentions che, a loro volta, si basano sui file JAR.
Ecco le classi di java.util.jar per manipolare il file manifest:
 
Attributes Mappa i nomi degli attributi nel file Manifest ai volori delle stringhe ad essi associate.
Manifest Gestisce i nomi delle entri ed i loro attributi.

 
 

Conclusioni
C'è una cosa da dire sulle classi di java.util: meglio averle che non averle. Si possono rendere davvero utili in molte situazioni e, una volta assimilate come strumenti standard, possono agevolare anche le fasi di design.
La prossima volta concluderemo il ciclo dei package principali con java.net. Infatti si può ben dire che le funzionalità di rete caratterizzano la piattaforma Java al pari delle altre funzionalità di base fornite dai package java.lang, java.io e java.util.
Risorse

  1. Sotto la foresta di Java. Un articolo dell'autore sull'ottimizzazione del pattern Observer/Observable.
  2. JDK Documentation (JavaSoft website). Il sito ufficiale di documentazione per il JDK 1.2
  3. Changes and Release Notes for the JDK 1.2 Software. Cambiamenti e novità nell'ultima versione del JDK.
  4. Collections Framework
  5. Collection Class Changes in 1.2
  6. Essential Java Classes
  7. Internationalization

  8. Java Platform 1.2 API Specification

MokaByte rivista web su Java

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