MokaByte 64 - Giugno 2002 
Corso introduttivo su Java
IV parte: gli array
di
Adrea Gini
Dopo aver introdotto i tipi primitivi, è giunto il momento di analizzare in profondità un altro strumento importantissimo in un linguaggio di programmazione: i vettori. I vettori permettono di gestire in maniera relativamente semplice grosse porzioni di memoria, e di effettuare su di essa calcoli ripetitivi. Vedremo anche alcuni esempi di uso dei vettor, come la memorizzazione di tabelle o il calcolo della media aritmetica di un insieme di valori.

Array
Molto spesso nei programmi si ha l'esigenza di manipolare un gruppo di variabili dello stesso tipo che contengono valori tra loro correlati. Immaginiamo di voler scrivere un programma che calcoli la media delle temperature giornaliere; se vogliamo prendere in considerazione 6 misurazioni all'ora, una ogni 10 minuti, ci serviranno ben 144 variabili.

int temp1 = 15; // ore 0.00
int temp2 = 16; // ore 0.10
int temp3 = 16; // ore 0.20
int temp4 = 16; // ore 0.30
....
int temp144 = 14; // ore 11.50

Oltre alla scarsa praticità di dover dichiarare 144 variabili, non esiste nessun modo pratico per effettuare dei calcoli che abbraccino tutto l'insieme dei valori: l'unico modo per calcolare la media sarebbe quello di realizzare una gigantesca espressione artimetica del tipo

int media = (temp1 + temp2 + temp3 + ....+ temp143 + temp144) / 144;

Per questo tipo di operazioni, è utile ricorrere ad un array, uno strumento concettualmente simile ad una tabella, che accomuna sotto un unico nome un insieme di variabili dello stesso tipo:

int[] temp = new int[144];

La creazione e l'utilizzo degli Array presenta delle differenze rispetto all'utlizzo delle normali variabili. Vediamole di seguito una ad una.

 

Dichiarazione di array
La dichiarazione di un array ha una sintassi un po' più complessa della dichiarazione di variabile semplice. Come per le variabili semplici dobbiamo indicare un tipo ed un nome, con la differenza che, dopo aver specificato il tipo, è necessario postporre una coppia di parentesi quadre.

int[] vettoreDiInteri;

 

Assegnamento
La variabile 'vettoreDiInteri' appena dichiarata non è un vettore, ma solamente un reference ad un vettore. Il vettore vero e proprio è un oggetto di memoria separato, che deve essere definito opportunamente. Prima di essere inizializzata, essa ha il valore 'null', un valore costante che indica che la variabile non referenzia alcun vettore. Per creare un vettore dobbiamo ricorrere alla parola riservata 'new', come nell'esempio seguente:

vettoreDiInteri = new int[10];

Il valore specificato tra parentesi quadre è la dimensione del vettore: è possibile specificare un qualunque valore intero positivo. Il vettore appena creato è formato da dieci elementi, inizializzati a zero.


Figura 1 - Un vettore è un oggetto di memoria composto da un
certo numero di elementi, ognuno dei quali può contenere un valore

 

Dereferenziazione
La dereferenziazione è l'operazione che permette di assegnare un valore ad un elemento del vettore. Per dereferenziare un elemento di un vettore, occorre specificare il nome del vettore seguito dal numero dell'elemento tra parentesi quadre:

vettoreDiInteri[1] = 10;

Gli elementi di un vettore si contano a partire da zero: pertanto se vogliamo assegnare il valore 27 al decimo elemento del vettore dobbiamo scrivere:

vettoreDiInteri[9] = 27;

 


Figura 2 - Lo stesso vettore dopo aver
dereferenziato il secondo e il decimo elemento

 

Differenza tra Assegnamento e Dereferenziazione
Bisogna fare molta attenzione a capire la differenza tra dereferenziazione ed assegnamento. La dereferenziazione è un'operazione indiretta: essa non opera sulla variabile, ma sull'oggetto di memoria puntato da essa. Se noi creiamo una nuovo ref ed eseguiamo un assegnamento pari al ref di un vettore già esistente, ci troviamo nella situazione in cui due variabili puntano allo stesso vettore, come nell'esempio seguente, esemplificato dalla Figura 3:

int[] vettoreDiInteri2;
vettoreDiInteri2 = vettoreDiInteri;


Figura 3
- Due variabili che fanno riferimento allo stesso vettore.

