I vettori


Le classi java.util.Vector e java.util.ArrayList definiscono degli oggetti, chiamati vettori, che consentono di rappresentare sequenze di oggetti di lunghezza variabile

Ciascun oggetto in un vettore ha un numero intero, detto indice, che ne indica la posizione nel vettore. Grazie all'indice è possibile accedere indipendentemente a ciascun elemento della sequenza. L'accesso ad una posizione inesistente provoca un errore (viene lanciata un'eccezione). 

I vettori sono simili agli array. Le differenze principali sono tre:

  • la dimensione di un vettore può variare durante l'esecuzione di un programma;
  • il tipo base di un vettore deve essere una classe, ovvero NON può essere un tipo primitivo (int, double, ...);
  • ci sono molti metodi a disposizione per inserire/rimuovere/cercare elementi.

Al momento della dichiarazione o creazione di un vettore, il tipo base degli elementi del vettore va indicato tra parentesi angolate dopo il nome della classe: Vector e ArrayList sono classi generiche, un concetto che approfondiremo tra breve.

Nell'esempio che segue, il primo comando costruisce un vettore di stringhe (inizialmente vuoto) e lo assegna alla variabile vet; il secondo crea invece un vettore di BankAccount.

      ArrayList<String> vet = new ArrayList<String>();
 
      Vector<BankAccount> bank = new Vector<BankAccount>();



Breve cenno sulle classi generiche

 
A partire dalla versione 5.0, sono stati introdotti in Java i tipi generici, cioè classi e interfacce con parametri di tipo, che possono essere istanziati con arbitrarie classi Java.

Vediamo un primo esempio di tipo generico: anche l'interfaccia Comparable che avevamo visto è in realtà parametrica rispetto al tipo degli elementi che possono essere confrontati.

public interface Comparable<T>{

    int compareTo(T o);

}

Comparable<T> è un'interfaccia generica, poiché ha un parametro di tipo T.

<T> è una dichiarazione di parametro formale di tipo. Dopo la dichiarazione, T può essere usato nel codice della classe come se fosse un tipo noto.

L'uso corretto di interfacce o classi generiche prevede l'opportuna istanziazione dei parametri di tipo, come nelle seguenti dichiarazioni di classi.

public class Metri implements Comparable<Metri>{
    private int metri;

    public Metri(int metri){
        this.metri = metri;
    }

    public int compareTo(Metri m){
        return metri - m.metri;
    }
}

public class Secondi implements Comparable<Secondi>{
    private int secondi;

    public Secondi(int secondi){
        this.secondi = secondi;
    }

    public int compareTo(Secondi s){
        return secondi - s.secondi;
    }
}

public class TestConfrontoErrato{

    public static void main (String [] args){
        Metri distanza = new Metri(135);
        Secondi durata = new Secondi(120);
        int comp = distanza.compareTo(durata);
                         // errore di compilazione
    }
}

Si noti che viene segnalato un errore a tempo di compilazione, invece che durante l'esecuzione. L'errore segnalato è


> javac TestConfrontoErrato.java
TestConfrontoErrato.java:6: compareTo(Metri) in Metri cannot be applied to (Secondi)
        int comp = distanza.compareTo(durata);
                           ^
1 error



Implementazione di vettori

 
L'implementazione dei vettori è basata sugli array. Al momento della creazione di un vettore, alla variabile di istanza elementData viene assegnato un array di oggetti la cui dimensione dipende dal costruttore utilizzato:

      Object[] elementData = new Object[initialCapacity];
                                       // default 10

Nota: indipendentemente dal parametro di tipo del vettore, l'array usato nell'implementazione è di oggetti della classe Object. Tutti i controlli di consistenza sui generici sono demandati all'analisi statica del compilatore.

Successivamente, se la capacità dell'array non è più sufficiente, viene creato un nuovo array più grande nel quale vengono copiati tutti gli elementi del vecchio.

      Object[] vettoreBis = new Object[2*elementData.length]; 
      System.arraycopy(elementData,0,vettoreBis,0,elementData.length); 
      elementData = vettoreBis; 



Differenze tra Vector<E> e ArrayList<E>

 
Nel seguito, usando la sintassi Java 5.0, indicheremo con Vector<E> e ArrayList<E> le classi generiche che definiscono i vettori quando il tipo base non è specificato.

Le classi generiche Vector<E> e ArrayList<E> sono sostanzialmente equivalenti, ma:

  • I metodi di Vector<E> sono sincronizzati, mentre quelli di ArrayList<E> non lo sono. Quindi se il programma è concorrente (cioè usa il multi-threading di Java) è opportuno usare Vector<E>, altrimenti converrebbe ArrayList<E> perché leggermente più efficiente.

  • Vector<E> fornisce, con opportuni metodi e costruttori, un controllo maggiore sulla capacità, cioè la dimensione dell'array soggiacente.

  • Per motivi storici, Vector<E> fornisce più metodi con nomi diversi per manipolare gli elementi di un vettore.

Descrizione completa delle classi: Vector [ Sun] e ArrayList [ Sun].





Classi Vector<E> e ArrayList<E>: costruttori


I costruttori di Vector<E> permettono di specificare la capacità iniziale del vettore (initialCapacity) e il valore da usare per aumentare la  capacità (capacityIncrement) quando necessario. Se (capacityIncrement == 0), il nuovo array avrà capacità doppia rispetto all'attuale.

    /* crea un vettore vuoto, con i parametri specificati */
    Vector (int initialCapacity, int capacityIncrement)

    /* per default, capacityIncrement==0 */
    Vector (int initialCapacity)
    
    /* per default, initialCapacity==10 e capacityIncrement==0 */
    Vector ()

I costruttori di ArrayList<E> permettono di specificare solo la capacità iniziale del vettore.

    /* crea un vettore vuoto con la capacita' iniziale indicata */
    ArrayList (int initialCapacity)
    
    /* per default, initialCapacity==10 */
    ArrayList ()




Vector<E> e ArrayList<E>: metodi


I metodi che operano sui vettori consentono tra l'altro di:
  • leggere o sostituire l'elemento in una certa posizione (operazioni analoghe a quelle sugli array);

  • aggiungere uno o più elementi, in varie posizioni;
  • eliminare uno o più elementi, in varie posizioni;
  • cercare un oggetto nel vettore;
  • trasformare il vettore in un array.
Vediamo alcuni metodi di Vector<String> o ArrayList<String>:

    /* restituisce il numero di elementi contenuti nel vettore */
    int size ()
                                               
    /* restituisce l'elemento di indice index */
    String get (int index)
    
    /* sostituisce obj all'oggetto di posizione index */
    String set (int  index, String obj)
    
    /* inserisce obj nella posizione index e sposta tutti gli
       elementi, da index in poi, di una posizione */
    void add (int index, String obj)
    
    /* aggiunge obj dopo l'ultimo elemento; restituisce true  */
    boolean add (String obj)
                                               
    /* rimuove l'oggetto presente nella posizione index e sposta
       all'indietro di una posizione tutti gli elementi successivi
       a quello rimosso */
    void remove (int index)
                                               
    /* rimuove l'oggetto obj se presente restituendo true,
       oppure restituisce false */
    boolean remove (Object obj)

    /* restituisce la prima posizione dell'oggetto 
       'elem' nel vettore, -1 se non esiste */
    int indexOf (Object elem) 

    /* la stringa restituita ha la forma "[el-1, el-2, ..., el-n]" */
    String toString () 

    /* restituisce true se l'argomento è un vettore con 
    lo stesso numero di elementi (size), e gli elementi 
    corrispondenti sono equivalenti */
    boolean equals (Object obj) 

    /*  Solo in Vector<E>, non in ArrayList<E>: 
    restituisce la capacita' del vettore */
    int capacity ()

Esempio: EsempioVettore.java

import java.util.Vector;

public class EsempioVettore {
    public static void main(String[] args) {
        Vector<String> v = new Vector<String>(3);
        System.out.println("contenuto di v: "+v);
        System.out.println("capacita' di v: "+v.capacity());
        System.out.println("n.elementi di v: "+v.size());
        v.add("a");
        v.add("b");
        v.add("d");
        v.add("e");
        v.add(2,"c");
        System.out.println("contenuto di v: "+v);
        System.out.println("capacita' di v: "+v.capacity());
        System.out.println("n.elementi di v: "+v.size());
        for (int i=0; i < v.size(); i++)
            System.out.println("elemento "+ i+": "+v.get(i));
        String first = v.get(0);
        System.out.println("primo elemento di v: "+ first);
        System.out.println("ultimo elemento di v: "+v.get(v.size()-1));
    }
}
        
                              


 




Vettori e array a confronto


Sottolineiamo alcune differenze sintattiche nell'uso di un vettore al posto di un array.

Si noti che il for generalizzato può essere usato anche per scandire gli elementi di un vettore: la sintassi è identica a quella vista per scandire un array.

Con array Con vettori
// Nessun import richiesto import java.util.Vector; // oppure
import java.util.ArrayList;
String[] elenco = new String[5]; ArrayList<String> elenco =
        new ArrayList<String>();

for (int i=0 ; i<elenco.length ; i++){
  elenco[i] = "Numero "+i;
}
 

for (int i=0 ; i<5 ; i++) {
  elenco.add("Numero "+i);
}
 
System.out.println(elenco[2]); System.out.println(elenco.get(2));

for (int i=0 ; i<elenco.length ; i++){
  System.out.println(elenco[i]);
}
 

for (int i=0 ; i<elenco.size() ; i++){
  System.out.println(elenco.get(i));
}
 
// for generalizzato
for (String str : elenco){
  System.out.println(str);
}
 
// for generalizzato
for (String str : elenco){
  System.out.println(str);
}
 
elenco[2] = "Mario Rossi"; elenco.set(2,"Mario Rossi");
?
// rimuove l'elemento in posizione 2
// ricompattando il vettore

elenco.remove(2);
?
// inserisce l'elemento in posizione 2
// spostando verso destra i successivi

elenco.add(2,"Mario Verdi");


Poiché gli array hanno dimensione fissa, non è ovvio cosa significhi cancellare o inserire un elemento in un array. Queste operazioni hanno però un chiaro significato se l'array è gestito come un array parzialmente riempito.
 
 
 
 
 
 




Array parzialmente riempiti


In molte situazioni è utile memorizzare dei dati in un array, anche se il numero di dati può variare. In questo caso si può usare un array parzialmente riempito (si veda anche l'Horstmann).

  • Occorre usare una variabile ausiliaria per ricordare quanti elementi sono stati inseriti nell'array. Il nome della variabile di solito è <nomeArray>Size.

  • Ogni ciclo di scansione dell'array deve terminare a <nomeArray>Size - 1, e non a <nomeArray>.length - 1, perché visiterebbe elementi dell'array non significativi.

  • Nel caso l'array sia pieno e si inserisce un ulteriore elemento, si può allocare un array più grande copiandoci dentro gli elementi, come nella realizzazione di Vector

Per copiare in modo efficiente (parte di) un array in un altro si può usare il metodo statico

    System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

Questo metodo può essere usato anche per spostare degli elementi all'interno di uno stesso array, fornendolo sia come primo che come terzo argomento.




Vettori e tipi primitivi


Come anticipato, il tipo base di un vettore non può essere un tipo primitivo: quindi è illegale ad esempio dichiarare una variabile di tipo ArrayList<int> oppure Vector<double>.

Java fornisce tuttavia le cosiddette classi involucro o classi contenitore, che possono essere usate come tipo base di vettori in cui si vogliano memorizzare numeri interi, numeri in virgola mobile, o valori booleani.

Le istanze di una classe involucro sono usate per incapsulare valori primitivi all'interno di un oggetto. Questo permette di trattare in maniera omogenea dati di tipi primitivi ed oggetti. La conversione tra un tipo di dati primitivo e la corrispondente classe involucro è automatica, grazie al meccanismo di autoboxing/unboxing introdotto in Java 5.0.




Classi Involucro


Ci sono classi involucro per tutti i tipi primitivi. Normalmente hanno un nome simile al corrispondente tipo primitivo, ma iniziano con maiuscola:

    Boolean, Byte, Character, Double, Float, Integer, Long, Short
Ciascuna di queste classi contiene (oltre agli ovvi metodi toString() e equals()):
  • un costruttore con parametro di tipo primitivo (ad esempio Character(char c) o Integer(int value));
  • un costruttore con parametro String (ad esempio Integer(String s));
  • un metodo tipoValue() che produce il valore di tipo primitivo corrispondente (ad esempio intValue());

  • eventuali metodi e costanti statiche di utilità generale.
Vediamo ad esempio la classe Integer: [ Sun ]

 




Esempio: la classe Integer


La classe Integer è la classe involucro per numeri interi. Grazie all'autoboxing/unboxing è possibile assegnare valori di tipo int a variabili di tipo Integer e viceversa.

    Integer numero = 30;

    int count = numero;

Nel primo comando viene invocato implicitamente un costruttore che crea un oggetto di tipo Integer a partire da un valore di tipo int. Nel secondo caso viene invocato implicitamente sull'oggetto di tipo Integer il metodo intValue(). Quindi i due comandi visti sono equivalenti ai seguenti:

    Integer numero = new Integer(30);

    int count = numero.intValue();


La classe Integer fornisce diversi altri membri. Tra questi ricordiamo le costanti Integer.MAX_VALUE (2147483647) e Integer.MIN_VALUE (-2147483648), che sono il più grande e il più piccolo valore rappresentabile in una variabile di tipo int, e il metodo statico parseInt(), che permette di convertire una stringa in un intero:

    int miliardo = Integer.parseInt("1000000000");

Un metodo analogo è il metodo parseDouble() della classe Double, che converte una stringa nel numero in virgola mobile che rappresenta.

Come abbiamo visto, questi metodi lanciano un'eccezione NumberFormatException se la stringa su cui sono invocati non rappresenta un numero.  





Esempio di uso di Vector<Integer>


Vediamo ora un semplice esempio di manipolazione di un vettore di interi, ProvaVectorInteger.java. Si osservi che il tipo base del vettore è Integer.

import java.util.Vector;

public class ProvaVectorInteger{
    public static void main(String [] args){
        System.out.println("Dammi 5 numeri interi: ");

        Vector<Integer> vet = new Vector<Integer>();
        for (int i = 0; i < 5; i++){
            int n = Input.readInt();
            vet.add(n);
        }
        System.out.println("Ecco i 5 numeri che mi hai dato: ");
        for (int num : vet)
            System.out.println(num);
   }
}

Si osservi che, grazie alla conversione automatica tra Integer e int, il codice è simile a quello che avremmo scritto usando un array di int al posto del vettore. A livello di esecuzione del codice, tuttavia, le differenze sono notevoli. Infatti mentre un assegnamento come arr[i] = n comporta semplicemente la copia del valore della variabile n nella variabile arr[i], l'esecuzione del comando vet.add(n) causa la creazione di un nuovo oggetto di tipo Integer e l'invocazione del metodo add.