Tipo predefinito che rappresenta insiemi di Object
Posso mettere un riferimento a un Point in una variabile Object
Object o=new Point(12,3);
è quasi tutto come se l'oggetto fosse un Object
Unica eccezione:
se invoco un metodo di Object che è ridefinito in Point, viene invocato il metodo definito in Point
Cosa succede se faccio queste cose:
Funziona tutto come se l'oggetto fosse
un Object,
tranne quando si invoca un
metodo che sta sia in Object che in
Point
Dopo un cast Point p=(Point) o:
variabile Point con dentro un riferimento
a un Point:
funziona tutto come al solito
Array, liste e insiemi
Hanno in comune: una variabile rappresenta un insieme di oggetti
Differenze
Rappresenta insiemi non ordinati di elementi
Non esiste un primo, secondo, ecc. elemento
Creazione di un insieme vuoto:
HashSet h; h=new HashSet();
Nota: non c'è un ordine degli elementi
Quindi, non si può dire ``inserisci in prima posizione'' oppure ``trova l'elemento in ultima posizione''
Creare un insieme che contiene i numeri interi da -5 a 5
Dato che negli HashSet ci posso solo mettere oggetti, devo usare il tipo Integer
Ciclo da -5 a 5
Ogni valore lo metto in un oggetto Integer
import java.util.*; class Valori { public static void main(String args[]) { HashSet s; s=new HashSet(); int i; Integer v; for(i=-5; i<=5; i++) { v=new Integer(i); s.add(v); } System.out.println(s); } }
L'istruzione println(s) converte l'insieme in una stringa, e poi la stampa
Viene stampato:
[5, -1, 4, -2, 3, -3, 2, -4, 1, -5, 0]
Gli elementi non sono nell'ordine in cui li ho messi!
Per definizione, in un insieme non devo tenere conto dell'ordine in cui gli elementi vengono inseriti
{1, 2, 3} e {2, 1, 3} sono lo stesso insieme
Sono due stringhe che rappresentano lo stesso insieme
Il metodo toString degli insiemi dà una stringa fra quelle che rappresentano l'insieme
Scrivere un metodo statico che verifica se un insieme contiene due interi consecutivi fra -10 e 10
static boolean consecutivi(HashSet s) { ... }
Non abbiamo ancora visto come fare cicli su tutti gli elementi
Facciamo un ciclo da -10 a 9
A ogni passo, verifichiamo la presenza
static boolean consecutivi(HashSet s) { int i; Integer v, t; for(i=-10; i<=9; i++) { v=new Integer(i); t=new Integer(i+1); if(s.contains(v) && s.contains(t)) return true; } return false; }
Se viene passato l'insieme:
[3, 1, 8, 0]
Cosa restituisce il metodo?
true
L'insieme [3, 1, 8, 0] contiene i numeri 0 e 1 che sono consecuitivi
Si usa un meccanismo che esiste anche sulle liste
Si crea un iteratore, che è un oggetto che serve a fare cicli sugli insiemi
Questo ciclo stampa gli elementi dell'insieme s
Iterator it; it=s.iterator(); while(it.hasNext()) { v=(Integer) it.next(); System.out.println(v); }
Si può fare la stessa cosa anche se s è una LinkedList
Crea un ordine fra gli elementi dell'insieme, e permette di passare da un elemento a quello dopo
it=s.iterator();
Crea un iteratore sull'insieme s
it.next();
Trova il prossimo elemento dell'insieme
it.hasNext()
Ritorna true se ci sono altri elementi nell'insieme
Per fare la stessa cosa su tutti gli elementi di una lista o di un array:
for(i=0; i<l.size(); i++) { elemento=l.get(i); opera su elemento }
Per liste e insiemi, si può fare il ciclo con un iteratore:
Iterator it; it=s.iterator(); while(it.hasNext()) { elemento=it.next(); opera su elemento }
Per gli insiemi, non esiste ordinamento, quindi non c'è il metodo get.
L'unico modo di fare cicli per gli insiemi è quello di usare un iteratore
Dato un insieme di punti, stampare solo quelli che stanno nel primo quadrante
static void primoQuadrante(HashSet s) { ... }
per ogni elemento dell'insieme se e' nel primo quadrante, stampalo
Nelle liste, la parte "per ogni elemento" si può implementare con un iteratore oppure con il ciclo solito
Per gli insiemi, posso solo usare l'iteratore
Creo un iteratore, e lo uso nel ciclo
Per vedere le coordinate, devo fare il cast
static void primoQuadrante(HashSet s) { Point p; Iterator i; i=s.iterator(); while(i.hasNext()) { p=(Point) i.next(); if(p.x>=0 && p.y>=0) System.out.println(p); } }
Verificare se un insieme di interi contiene due numeri consecutivi
static boolean consecutivi(HashSet s) { ... }
Il metodo di prima lo faceva solo per interi da -10 a 10
Ora lo voglio per tutti gli interi
Non si può fare un ciclo su tutti i possibili valori interi
Si fa un ciclo su tutti gli elementi dell'insieme
Per ogni elemento, se c'è anche quello più grande di uno, si ritorna true
Se questo non è mai vero, si ritorna false
static boolean consecutivi(HashSet s) { int i; Integer v, t; Iterator it; it=s.iterator(); while(it.hasNext()) { v=(Integer) it.next(); i=v.intValue(); t=new Integer(i+1); if(s.contains(t)) return true; } return false; }
Attenzione a non confondere s e it, e usare i metodi sull'oggetto sbagliato:
Se:
L'iteratore diventa non più valido (non si può più usare)
Quindi, dopo la modifica non si può più invocare it.hasNext() oppure it.next()
Eliminare tutti gli elementi di valore pari da un insieme
Faccio il solito ciclo
Quando trovo un elemento pari, lo elimino
static void eliminaPari(HashSet s) { Integer v; Iterator i; i=s.iterator(); while(i.hasNext()) { v=(Integer) i.next(); if(v.intValue()%2==0) s.remove(v); } }
Perchè non funziona?
Dopo una modifica a un insieme, tutti i suoi iteratori diventano non validi
Invocare un metodo su un iteratore non valido produce un errore
Nel metodo di prima, dopo aver fatto s.remove(v), si proseguiva il ciclo, per cui venivano fatte le invocazioni i.hasNext() e i.next(), che producono un errore
Ogni volta che viene invocato s.iterator() viene creato un nuovo iteratore
Questo iteratore riparte dall'inizio
La soluzione: ogni volta che trovo un elemento pari, lo elimino e ricreo l'iteratore
static void eliminaPari(HashSet s) { Integer v; Iterator i; i=s.iterator(); while(i.hasNext()) { v=(Integer) i.next(); if(v.intValue()%2==0) { s.remove(v); i=s.iterator(); } } }
Richiede due metodi
Intanto: implementare questi due metodi
Solita cosa: ciclo su tutti gli elementi
static boolean contienePari(HashSet s) { Integer v; Iterator i; i=s.iterator(); while(i.hasNext()) { v=(Integer) i.next(); if(v.intValue()%2==0) return true; } return false; }
Solito ciclo: quando trovo un elemento pari lo elimino
Dopo l'eliminazione, esco
static void eliminaPrimoPari(HashSet s) { Integer v; Iterator i; i=s.iterator(); while(i.hasNext()) { v=(Integer) i.next(); if(v.intValue()%2==0) { s.remove(v); break; } } }
Attenzione: se invoco un metodo su i dopo una modifica, viene dato errore in esecuzione
Verificare che non vengano fatte, per sbaglio, invocazioni di metodo sugli iteratori dopo una modifica
Domanda: ora che ho realizzato i due metodi,
come faccio il metodo di eliminazione di tutti
gli elementi?
Devo seguire questo algoritmo:
elimina primo elemento pari elimina primo elemento pari elimina primo elemento pari elimina primo elemento pari ...
Quando non ho più elementi pari, mi posso anche fermare
Per eliminare tutti gli elementi pari da un insieme:
Finchè ci sono elementi pari, elimino il primo
static void eliminaPari(HashSet s) { while(contienePari(s)) eliminaPrimoPari(s); }
Si possono fare insiemi di Point, String, Integer, ecc.
Se creo una nuova classe, come Studente, questa deve avere due metodi:
Non ci interessa sapere a cosa serve
Questo qui sotto funziona:
class NomeClasse { ... public int hashCode() { return 0; } }
Usate sempre questo metodo, esattamente come è scritto
Nella classe ci deve stare sia equals che hashCode!
Non viene dato nessun errore,
nè in compilazione, nè in esecuzione
Cosa cambia?
Senza hashCode, il metodo contains verifica se c'è un oggetto == a quello passato, invece di verificare se c'è un oggetto equals
Ci vuole anche il metodo equals
class Studente { String nome; int esami; double media; public String toString() { return "["+this.nome+" "+ this.esami+" "+ this.media+"]"; } public boolean equals(Object o) { if(o==null) return false; if(this.getClass()!=o.getClass()) return false; Studente s; s=(Studente) o; if(s.nome==null) { if(this.nome!=null) return false; } else if(!s.nome.equals(this.nome)) return false; return ((s.esami==this.esami) && (s.media==this.media)); } public int hashCode() { return 0; } }
Aggiungere a Studente un metodo statico leggiStudente che legge uno studente da un BufferedReader (ogni componente sta su una linea)
Scrivere un metodo statico leggiFile della classe Studente che legge un intero file, il cui nome viene passato come argomento, e resituisce un oggetto HashSet che contiene gli studenti letti
Dato che serve un HashSet, devo avere i metodi equals e hashCode:
class Studente { String nome; int esami; double media; public String toString() { return "["+this.nome+" "+ this.esami+" "+ this.media+"]"; } public boolean equals(Object o) { if(o==null) return false; if(this.getClass()!=o.getClass()) return false; Studente s; s=(Studente) o; return ((s.nome.equals(this.nome)) && (s.esami==this.esami) && (s.media==this.media)); } public int hashCode() { return 0; } // altri metodi }
Il metodo toString va messo sempre, perchè è comodo
Il metodo prende come paramentro un BufferedReader
Deve solo leggere tre linee, e convertirle se necessario
Dato che uso i file, serve l'import, e devo fare throws IOException
import java.io.*; class Studente { // componenti e metodi static Studente leggiStudente (BufferedReader b) throws IOException { Studente t; t=new Studente(); String s; s=b.readLine(); if(s==null) return null; t.nome=s; s=b.readLine(); if(s==null) return null; t.esami=Integer.parseInt(s); s=b.readLine(); if(s==null) return null; t.media=Double.parseDouble(s); return t; }
Sembra lungo, ma fa solo questo: legge tre linee, per ogni linea controlla se vale null (e in questo caso, ritorna null), la converte se necessario, e la mette nell'oggetto creato
Il metodo leggiStudente ritorna null quando il file è finito
Il metodo/programma che lo invoca, può vedere il valore di ritorno per capire se il file è finito
Esempio: il metodo leggiFile (prox pagina) usa questo fatto per capire quando non ci sono più oggetti da leggere
Qui il parametro è il nome di un file
Quindi, il FileReader e il BufferedReader vanno creati
Dato che sono operazioni su file, bisogna mettere throws IOException
Dato che serve il tipo HashSet, devo anche fare import java.util.*;
import java.io.*; import java.util.*; class Studente { // componenti e altri metodi static HashSet leggiFile(String nomefile) throws IOException { FileReader r=new FileReader(nomefile); BufferedReader b=new BufferedReader(r); HashSet h; h=new HashSet(); Studente s; while(true) { s=leggiStudente(b); if(s==null) break; h.add(s); } return h; } }
Si tratta soltanto di leggere oggetti Studente da file, e metterli nell'insieme, fino a che non si legge un oggetto null
Leggere il file roma1.txt in un HashSet
Stampare l'insieme (un oggetto per linea)
Contare quanti studenti hanno dato meno di cinque esami
Stampare i dati dello studente Totti, se si trova nell'insieme
Anche se non è richiesto: conviene fare un metodo per ognuna delle cose da fare
Metodo di stampa:
static void stampa(HashSet s) { Iterator i; i=s.iterator(); while(i.hasNext()) System.out.println(i.next()); }
Metodo che trova gli studenti con meno di un certo numero di esami:
static int contaMeno(HashSet s, int quanti) { Iterator i; i=s.iterator(); int conta=0; Studente q; while(i.hasNext()) { q=(Studente) i.next(); if(q.esami<quanti) conta++; } return conta; }
Perchè il parametro quanti?
È bene cercare di fare metodi generali, perchè si possono riusare
In questo caso: l'unica cosa che cambiava era che: if(q.esami<5) diventa if(q.esami<quanti)
Se la differenza è piccola conviene cercare di scrivere metodi più generali
Se la generalità comporta una complicazione, meglio il metodo non generale
Faccio un ciclo di scansione
Se il nome dello studente è Totti, stampo tutto lo studente
static void stampaStudente(HashSet s, String nome) { Iterator i; i=s.iterator(); Studente q; while(i.hasNext()) { q=(Studente) i.next(); if(q.nome.equals(nome)) System.out.println(q); } }
Notare anche qui: fare un metodo generale comporta solo una piccola complicazione (un parametro in più, e poi la modifica della condizione dell'if)
Il programma completo è fatto cosí
import java.util.*; import java.io.*; class ProvaStudente { // altri metodi public static void main(String args[]) throws IOException { HashSet tutti; tutti=Studente.leggiFile("roma1.txt"); stampa(tutti); System.out.println(contaMeno(tutti, 5)); stampaStudente(tutti, "Totti"); }
Notare che il metodo main invoca il metodo Studente.leggiFile che opera con file.
Quindi, devo fare throws IOException
In generale: se si presenta l'errore di compilazione unreported exception..., basta aggiungere throws