In una situazione come questa le operazioni vettoreDiInteri[5] = 10 e vettoreDiInteri[5] = 10 avranno entrambe il risultato di porre a 10 l'elemento numero 5 dell'unico vettore puntato dalle due variabili. Per procedere alla effettiva copia di un vettore, è necessario dapprima creare un vettore delle stesse dimensioni, quindi copiare uno ad uno gli elementi del primo nel secondo. Questa operazione può essere eseguita con un ciclo while, come si vede nell'esempio seguente:

// crea un vettore e lo inizializza
int[] v1 = new int[5];
v1[0] = 10;
v1[1] = 12;
v1[2] = 14;
v1[3] = 16;
v1[4] = 18;
// crea un vettore della stessa dimensione di v1
int[] v2 = new int[5];

int i = 0;
while(i < v1.lengt) {
  // copia il valore della i-esima cella
  // di v1 nella i-esima cella di v2
  
v2[i] = v1[i];
}


Inizializzazione automatica di un vettore
Un vettore può essere inizializzato con una serie di valori, in modo simile a come si può fare con le variabili. L'istruzione:

int[] vettore = {10,12,14,16,18};

equivale alla sequenza:

int[] vettore = new int[5];
vettore[0] = 10;
vettore[1] = 12;
vettore[2] = 14;
vettore[3] = 16;
vettore[4] = 18;

 

Lunghezza di un vettore
I vettori creati con l'operatore 'new' hanno esattamente la dimensione specificata nella dichiarazione. Gli indici sono numerati a partire da 0, per cui l'ultimo elemento avrà indice pari alla dimensione del vettore meno uno. Ad esempio, in un vettore da 10 elementi, gli indici sono compresi tra 0 e 9. Il programmatore può essere interessato a conoscere la dimensione di un array a runtime: per questo scopo ogni vettore dispone di un'apposita variabile 'lenght', accessibile attraverso l'operatore '.':

int vettoreDiInteri[] = new int[10];
System.out.print("La dimensione del vettore è ");
System.out.println(vettoreDiInteri.length);

 

Un esempio di manipolazione di vettori
Il vettore è uno strumento potentissimo, che permette di lavorare su porzioni di memoria anche molto grandi usando un numero ridotto di istruzioni. I cicli while permettono di valutare uno ad uno gli elementi di un array, e di effettuare qualche tipo di operazione su di essi. Per calcolare la media dei valori contenuti in un ipotetico vettore 'vettoreDiInteri', posso utilizzare un frammento di codice di questo tipo:

int i = 0;
int somma = 0;
int media = 0;

while(i<vettoreDiInteri.lengt) {
  somma = somma + vettoreDiInteri[i];
  i++;
}
media = somma / sommaDiInteri.length;

Il seguente esempio permette di togliersi una soddisfazione: quella di scrivere un programma che sfrutti una gran parte della memoria del nostro computer. Per consumare una grande quantità di memoria con un solo programma, esistono quattro approcci di base:

  1. Scrivere un programma enorme che effettui calcoli banali
  2. Scrivere un programma piccolo che esegua calcoli complessi
  3. Scrivere un programma banale che esegua calcoli banali su un grande numero di valori
  4. Utilizzare un mix delle tecniche precedenti.

La disponibilità di memoria ram a basso costo permette oggi di assemblare PC con quantità tali di memoria RAM da rendere sempre più difficile raggiungere l'obbiettivo con la prima di queste tecniche. La seconda, tipica dei programmi di manipolazione di immagini, non è adatta ad un corso base di programmazione. Fortunatamente la terza di queste tecniche risulta essere perfetta in questa circostanza.

Un vettore di byte di dimensione 1024 occupa esattamente un K-byte di memoria. Un vettore di int della stessa dimensione ne occupa 4, dal momento che un int è grande 4 byte. Se voglio creare un vettore di interi da 64 megabyte, posso calcolarne la dimensione moltiplicando 64 per 1048576 (pari a 1024 al quadrato), e dividendo per quattro. Una volta creato un simile vettore, lo posso riempire di valori casuali scelti tra 0 e 10000; infine posso calcolare la somma di tutti i valori, e la relativa media aritmetica. Si noti l'uso di una variabile di tipo 'long' per memorizzare la somma di tutti i numeri: è facile comprendere che una variabile intera non sarebbe sufficiente.

public class MemoryConsumer {

