Corso Java - Quarta lezione: package, tipi di dato, variabili e operatori

Giunti alla quarta puntata del nostro excursus sulla programmazione ad oggetti e, in particolare, sul linguaggio Java, è arrivato il momento di puntualizzare una serie di concetti dei quali finora non abbiamo fornito esauriente spiegazione.
Corso Java - Quarta lezione: package, tipi di dato, variabili e operatori

Giunti alla quarta puntata del nostro excursus sulla programmazione ad oggetti e, in particolare, sul linguaggio Java, è arrivato il momento di puntualizzare una serie di concetti dei quali finora non abbiamo fornito esauriente spiegazione. Questi concetti spaziano su un largo fronte della conoscenza del linguaggio e sono da considerarsi assolutamente fondamentali. Solo dopo questo appuntamento potremmo dire di aver posto realmente le basi per una comprensione del linguaggio.

I package e loro organizzazione

In ogni linguaggio di programmazione esistono una serie di funzioni standard scritte in supporto al linguaggio vero e proprio e, ovviamente, Java non fa eccezione. Oramai le funzioni delle librerie standard sono talmente numerose ed ampie che si può affermare senza ombra di dubbio come risulti cosa praticamente impossibile conoscerle in maniera esaustiva. In Java queste librerie sono poste, ovviamente, sotto forma di classi e sono fornite tutte insieme direttamente da Sun: esse sono, infatti, comprese nel “kit” che normalmente utilizziamo per programmare Java e che tutti voi dovreste avere scaricato ed installato. Queste classi, tuttavia, non sono fornite singolarmente ma raggruppate in modo omogeneo in base alle loro funzioni. Questi raggruppamenti prendono il nome di package. Ad esempio, molte classi che implementano funzioni matematiche, si trovano all’interno del package java.math. Per poter accedere ad ogni classe e metodo della libreria standard del linguaggio, si utilizza l’istruzione import tramite la quale è possibile sia “importare” tutte le classi di un determinato package sia una specifica classe, come nei seguenti due esempi:
import java.util.*;
import java.util.Random;

Notate che adottando la prima o la seconda versione, potrete comunque utilizzare la classe Random. Potete trovare un gran numero di informazioni circa tutti i package e tutte le classi all’interno della documentazione ufficiale distribuita dalla Sun e liberamente prelevabile dal web. Fino ad ora abbiamo utilizzato funzioni della libreria standard (per esempio println()) senza mai ricorrere ad istruzioni import. Per quale motivo? Il motivo è che in Java qualunque istruzione scriviate come import il compilatore inserisce in automatico l’istruzione

import java.lang.*;

in cui viene importato il package che contiene le più comuni funzioni di libreria del linguaggio. Non avrete quindi mai bisogno di scrivere una riga come questa nei vostri programmi. Di seguito si riporta un elenco dei package più utilizzati ed una loro breve descrizione (l’ordine è da considerarsi assolutamente casuale):

java.io: contiene tutte le classi per eseguire le operazioni di I/O (input/output) su file e non solo. Si tratta di un package molto ampio è studiato per essere molto “portabile”, infatti in Java è possibile considerare il trasferimento dati tra due host remoti alla stregua del trasferimento dati su localhost. Questa è una delle caratteristiche che rendono Java particolarmente adatto alla programmazione di rete. Ci occuperemo di alcune di queste classi più avanti.
java.util: contiene classi per gestire date, orari, timer, calendari, numeri pseudo casuali e molto altro. Si tratta senza dubbio di uno dei package più utilizzati in assoluto nonché uno dei più vasti. Contiene anche classi per la gestione di strutture dati semplici, come liste semplicemente concatenate di varia nature e liste ad accesso sequenziale (Vector) di cui ci occuperemo fra poco. Fanno parte inoltre di questo package le classi per gestire i file zip e i file jar.
java.applet: contiene essenzialmente la classe omonima, uno strumento fondamentale per svilupparne altre. Ci occuperemo esclusivamente delle applet nella prossima lezione, e le useremo come esempi, laddove possibile, nelle prossime lezioni del corso.
java.awt: contiene una serie di classi utili per la realizzazione delle interfacce grafiche, quali elementi delle stesse, gestori di layout, gestori di contorni, finestre di vario tipo e molto altro ancora.
javax.swing: si tratta essenzialmente di una naturale “evoluzione” delle awt, comprende delle classi più performanti e semplici da utilizzare delle awt praticamente in tutti i settori della creazione delle interfacce. Questo package è scritto interamente in Java.
java.net: contiene una serie di classi estremamente duttili per la programmazione orientata alle reti (networking programming) soprattutto ad alto livello. Costituisce uno dei punti di forza del linguaggio. In questo corso ci occuperemo più avanti di alcune classi di questo package ma il lettore sappia che sono stati scritti libri interi inerenti esclusivamente questo argomento.
java.sql: contiene le classi utilizzate per l’interfacciamo a database di diversa natura. E’ bene sapere da subito che la forza di Java in questo settore consiste nell’avere una serie di classi e metodi che possono essere utilizzati su tutti i più comuni tipi di database. Questo è possibile grazie alla divisione che Java opera tra queste classi e i driver: in sostanza per accedere ad un database di tipo X avremmo bisogno esclusivamente di installare l’opportuno driver (vedi lezioni successive) senza dover apprendere altre funzioni del linguaggio (cosa particolarmente scomoda in altri linguaggi, ad esempio C++).

