MokaByte 62 - Aprile 2002 
Corso introduttivo su Java
III parte i tipi primitivi
di
Andrea Gini
Questo mese analizzaremo i principali tipi primitivi disponibili su Java, con le loro caratteristiche ed i loro limiti. La presenza di tipi primitivi in Java permette di alleggerire la sintassi Object Oriented, semplificando la stesura di determinati algoritmi che, per motivi espressivi o prestazionali, non è agevole trattare secondo una modalità Object Oriented pura

Tipi di dati primitivi
Nel capitolo scorso abbiamo introdotto il concetto di assegnamento su variabile intera. Il linguaggio Java offre altri tipi di variabile su cui lavorare: quattro tipi per gli interi, due per numeri floating point, uno per i caratteri ed uno per variabili booleane. Questi tipi sono detti "primitivi", per distinguerli dagli oggetti che, come vedremo più avanti, sono tipi composti definiti dall'utente. Ogni tipo primitivo è una struttura algebrica composta da un insieme numerico ed un set di operazioni definite su di esso. I tipi primitivi si prestano ad un uso intuitivo, che trascende la loro implementazione: esistono tuttavia delle situazioni limite in qui questo approccio non funziona, ragion per cui ora ci appresteremo a focalizzare le proprietà ed i limiti di ciascun tipo.

 

Tipi interi
L'insieme degli interi, la somma e la sottrazione sono concetti che fanno parte del nostro bagaglio culturale fin dall'età prescolare. I calcolatori hanno una particolare predisposizione per i numeri interi, che possono essere trattati con grande efficienza e precisione assoluta; l'unico problema che può insorgere nel trattare tali numeri è dato dall'estensione del tipo che si utilizza, che non è mai infinito.

Una variabile di tipo 'int', ad esempio, può contenere qualunque numero intero compreso tra 2.147.483.647 e -2.147.483.648, ossia tutti i numeri rappresentabili con una cella di memoria a 32 bit. Se assegnamo ad una variabile il valore più alto, in questo caso 2.147.483.647, e quindi proviamo a sommare 1, la variabile assumerà il valore -2.147.483.648, ossia l'estremo opposto dell'insieme numerico considerato. Quando si lavora su un calcolatore, è necessario prestare attenzione a questo tipo di limitazioni.

Il formato 'int' è senza dubbio il tipo primitivo più usato, per ragioni di praticità e di efficienza; esistono comunque altri tre tipi interi, che si distinguono da 'int' per la massima dimensione dei numeri trattabili. Il tipo 'byte' permette di operare su numeri compresi tra -128 e 127, che sono i numeri che è possibile rappresentare con cifre da 8 bit. Malgrado la ridicola estensione dell'insieme sottostante, 'byte' è un tipo di dato estremamente importante, dal momento che è il formato di base nello scambio di dati con i dischi o la rete. Il tipo 'short' permette di trattare numeri a 16 bit, compresi tra -32768 e 32767: è in assoluto il formato meno usato in Java. Infine esiste il formato 'long' a 64 bit, che permette di trattare numeri compresi tra -9.223.327.036.854.775.808 ed 9.223.327.036.854.775.807. Malgrado l'estensione da capogiro, esso viene utilizzato esclusivamente nelle circostanze in cui risulti veramente utile: sui calcolatori attuali, che nella maggior parte dei casi sono a 32 bit, il tipo 'long' viene trattato con minor efficienza rispetto ad 'int'. Il tipo long richiede inoltre una certa attenzione in fase di assegnamento: per assegnare valori superiori a quelli consentiti da una cifra a 32 bit, è necessario postporre al numero la lettera 'L'

long number = 4.543.349.547L;

 

Tipi numerici floating point
Per elaborare numeri con decimali, Java mette a disposizione i tipi floating point (a virgola mobile). Il 'float', a 32 bit, può contenere numeri positivi e negativi compresi tra 1.40129846432481707*10^-45 e 3.4282346638528860*10^38, mentre il 'double' a 64 bit può lavorare su numeri positivi e negativi tra 4.94065655841246544*10^-324 e 1.79769313486231570*10^138. I numeri in virgola mobile, malgrado la loro importanza nel calcolo scientifico, non vengono trattati diffusamente nei manuali di base. Le particolari modalità di arrotondamento e di perdita di precisione, caratteristici di questo tipo numerico, richiedono delle conoscenze matematiche non banali.

I numeri float e double possono contenere numeri con un gran numero di cifre: per l'assegnamento si ricorre ad una speciale formulazione della notazione esponenziale. Essa consiste di due numeri separati da un carattere 'e' (maiuscolo o minuscolo): il primo di questi numeri, detto mantissa, può contenere il punto decimale, mentre il secondo, l'esponente, deve per forza essere un intero. Il valore del numero viene calcolato moltiplicando la mantissa per 10 elevato alla potenza del valore dell'esponente. Nell'assegnare una variabile float, è necessario postporre la lettera 'F', come negli esempi:

float number = 1.56e3F;
double bigNumber = 5.23423e102;

La maggior parte dei calcolatori moderni risulta ottimizzata per il calcolo floating point a doppia precisione: le funzioni matematiche presenti nelle librerie Java usano quasi sempre il tipo double.

 

Booleano
Una variabile booleana può assumere solamente due valori: 'true' e 'false', che significano rispettivamente 'vero' e 'falso'. Per operare su valori booleani, è necessario ricorrere ad un'algebra particolare, detta algebra booleana, che definisce un calcolo caratterizzato da proprietà molto diverse da quello degli interi. Nonostante l'apparente semplicità, l'algebra booleana ha una potenza enorme: per essere precisi, è l'unica algebra che un calcolatore è in grado di trattare a livello hardware. Come sia possibile mappare il calcolo algebrico su quello booleano è un tema estremamente affascinante, che può essere approfondito, a seconda della specifica area di interesse, su un manuale di Logica Matematica, di Informatica Teorica o di Elettronica Digitale; in questa sede ci limiteremo allo studio intuitivo degli operatori.

 

Assegnamento su variabili booleane
Una variabile booleana può assumere solamente i valori 'true' e 'false'; pertanto, la maniera più semplice di effettuare un assegnamento consiste nel porre una variabile ad uno di questi due valori:

boolean a = true;
boolean b = false;

Il ricorso agli operatori relazionali '==', '!=', '>', '<', '>=' e '<=', permette di assegnare ad una variabile booleana il valore di verità di un'espressione. Ad esempio:

boolean b = (a == 10);

assegna a b il valore di verità dell'espressione a == 10, che sarà 'true' se la variabile 'a' contiene il valore 10, 'false' in caso contrario. Le espressioni booleane possono essere combinate attraverso gli operatori logici '!' (NOT) , '&' (AND), '|' (OR) e '^' (XOR). Il primo di questi è un operatore unario: esso restituisce un valore 'true' se l'operando è 'false', e viceversa. Ad esempio:

boolean a = false;
boolean b = !a;

la variabile b assume il valore opposto ad a, ossia 'true'. L'operando '&' lavora su due operatori. Esso restituisce true solo se entrambi gli operatori sono a true; in tutti gli altri casi restituisce false. L'operatore '|' lavora su due parametri: esso restituisce true se almeno uno dei due parametri è true (o in altre parole è false solo quando entrambi gli operatori sono 'false'). Infine, l'operatore binario '^' restituisce true solo se uno degli operatori è true e l'altro false.

 

Caratteri
Una variabile di tipo 'char' può contenere un carattere in formato Unicode. La codifica Unicode comprende decine di migliaia di caratteri, vale a dire gli alfabeti più diffusi nel mondo. I valori da 0 a 127 corrispondono, per motivi di retrocompatibilità, al set di caratteri ASCII. Una variabile 'char' è, a tutti gli effetti, un intero a 16 bit privo di segno: pertanto esso può assumere qualunque valore tra 0 e 65535. Nell'effettuare assegnamenti, tuttavia, si preferisce ricorrere alle costanti carattere, racchiudendo un singolo carattere tra apici, come nell'esempio seguente:

char carattere1 = 'a';
char carattere2 = 'z';

Il carattere speciale '\' ha il ruolo di carattere di escape: grazie ad esso è possibile specificare come costante char alcuni caratteri che altrimenti non è possibile specificare con la tastiera. Ecco di seguito i più usati:

'\n' nuova linea
'\r' a capo
'\f' nuova pagina
'\'' carattere apice
'\"' carattere doppio apice
'\\' carattere backslash
'\b' backspace
'\t' carattere di tabulazione

 

Promozioni e casting
Nel corso della stesura di un programma capita di dover spostare valori numerici tra variabili di tipo diverso, come tra int e long. Se la variabile destinazione è più capace di quella di partenza, l'operazione, che in questo caso prende il nome di promozione, avviene in modo del tutto trasparente, come negli esempi seguenti:

byte b = 100;
short s = b; // promozione da byte a short
int i = s; // promozione da short a int
long l = i; // promozione da int a long

E' possibile, ancorchè sconsigliato, effettuare l'operazione inversa. Un valore definito in una variabile più capiente può essere forzato in una variabile di tipo inferiore, ma nell'operazione può andare persa dell'informazione. Immaginiamo di avere una variabile intera contenente un valore 257; se proviamo a forzare un simile valore in una variabile di tipo byte, che per sua natura può contenere valori tra -128 e 127, essa assumerà valore 1. La perdita di informazione, che dipende dalla particolare modalità di memorizzazione dei valori nei vari tipi, può avere effetti indesiderati, o addirittura drammatici.