  public static void main(String argv[]) {

    long sum = 0;
    long average = 0;

    // calcola la dimensione del vettore.
    // Se si dispone di poca memoria, ridurre
    // il valore della variabile megabytes.
    int megaBytes = 64;
    int dim = megaBytes * 1048576 / 4;
    int[] bigArray = new int[dim];


    // riempie il vettore di valori casuali
    int i = 0;
    while ( i < bigArray.length ) {
      bigArray[i] = (int)(32000 * Math.random());
      i++;
    }

    // calcola la somma di tutti i valori
    i = 0;
    while ( i < bigArray.length ) {
     sum = sum + bigArray[i];
     i++;
    }

    // calcola la media
    average = sum / bigArray.length;
  
    // stampa i risultati
    System.out.print("La somma dei numeri presenti nel vettore è");
    System.out.println(sum);
    System.out.print("La media della somma dei numeri presenti nel                       vettore è ");
    System.out.println(average);
  }
}

Il programma deve essere salvato, come di consueto, in un file dal nome "MemoryConsumer.java"; per compilarlo bisogna digitare il comando

javac MemoryConsumer.java

mentre per eseguirlo bisogna ricorrere all'istruzione

java MemoryConsumer

Dopo qualche istante, il programma stamperà un output del tipo:


La somma di tutti i numeri presenti nel vettore è 239999200405
La media della somma di tutti i numeri presenti nel vettore è 15999

Per poter eseguire questo programma, è necessario disporre di un computer con almeno 256 MB di ram. Se non si dispone di memoria sufficiente, il computer segnalerà un errore:

java.lang.OutOfMemoryError
   <<no stack trace available>>
Exception in thread "main"

In questo caso si provi a diminuire il valore della variabile 'megaBytes', portandolo ad esempio a 32, quindi si ricompili ed esegua.

 

Vettori multidimensionali
Il linguaggio Java consente di creare vettori bi-dimensionali, ricorrendo ad una sintassi del tipo:

int i[][] = new int[10][15];

I vettori bidimensionali sono concettualmente simili ad una tabella rettangolare, dotata di righe e colonne. Il seguente programma riprende l'idea delle tabelline pitagoriche, creando dapprima una rappresentazione in un vettore bidimensionale, quindi stampando quest'ultimo sullo schermo:

public class Tabelline2 {

  public static void main(String argv[]) {
    int[][] tabellina = new int[11][11];

    // crea la tabellina in un vettore bidimensionale
    int i = 0;
    int j = 0;
    while(i <= 10) {
      while(j <= 10) {
        int prodotto = i*j;
        tabellina[i][j] = prodotto;
        j = j + 1;
      }
      i = i + 1;
      j = 0;
    }

    // stampa il contenuto del vettore
    i = 0;
    j = 0;
    while(i <= 10) {
      while(j <= 10) {
        int prodotto = i*j;
        System.out.print(tabellina[i][j]);
        System.out.print("\t");
        j = j + 1;
      }
      i = i + 1;
      j = 0;
      System.out.println();
    }
  } 
}

Allo stesso modo è possibile definire vettori con un numero qualunque di dimensioni:

int v1[][][] = new int[10][15][5];
int v2[][][][] = new int[10][15][12][5];

Tali strutture, in ogni caso, risultano decisamente poco utilizzate.

 

Vettori incompleti
I vettori n-dimensionali vengono implementati in Java come array di array. Questa scelta implementativa rende possibile la realizzazione di tabelle non rettangolari, come nell'esempio seguente:

// crea un vettore con una componente incompleta
int tabella[][] = new int[5][];

tabella[0] = new int[3];
tabella[1] = new int[2];
tabella[2] = new int[5];
tabella[3] = new int[2];
tabella[4] = new int[6];

 


Figura 4
- Rappresentazione in memoria di un vettore non rettangolare

Anche in questo caso siamo in presenza di un costrutto scarsamente utilizzato, che tuttavia vale la pena conoscere.

 

Inizializzazione automatica di un vettore multidimensionale
Un vettore può essere inizializzato con una serie di valori, in modo simile a come si può fare con i vettori semplici. Ovviamente è necessario ricorrere ad un costrutto un po' più complesso, che tenga conto della particolare struttura di questi vettori. La seguente istruzione, ad esempio, crea un vettore a tre componenti in verticale, in cui la prima riga ha tre colonne, la seconda due e la terza quattro, e contemporaneamente inizializza gli elementi con i valori specificati, come si può vedere in figura 5:

int[] vettore = { { 10,12,14},{16,18},{20,22,24,26}};


Figura 5
- Un altro esempio di vettore non rettangolare

 

Conclusioni
Questo mese abbiamo trattato i vettori, una classica struttura dati comune a quasi tutti i linguaggi di programmazione. Oltre all'uso elementare, abbiamo analizzato alcune particolarità dei vettori in Java, come la possibilità di definire vettori multidimensionali, vettori incompleti e vettori non rettangolari. Il mese prossimo cominceremo a guardare i costrutti di programmazione di Java non ancora introdotti.

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