I package, a loro volta, possono contenere altri package, quindi non dovreste stupirvi se in qualche esempio doveste trovare:

import java.swing.events.*;

Ovviamente niente impedisce al programmatore che lo desideri di costruire egli stesso un package: Java per fare questo mette a disposizione l’istruzione package. Ci occuperemo di questo quando parleremo di I/O poiché l’utilizzo di package e import in modo sbagliato può comportare dei problemi a quel livello.

Tipi di dato, variabili e costanti

E’ certamente chiaro a tutti che durante l’esecuzione di un programma è assolutamente necessario accedere ad un certo numero di dati ed è altrettanto chiaro che una certa quantità di altri dati verrà generata durante l’esecuzione. Queste informazioni vanno ovviamente debitamente conservate durante l’esecuzione del programma in apposite aree di memoria, e questa è una pratica che spetta in parte all’ambiente a run-time ed in parte al programmatore, nel senso che egli dovrà indicare quanta memoria riservare e cosa metterci, mentre sarà l’interprete a decidere il dove. Noi non ci occuperemo minimamente di come l’interprete si occupa della memoria, perché questo esula dai nostri scopi e perché richiede buone conoscenze di architettura degli elaboratori e di strutture dati, a volte, non elementari. Viceversa forniremo adesso le nozioni fondamentali per il programmatore.

Non tutte le informazioni che desideriamo conservare durante l’esecuzione di un programma richiedono lo stesso spazio in memoria centrale: è evidente che la semplice memorizzazione di un carattere è ben diversa dalla memorizzazione, ad esempio, di un oggetto istanza della classe telecomando o televisore di cui abbiamo parlato nell’introduzione (ved. questa pagina, in proposito). Di conseguenza sarà anche diverso lo spazio necessario per il loro “stoccaggio”. Questo spazio dipenderà chiaramente dal tipo di dato che desideriamo memorizzare e tutte le informazioni che noi desideriamo registrare possono essere ricondotte fondamentalmente a due categorie diverse:
1. tipi di dato predefinito
2. oggetti
In qualunque caso, comunque, noi accediamo a questi dati attraverso un nome simbolico: nella maggior parte dei casi si tratta di una variabile poiché il suo valore potrà mutare nel corso dell’esecuzione del programma, mentre utilizziamo una costante se vogliamo che non sia possibile effettuare dei cambiamenti. Deve essere chiaro che variabili e costanti sono semplicemente nomi simbolici per indicare certe locazioni di memoria (non abbiamo bisogno di sapere quali, provvederanno il compilatore prima e l’interprete poi a gestire il problema) tramite le quali ci è possibile accedere al valore memorizzato in quelle locazioni. La dichiarazione di una variabile avviene con la sintassi seguente:
Tipo nome_variabile ;

per esempio:

int conta ;

dichiara una variabile di tipo intero a cui assegniamo il nome conta.

I tipi di dato predefinito riguardano la memorizzazione di informazioni semplici quali numeri, caratteri e valori booleani. Di seguito riassumiamo i più utilizzati in una semplice tabella:

Tipo Nome Valori Descrizione
Numero intero int Da -2147483648 a 2147483647  
Numero reale float Da -3.4E+38 (7 decimali) a 3.4E+38 (7 decimali) Precisione singola
Numero reale double Da -1.7E+308 (15 decimali) a 1.7E+308 (15 decimali) Precisione doppia, doppio utilizzo di memoria
Booleano bool Può assumere solo i valori true e false Rappresenta una condizione, che può essere solo vera o falsa
Carattere char Può memorizzare tutti i valori del set Unicode  