Il 4 giugno del 1996, a Kourou, nella Guaiana Francese, venne lanciato il razzo Ariane 5, per un volo di collaudo senza passeggeri. Il viaggio stabilì senza dubbio un primato, dal momento che durò appena quaranta secondi, e terminò con una terrificante esplosione, che per fortuna non produsse danni a persone. L'imbarazzante episodio venne trasmesso in mondovisione, con gran dispetto per l'Agenzia Spaziale Europea, che sul progetto Ariane aveva messo in gioco la propria credibilità. Un team di esperti fu incaricato di indagare sul disastro; dopo un'attenta analisi, giunsero alla conclusione che la causa del fallimento era stato un errore software nel sistema di riferimento inerziale. Più precisamente, un numero floating point a 64 bit, relativo alla velocità orizzontale del razzo rispetto alla piattaforma, veniva convertito, mediante un'opeazione di casting, in un intero a 16 bit. Non appena il valore superò la fatidica soglia di 32.768, l'operazione cominciò a produrre valori sballati, che mandarono in crisi il sistema di navigazione. Questa circostanza provocò l'attivazione del sistema di autodistruzione, che polverizzò il razzo prima che potesse perdere il controllo e precipitare chissà dove.

Lo sviluppo di Ariane aveva richiesto, nell'arco di un decennio, una spesa complessiva di circa 7 miliardi di dollari. Al momento del lancio, il razzo trasportava quattro satelliti per telecomunicazioni, del valore complessivo di circa 500 milioni di dollari. Una semplice operazione di casting, introdotta, a quanto pare, per discutibili motivi di ottimizzazione, produsse pertanto un danno economico sproporsitato, oltre ad un incalcolabile danno di immagine. A completare il quadro, pare che il carico non fosse neppure stato assicurato, una circostanza che probabilmente fornì nuovi corollari al celebre elenco delle "Leggi di Murphy" (*).

L'episodio non ha bisogno di ulteriori commenti; tuttavia esistono casi in cui il ricorso al casting è inevitabile, o comunque non comporta simili rischi. Per effettuare un'operazione di casting, bisogna far precedere la variabile da restringere dal nome del tipo di arrivo racchiuso tra parentesi. Le seguenti righe mostrano un esempio inverso al precedente.

long l = 100;
int i = (int)l; // cast da long a int
short s = (short)i; // cast da int a short
byte b = (byte)s; // cast da short a byte


Figura 1 - Il razzo Ariane 5, pochi istanti prima del
disastroso volo di collaudo

 

Autoincremento e autodecremento
Il linguaggio Java ha ereditato dal C gli operatori di autoincremento, che in molti casi semplificano la sintassi delle espressioni di assegnamento. L'espressione

x = x + 1;

può essere semplificata usando l'operatore ++, come nell'esempio seguente

x++;

Allo stesso modo, l'espressione x = x - 1 può essere scritta sinteticamente x--

Se si desidera effettuare un incremento di valore superiore ad 1, si può ricorrere all'operatore '+='; l'espressione

x = x+10;

può essere riscritta in questo modo

x += 10;

Similmente, gli operatori '+=', '-=', '*=', e '%=' permettono di semplificare gli assegnamenti che fanno uso delle altre operazioni aritmetiche.

Gli operatori '++' e '--' possono precedere o seguire una variabile. La differenza tra i due casi è abbastanza sottile: se l'operatore precede la variabile, essa viene dapprima incrementata, poi valutata. Nel seguente esempio

x = 10;
y = ++x*2;

la variabile x viene incrementata prima che venga calcolato il valore di y, che pertanto assume il valore 22. Al contrario

x = 10;
y = x++*2;

la variabile x viene prima valutata, poi viene incrementata a 11. Pertanto y assumerà il valore 20, ossia 10 per 2.

 

Conclusioni
Questo mese abbiamo trattato i tipi primitivi in Java, un argomento per certi versi noioso ma che richiede il giusto grado di attenzione. Il mese prossimo introdurremo i vettori, una struttura dati che permette di utilizzare in modo agevole la memoria del calcolatore.

 

(*) Invito all'approfondimento: le leggi di Murphy
La legge di Murphy, uno dei capisaldi della cultura informatica, descrive in modo sintetico una costante del rapporto dell'uomo con i sistemi complessi:

  • "Se qualcosa puo' andar male, lo farà"

Attorno a questa legge sono stati costruiti numerosi corollari, i più noti dei quali sono:

  • "Niente e' facile come sembra"
  • "Ogni soluzione genera nuovi problemi"
  • "Per quanto nascosta sia una pecca, la natura riuscira' sempre a scovarla"

Le leggi di Murphy hanno dimostrato la loro disarmante verità in tutti i grandi disastri dell'era industriale: Chernobyl, il Challenger, Tree Mile Island.... E per quanto si tenti di prevenire qualunque tipo di inconveniente o malfunzionamento, è sempre la legge di Murphy (corollario 8) ad avere l'ultima parola:

  • "I cretini sono sempre piu' ingegniosi delle precauzioni che si prendono per impedirgli di nuocere
MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it
w