LIP: 1 Marzo 2005 Classe Object e Vettori
Partiamo da Lesercizio dellultima 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
Sono metodi ereditati da tutti i sottotipi I metodi più utili sono: public String toString() { \\EFFECTS: restituisce una rappresentazione\\EFFECTS dell'oggetto this in forma di stringa. } public boolean equals(Object obj) \\EFFECTS :verifica se l'oggetto this è uguale a obj.\\EFFECTS Metodi Eredidati da Object
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 Concettualmente, l'invocazione.equals( ) 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. Daltra 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 equals
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 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) Vector
Costruttori e Metodi (alcuni) public Vector (){ \\EFFECTS\\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)
public int size (){ \\EFFECTS\\EFFECTS: restituisce il numero di elementi presenti nel vettore} public Object elementAt (int index){ \\EFFECTS\\EFFECTS: restituisce l'elemento di indice index } public void setElementAt (Object obj, int index){ \\EFFECTS\\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 simili a quelli dellarray
public void insertElementAt (Object obj, int index){ \\MODIFIES:this \\EFFECTS: inserisce obj nella posizione index e sposta tutti gli\\EFFECTS elementi, da index in poi, di una posizione} public void addElement (Object obj){ \\MODIFIES:this \\EFFECTS\\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} Metodi per rimuovere e aggiungere
Differenze con gli Arrays Rifate lesercizio 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 distanza 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 distanza 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 distanza nome)
Quindi quando usiamo i metodi della classe Vector bisogna usare cast opportuni Abbonato a= (Abbonato) persone.elementAt(1); a.nome…….// accesso alla variabile distanza (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 limplementazione del tipo di dato Elenco deve essere rifatto il programma che il testing? Dipende da come lo avete fatto (se accedeva alla variabile distanza persone di tipo array della classe Elenco..chiaramente non va piu bene) Per esempio si poteva verificare la dimensione corrente dellarray 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 distanza 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 lutente del tipo di dato (p.e. Elenco) puo accedere alle variabili distanza che lo implementano tutto il codice diventa dipendente dalla implementazione del tipo di dato Quando limplementazione del tipo di dato dovesse (come tipicamente sara) essere migliorata-modificata tutto il codice che la usa e che dipende dallimplementazione 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 lesercizio dellaltra 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 lordinamento 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 distanza 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 distanza 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
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++;} } Sottoclasse Il metodo removeElementAt elimina una posizione (non lascia una posizione null)
Tester
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