In realtà esistono anche altri tipi di dati predefinito, mai noi ci accontenteremo di questi. E’ una buona regola di programmazione utilizzare il tipo double solo nel caso ciò si renda assolutamente necessario, ad esempio in calcoli in cui è richiesta una notevole precisione. Normalmente la precisione del tipo float è più che sufficiente. A proposito di alcuni di questi tipi è necessario fare alcune precisazioni:
– quando assegniamo un valore dato di tipo float dobbiamo far seguire al valore che stiamo assegnando al lettera f, ad esempio float radice = 1.4142f;
– quando assegniamo un valore dato di tipo double dobbiamo far seguire al valore che stiamo assegnando la lettera d.
– per distinguere i caratteri Java, come molti altri linguaggi, utilizza l’apice semplice ovvero char a = 'c' assegna ad una variabile di nome a il valore c.
– Sun scelse il set di caratteri Unicode per poter rendere Java il più indipendente possibile dalla piattaforma. Infatti Unicode è una codifica che utilizza 16 bit in luogo degli 8 bit della codifica ASCII quindi consente la rappresentazione di 65536 caratteri, di cui ASCII rappresenta essenzialmente un sottoinsieme. Questo permette di utilizzare lo stesso set di caratteri (e quindi lo stesso codice!) anche in paesi dove siano utilizzati altri simboli (si pensi alla Russia ad esempio). Pur tuttavia a causa delle richieste per l’assegnazione dei code che giungono molto numerose da tutto il mondo, anche Unicode potrebbe essere sostituito da una nuova rappresentazione.
In Java utilizziamo la stessa sintassi sia per dichiarare un tipo di dati predefinito sia per dichiarare un oggetto istanza di una certa classe, sia essa della libreria standard o no. Nonostante questo esiste una sostanziale differenza, di cui dobbiamo occuparci, nell’uso dei tipi di dato predefinito e delle classi. Fino ad ora abbiamo utilizzato i tipi di dato predefinito senza usare l’operatore new che era invece indispensabile nella classe dado esaminata nella seconda lezione (ved. questa pagina in proposito). Giunti a questo punto siamo in grado di capirne i motivi: possiamo usare i tipi di dato predefinito senza l’operatore new mentre dobbiamo utilizzarlo per istanziare oggetti dalle classi. Si tratta di una regola del linguaggio alla quale bisogna prestare attenzione: a voler essere rigorosi il paradigma della programmazione ad oggetti dovrebbe impedire che in un linguaggio a oggetti esistano questi “compromessi” in quanto non vi è alcuna necessità. Purtuttavia si deve considerare che per un linguaggio essere orientato ad oggetti non è una condizione che si possa risolvere con un “dentro o fuori”: esistono, per fortuna, delle opportune vie di mezzo e Java ne fa pienamente parte, pur essendo uno dei linguaggi più aderenti al paradigma in assoluto, ad esempio più del tanto decantato C++. In effetti i tipi di dato predefiniti sono accessibili in questa forma esclusivamente per brevità poiché vengono utilizzati molto spesso, ma esistono anche le classi che li implementano, dette classi wrapper: per cui se qualcuno sente la necessità di essere dogmatico in Java può tranquillamente farlo. Ci occuperemo più avanti delle classi wrapper che si dimostrano eccezionalmente utili nella stesura e gestione di interfaccie utente (GUI: Graphics User Interface).
L’operatore new, come già detto, deve essere seguito da un costruttore dichiarato all’interno della classe in questione, di cui abbiamo parlato nella lezione 2.
Un discorso differente deve essere fatto per le stringhe: esse in Java non sono considerate tipi predefiniti, nonostante ciò è lecito dichiarare una stringa in ciascuno dei seguenti modi:

String sito = new String("Ilsoftware.it");
String sito = "Ilsoftware.it";

In questo caso, però, si tratta di una eccezione che i creatori del linguaggio hanno deciso di inserire poiché le stringhe sono, tra i tipi non predefinti, di gran lunga quello più utilizzato. Nonostante ciò, chi scrive è fermamente convinto che l’unico risultato ottenuto sia l’aver “inquinato” il linguaggio quindi durante tutte le lezioni e gli esempi utilizzeremo la prima dicitura. Le stringhe in Java devono sempre essere racchiuse tra doppi apici.
Tutte queste regole vengono applicate anche per le costanti, che si ottengono semplicemente facendo precedere la parola riservata final al tipo di dato.

