LIP: 1 Marzo 2005 Classe Object e Vettori Partiamo da • L’esercizio dell’ultima esercitazione realizzato tramite array • Vedremo come si puo’ fare in modo piu’ efficiente usando un nuovo tipo di dato (Vector) Classe Object • La classe Object è la superclasse, diretta o indiretta, di ciascuna classe in Java, quindi Object è supertipo di qualsiasi tipo (che definisce oggetti). • Grazie al meccanismo dell'ereditarietà i suoi metodi sono ereditati da tutti i tipi (che definiscono oggetti). ad una variabile di tipo Object possiamo assegnare oggetti di qualsiasi tipo (principio di sostituzione) Attenzione • I tipi primitivi int, boolean, double non sono sottotipi di Object, non sono oggetti (vedi la differenza nella semantica di FP) • String, Integer sono esempi di tipi primitivi sottotipi di Object • Abbonato, Elenco sono esempi di tipi non primitivi sottotipi di Object Metodi Eredidati da Object •Sono metodi ereditati da tutti i sottotipi •I metodi più utili sono: { \\EFFECTS: restituisce una rappresentazione dell'oggetto this in forma di stringa. } public String toString() public boolean equals(Object obj) \\EFFECTS :verifica se l'oggetto this è uguale a obj. Commenti a toString() •La definizione del metodo nella classe Object restituisce una stringa che contiene il nome della classe dell'oggetto su cui il metodo è invocato ed una rappresentazione esadecimale del codice hash dell'oggetto (indirizzo in memoria dell'oggetto). •Questo accade perché la classe Object non può conoscere la struttura dell'oggetto. •Il metodo ereditato e’ poco utile. •Il metodo deve quindi essere sovrascritto in ogni classe che lo usa per ottenere un risultato significativo. Tipicamente, di un oggetto si vogliono mostrare (nella stringa restituita) i valori delle variabili d'istanza o comunque una informazione significativa che descriva lo stato interno Commenti ad equals •Concettualmente, l'invocazione <obj1>.equals(<obj2>) del metodo equals dovrebbe restituire true quando il contenuto dei due oggetti è uguale (non il riferimento, come per l'operatore ==). •L'esempio tipico è il confronto tra stringhe. •D’altra parte il metodo equals della classe Object, e’ implementato non potendo fare alcuna assunzione sulla struttura interna degli oggetti su cui viene invocato (utilizza semplicemente l'operatore == per confrontarli.) •Deve quindi essere sovrascritto in modo opportuno nel sottotipo (overriding) a seconda delle caretteristiche degli oggetti •Per il tipo String il metodo e’ gia’ ridefinito in modo primitivo Metodo equals •Notiamo che il parametro del metodo equals ha tipo Object •Lo possiamo chiamare su oggetti di ogni tipo (come String ) proprio grazie al principio di sostituzione •Il confronto con == va bene solo per testare se un oggetto ha riferimento null Tipo Vector • La classe java.util.Vector definisce degli oggetti, chiamati vettori (Vector), che consentono di rappresentare sequenze di oggetti di lunghezza variabile. • Simili agli array a parte il fatto che -la dimensione di un vettore può variare durante l'esecuzione di un programma - non vanno creati per un tipo prefissato, le posizioni del Vector hanno un tipo generico Object -quindi possono contenere oggetti di ogni tipo anche tra loro disomogenei (tipo String o Integer) Costruttori e Metodi (alcuni) public Vector (){ \\EFFECTS: crea un vettore vuoto} • Notate che a differenza che per gli arrays non e’ necessario fissare al momento della creazione la dimensione • Ci sono anche altri costruttori tipo quelli degli arrays che permettono di creare un vettore vuoto ma con una certa capacita’ (dato numero di posizioni allocate ma vuote). Serve solo per avere implementazioni piu’ o meno efficienti (per ora lo ignoriamo) Metodi simili a quelli dell’array public int size (){ \\EFFECTS: restituisce il numero di elementi presenti nel vettore} public Object elementAt (int index){ \\EFFECTS: restituisce l'elemento di indice index } public void setElementAt (Object obj, int index){ \\EFFECTS: sostituisce obj all'oggetto della posizione index} • Se index e’ fuori dal size del vettore viene sollevata una eccezione come per gli arrays Metodi per rimuovere e aggiungere public void insertElementAt (Object obj, int index){ \\MODIFIES:this \\EFFECTS: inserisce obj nella posizione index e sposta tutti gli elementi, da index in poi, di una posizione} public void addElement (Object obj){ \\MODIFIES:this \\EFFECTS: aggiunge una posizione alla fine che contiene obj } public void removeElementAt (int index){ \\MODIFIES:this \\EFFECTS: rimuove l'oggetto presente nella posizione index e sposta all'indietro di una posizione tutti gli elementi successivi a quello rimosso} public boolean removeElement (Object obj){ \\MODIFIES:this \\EFFECTS: rimuove la prima occorrenza dell'oggetto obj se presente restituendo true,oppure restituisce false} Differenze con gli Arrays • Rifate l’esercizio dell’ Elenco di abbonati usando un Vector invece di un array • Lo stato interno di un oggetto di tipo Elenco e’ descritto da un Vector di Abbonato (invece che da un array) public Vector persone; // variabile d’istanza • Essendo Abbonato sottotipo di Object possiamo usare un Vector per memorizzare abbonati (per esempio per aggiungere) persone.addElement(new Abbonato(12,15)) Attenzione •quando usiamo i metodi della classe Vector restituiscono valori di tipo Object. Cosa succede se vogliamo leggere il nome del primo Abbonato del Vector? // accesso alla variabile d’istanza di Abbonato che memorizza il nome int n= persone.elementAt(1).nome; •Il compilatore non puo’ sapere quale tipo di valori sono correntemente memorizzati nella prima posizione del Vector, quindi guarda il tipo restituito dal metodo •elementAt e’ un metodo che restituisce un valore di tipo Object, quindi rileva un errore di tipo (Object non ha una variabile d’istanza nome) Quindi •quando usiamo i metodi della classe Vector bisogna usare cast opportuni Abbonato a= (Abbonato) persone.elementAt(1); a.nome…….// accesso alla variabile d’istanza (senza il cast darebbe un errore) •In questo modo possiamo aggirare il problema della differenza tra tipo effettivo (Abbonato) e tipo apparente (Object) Commenti • Nel momento in cui cambiate l’implementazione del tipo di dato Elenco deve essere rifatto il programma che il testing? • Dipende da come lo avete fatto (se accedeva alla variabile d’istanza persone di tipo array della classe Elenco ..chiaramente non va piu’ bene) • Per esempio si poteva verificare la dimensione corrente dell’array per testare i metodi definiti Elenco e=new Elenco(); System.out.println(e.persone.length); e.inserisci(12); System.out.println(e.persone.length); Commenti • • • • • • Notiamo che il metodo di testing puo’ accedere alla variabile d’istanza persona perche’ questa e’ public Questo suggerisce che non e’ una buona pratica di programmazione rendere visibili le variabili che implementano lo stato interno di un tipo di dato Infatti se l’utente del tipo di dato (p.e. Elenco) puo’ accedere alle variabili d’istanza che lo implementano tutto il codice diventa dipendente dalla implementazione del tipo di dato Quando l’implementazione del tipo di dato dovesse (come tipicamente sara’) essere migliorata-modificata tutto il codice che la usa e che dipende dall’implementazione dovra’ essere riscritto Come succede in questo caso col metodo di testing E’ fondamentale invece (come vedremo) rendere le varie parti indipendenti dalla loro implementazione interna usando il piu’ possibile specificatori di accesso private Sulla implementazione di Elenco • • • Per quelli che se la sentono (tipo che hanno gia’ finito l’esercizio dell’altra volta) potete fare una implementazione un po’ piu’ efficiente Usando un Vector in cui gli Abbonati sono mantenuti in modo ordinato rispetto al loro nome (come in un elenco del telefono vero) In tale caso i metodi di ricerca, inserimento e rimozione devono essere fatti in modo da mantenere l’ordinamento e da sfruttarlo Per Esempio Se gli Abbonati sono ordinati in base al nome per cercare un certo Abbonato non dovremo sempre scorrere tutto il vettore Classe Elenco 1 import java.util.*; import java.io.*; public class Elenco{ public Vector persone; // variabile d’istanza public static int numero=1; // costruttore public Elenco(){ persone=new Vector(); } } •Lo stato interno di un oggetto di tipo Elenco e’ descritto da un Vector persone •Le variabili d’istanza devono essere inizializzate dal costruttore (invocato per creare un nuovo oggetto) •persone viene inizializzato al vettore vuoto tramite il costruttore di Vector new Vector(); •Se non creiamo il vector il riferimento rimane null non possiamo farci nulla! Classe Elenco 2 \\metodi public int cercanum(int s){ for (int j=0; j< persone.length;j++){ Abbonato a =(Abbonato) persone.elementAt(j); if (a.nome==s) {return a.num;}} return 0; } public void inserisci(int s){ persone.addElement(new Abbonato(s,numero)); numero=numero+1; } } •Quando leggiamo dal Vector dobbiamo fare il cast (anche se sappiamo che contiene oggetti di tipo Abbonato, il compilatore non lo puo’ sapere). •Il metodo addElement aggiunge in fondo al vettore (crea una nuova posizione) •Per aggiungere un nuovo Abbonato dobbiamo fare new Abbonato() per creare il nuovo oggetto Sottoclasse public class Elencoext extends Elenco{ public Elencoext(){ } public void rimuovi(int s){ int j=0; while (j< persone.size()){ Abbonato a= (Abbonato) persone.elementAt(j); if (a.nome==s) {persone.removeElementAt(j);} else {j++;} } } } •Il metodo removeElementAt elimina una posizione (non lascia una posizione null) Tester public class Tester{ public static void main(String[] args){ Elenco e=new Elenco(); e.inserisci(112); int k=e.cercanum(112); System.out.println(k); int h=e.cercanum(113); System.out.println(h); e.inserisci(113); int t=e.cercanum(113); System.out.println(t); Elencoext y= new Elencoext(); y.inserisci(112); y.inserisci(112); int r=y.cercanum(112); System.out.println(r); System.out.println(y.persone.size()); y.rimuovi(112); System.out.println(y.persone.size()); } } Commenti • La gestione di dati modificabili e’ notevolmente piu’ semplice rispetto agli arrays (automaticamente vengono aggiunte o tolte posizioni) • Per contro bisogna fare attenzione ai sottotipi ed usare cast per risolvere i problemi del compilatore