Gli operatori

Quando noi scriviamo una qualunque espressione essa è composta di operandi e operatori. Abbiamo visto a cosa corrispondo gli operandi in Java, occupiamoci adesso degli operatori.
Java implementa tutti gli operatori più comuni presenti nella maggior parte dei linguaggi del mondo. Essi si possono dividere come segue (anche in questo caso elenchiamo solo i principali):

1. operatori aritmetici + – / * %
L’utilizzo di questi operatori non richiede alcuna spiegazione supplementare poiché è vincolato dalle leggi dell’aritmetica e dell’algebra esattamente come le conosciamo. L’operatore % calcola il resto di una divisione tra interi.

2. incrementi e decrementi ++ —
Si tratta di due operatori molto utilizzati per eseguire l’incremento di una variabile che abbiamo già usato in precedenza, ad esempio nei cicli for.
variabile++; //incrementa di un'unità la variabile
variabile--; //decrementa di un'unità la variabile

Tuttavia vi è una precisazione da fare. Vi è una notevole differenza tra le seguenti due righe di codice, pur essendo entrambe corrette:
++variabile;
variabile++;

Nel primo caso la variabile viene incrementata e solamente dopo il controllo torna al programma, nel secondo caso avviene il contrario. Questa può sembrare una precisazione inutile, ma non lo è. Per esempio si consideri il seguente frammento di codice:

int quiz = 5;
System.out.println(quiz++);
System.out.println(++quiz);

Il risultato della sua esecuzione non è quello che ci si potrebbe aspettare ( 6 7 ) bensì
5
7

poiché nella prima println il valore viene stampato prima dell’incremento, nella seconda solo successivamente.

3. L’operatore di concatenazione tra stringhe
La concatenazione tra stringhe avviene in Java tramite l’operatore + . Questo operatore si presenta, evidentemente, identico alla somma aritmetica ma svolge dei compiti completamente diversi: in Java si tratta di una eccezione poiché non è ammesso l’overloading degli operatori, pratica molto usuale, ad esempio, in C++. Se uno dei due operandi non è egli stesso una stringa viene convertito automaticamente. Tuttavia se vogliamo concatenare come stringhe due numeri dobbiamo frapporre un carattere di stringa affinché il compilatore sappia che si tratta della concatenazione di stringhe e non della somma aritmetica. Si veda in proposito il listato prelevabile da qui in cui ho inserito tutte le situazioni più comuni.

4. Operatori logici
Gli operatori logici AND e OR in Java sono rappresentati rispettivamente da && e ¦¦ . Il loro utilizzo, nelle espressioni logiche, è vincolato dalle leggi naturali che noi tutti conosciamo. Ad ogni modo da qui in avanti ci capiterà spesso di utilizzarli, cosicché anche coloro i quali nutrono dei dubbi dovrebbero riuscire a chiarirli grazie alle spiegazioni che daremo in itinere. Analogo discorso vale per l’operatore NOT.

5. Operatori di assegnamento
In Java oltre al normale operatore di assegnamento (=) esistono una serie di altri operatori che svolgono un duplice compito: un assegnamento ed un operatore tradizionale. Essi sono costruiti facendo precedere al segno uguale (=) l’operatore aritmetico. Per esempio, sono assolutamente equivalenti le seguenti righe di codice:

a += 1;
a = a+ 1;

Ovviamente come nell’aritmetica e nella logica che conosciamo, anche in Java gli operatori hanno una ben precisa scala di precedenza: precisamente, nel nostro elenco, quelli elencati per primi hanno priorità maggiore; in questo caso l’ordine è stato rigoroso. Ciò nonostante il modo migliore per sciogliere i dubbi quando ci si trova davanti a problemi di priorità rimane l’inserimento di parentesi tonde anche sovrabbondanti.

Si chiude qui il nostro quarto appuntamento nel quale ci auguriamo di essere riusciti a chiarire una serie di punti cardine del linguaggio. Nel prossimo articolo vedremo in particolare alcuni dei metodi più comuni per organizzare organicamente i dati e cominceremo a parlare di applet.

Ti consigliamo anche

Link copiato negli appunti