Contents
- 1 Esercizitazioni di Laboratorio
- 1.1 Informazioni sul docente
- 1.2 Esercitazione 1 (ADT)
- 1.3 Esercitazione 2 (OOP)
- 1.4 Esercitazione 3 (ereditarietà)
- 1.5 Esercitazione 4 (input/output)
- 1.6 Esercitazione 5 (grafica)
- 1.7 Esercitazione 6 (grafica con componenti GUI)
- 1.8 Esercitazione 7 (liste)
- 1.9 Esercitazione 8 (thread)
- 1.10 Esercitazione 9 (Binary Search Trees, BST)
- 1.11 Altre informazioni utili
- 1.11.1 Come ottenere le parentesi graffe su una tastiera italiana
- 1.11.2 Principali errori nell'esecuzione di programmi Java
- 1.11.3 Individuare gli errori mediante le eccezioni
- 1.11.4 Come produrre la documentazione Javadoc
- 1.11.5 Visualizzazione in formato leggibile del bytecode di una classe
- 1.11.6 Classi base per la creazione di programmi di rete
Esercizitazioni di Laboratorio
In questa pagina vengono riassunte tutte le esrcitazioni fatte in laboratorio. Viene messo a disposizione degli studenti del corso il materiale didattico usato durante le esercitazioni (lucidi, codici sorgenti, testi degli esercizi).
Il sorgente degli esercizi proposti è localizzato nel repository Subversion al seguente indirizzo http://mars.ing.unimo.it/svn/FondamentiC08.Per poter scaricare il codice si può utilizzare il browser, ma è consigliabile fare il checkout con Eclipse, come spiegato alla pagina Istruzioni Subversion
Informazioni sul docente
Dipartimento di Ingegneria dell'Infomazione
Università di Modena e Reggio Emilia, Via Vignolese 905 - 41100 Modena
Telefono 0592056322 - Fax 0592056126
Esercitazione 1 (ADT)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
10/04/08 | Programmazione ad oggetti: motivazioni e vantaggi. Astrazione di dati e implementazione mediante linguaggi non orientati agli oggetti; discussione su vantaggi e svantaggi.
Implementazione di un dato astratto e di un tipo di dato astratto per la gestione di un conto corrente in C. Utilizzo dei metodi per incapsulare operazioni, controlli sui parametri e sui dati di ingresso di ogni operazione. Cenni su altre implementazioni OOP possibili in C (si veda la sezione Materiale aggiuntivo). |
![]() | |
Lucidi introduttivi: | intro_es1.pdf |
Realizzazione di un dato astratto in C: | Esercitazione 1 |
Realizzazione di un tipo di dato astratto in C: | Esercitazione 2 |
![]() | |
OOP in C: uso di strutture pubbliche e private | Esercitazione 3 |
Variante con strutture annidate: | Esercitazione 4 |
Esercitazione 2 (OOP)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
17/04/2008 | Introduzione agli ambienti di sviluppo IBM Eclipse e NetBeans: progetti, file sorgenti, compilazione ed esecuzione. Cenni sul sistema di controllo delle versioni Subversion ed installazione del plugin subeclipse per Eclipse. Si vedano le pagine:
Implementazione di un semplice conto corrente in Java: variabili private di istanza e metodi di accesso. Implementazione di una classe contenitore per il metodo main, creazione di vettori (array) di dati primitivi e di oggetti. Utilizzo di this per risolvere l'ambiguita'. Esempi di errori di compilazione di esecuzione, con particolare riferimento a NullPointerException e ArrayIndexOutOfBounds |
![]() | |
Realizzazione di un conto corrente semplice: | testo dell'esercizio |
Esercitazione 1 | |
Realizzazione di un conto corrente con titolare: | testo dell'esercizio |
Esercitazione 2 |
Esercitazione 3 (ereditarietà)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
24/04/2008 | Ereditarieta': costruzione di gerarchie di classi, classi astratte, specificatori di accesso e classi abstract. Utilizzo dell'operatore final per impedire la sovrascrittura (overriding) di un metodo.
Utilizzo di this() e super() per richiamare i costruttori di una classe; utilizzo di this e super per risolvere l'ambiguita' fra membri di classi in relazione gerarchica. Composizione fra gerarchie di classi. Viene fornito come materiale aggiuntivo l'implementazione di una gerarchia di veicoli (esercitazione di laboratorio corrispondente, anno 2004). |
![]() | |
Lucidi introduttivi: | Media:intro_es3.pdf |
Gerarchia di persone: | Media:gerarchiaPersone.txt |
Esercitazione 1 | |
Implementazione di classi per la gestione di automobili e dei relativi motori: | Media:automobili.txt |
Esercitazione 2 |
Esercitazione 4 (input/output)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
08/05/2008 | Input/Output su file, in modalita' testuale e binaria, con particolare attenzione agli stream.
Utilizzo delle classi BufferedStreamReader e BufferedStreamWriter per l'I/O testuale. Utilizzo delle classi DataOutputStream e DataInputStream per la scrittura/lettura di tipi di dati primitivi da stream; ObjectOutputStream e ObjectInputStream per la scrittura/lettura di interi oggetti da stream; introduzione ai concetti di serializzazione. Introduzione alle eccezioni e ai blocchi try...catch. Implementazione di un esercizio di serializzazione binaria e testuale di un insieme di classi Famiglia e Persona. Viene fornito come materiale aggiuntivo un esercizio che mostra la lettura di input utente da STDIN, oltre ad un esercizio ulteriore di serializzazione di oggetti e di scrittura/lettura di parametri testuali nella forma chiave=valore. |
![]() | |
Lucidi introduttivi: | Media:intro_es4.pdf |
Esempio di input da stdin e output su file binario: | Media:ioBinario.txt |
Esercitazione 1 | |
Input/Ouput di famiglie su file di testo e binario: | Media:ioBinario2.txt |
Esercitazione 3 | |
![]() | |
Esempio di lettura input utente tramite menu testuali: | Esercitazione 2 |
Esercitazione 5 (grafica)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
15/05/2008 | Introduzione alla grafica Java: utilizzo dei pannelli per disegnare, realizzazione di finestre di primo livello (Frame) e gestione degli eventi dei menu'. Utilizzo di archivi Jar e della relativa documentazione Javadoc.
Realizzazione di un esercizio che visualizzo il grafico di una opportuna funzione. Controllo dei parametri e utilizzo dell'eccezione IllegalArgumentException. Utilizzo delle classi Adapter e implementazione di una semplice classe per la chiusura dell'applicazione e della relativa finestra. |
![]() | |
Disegno di un uomo stilizzato: | Esercitazione 1 |
Programma per disegnare grafici di funzioni: | Esercitazione 2 |
Esercitazione 6 (grafica con componenti GUI)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
22/05/2008 | Componenti Swing di base: pulsanti, caselle di testo, pannelli con scrolling, ecc. Gestione degli eventi ActionEvent. Realizzazione di un programma con GUI per la gestione di note di testo semplice.
Utilizzo dei layout manager. |
![]() | |
Programma di gestione note: | Esercitazione 1 |
Esercitazione 7 (liste)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
29/05/2008 | Trattamento di liste ad array: aggiunta e rimozione di elementi. Come implementare l'interfaccia List e come preparare gli oggetti che devono essere memorizzati in una lista, con particolare riferimento alla sovrascrittura del metodo equals(..). Gestione degli errori attraverso le eccezioni.
Implementazione di liste a puntatori: lista con un solo link (unidirezionale) e con doppio link (bidirezionale) |
![]() | |
Lista generica ad array: | Esercitazione 1 |
Lista ad array estesa: | Esercitazione 2 |
Lista generica a puntatori: | Esercitazione 3 |
Esercitazione 8 (thread)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
05/06/2008 | Lezione ed esercitazione sui thread Java. Creazione di thread ereditando da java.lang.Thread e implementando Runnable. Stati di un thread. Sincronizzazione tra thread: join(), wait() e notify(). Cenni sulla parola chiave synchronized. |
![]() | |
Lucidi del seminario sui thread: | Seminario Thread |
Creazione di thread ereditando da Thread: | Esercitazione 1 |
Creazione di thread implementando Runnable: | Esercitazione 2 |
Sincronizzazione di thread/utilizzo di join(): | Esercitazione 3 |
Sincronizzazione di thread/utilizzo di wait() e notify(): | Esercitazione 4 |
Esercitazione 9 (Binary Search Trees, BST)
Data Esercitazione | Riassunto degli argomenti trattati |
---|---|
12/06/2008 | Gestione di alberi binari: introduzione e problematiche nell'utilizzo di tecniche ricorsive. Uso delle librerie BST per la gestione di un'anagrafica ordinata studenti. |
![]() | |
Albero binario per studenti | testo dell'esercizio |
Esercitazione 1 |
Altre informazioni utili
Come ottenere le parentesi graffe su una tastiera italiana
Le tastiere con un layout differente da quello americano non hanno dei tasti con le parentesi graffe. E' possibile ottenere tali parentesi in uno dei seguenti modi (alcuni potrebbero non funzionare a seconda del programma usato):
- codice ASCII:
tenere premuto ALT e digitare 123 per la parentesi aperta, 125 per quella chiusa
- SHIFT+ALT grafico:
premere contemporaneamente SHIFT e ALT grafico (ALT GR, il tasto posto a destra) e i tasti con le parentesi quadre per ottenere quelle graffe
- ALT grafico (Linux):
premere l'ALT grafico (ALT GR, il tasto posto a destra) e il numero 7 per la parentesi aperta, 8 per quella chiusa.
- cambio layout tastiera (Windows):
premendo ALT+SHIFT e' possibile cambiare il layout fra americano e italiano, e quindi utilizzare la tastiera come se fosse americana.
Principali errori nell'esecuzione di programmi Java
- Tendando di eseguire un programma compare
Exception in thread "main" java.lang.NoSuchMethodError: main significa che si sta tentando di eseguire una classe che non dichiara il metodo main o che non lo dichiara con il prototipo corretto. Si ricordi che il prototipo del main e' il seguente:
<java> public static void main(String argv[]) </java>
- Tendando di eseguire un programma si ottiene
Exception in thread "main" java.lang.NoClassDefFoundError:
significa che si sta cercando di accedere ad una classe che non puo' essere trovata. Tipicamente questo avviene perché il nome della classe non e' corretto o la classe non e' presente nel classpath.
- Durante l'esecuzione del programma compare
Exception in thread "main" java.lang.NullPointerException
significa che si sta accedendo tramite un riferimento non inizializzato correttamente. Si ricordi che i riferimenti agli oggetti sono inizializzati a null, e quindi occorre fare una escplicita istanziazione. Ad esempio:
<java> ContoCorrente cc; // dichiara solo il riferimento => cc vale null
... cc = new ContoCorrente(..); //ora posso usare cc senza problemi di NullPointerException
</java>
Si ricordi inoltre che anche i vettori vanno istanziati con new: <java> int vettore[]; ... vettore = new int[10]; </java>
- Durante l'esecuzione del programma compare
Exception in thread "main" java.lang.ArrayIndexOutOfBounds
significa che si sta accedendo ad un array oltre il suo ultimo valore. Si ricordi che un valore di n elementi ha un indice che puo' variare fra 0 e n-1.
- A tempo di compilazione compare l'errore
cannot resolve symbol
il compilatore non riesce a capire a quale variabile o metodo si sta facendo riferimento. Tipicamente questo accade quando si sbaglia a scrivere il nome di una variabile o di un metodo. Ad esempio:
<java> int laMiaVariabile;
... laTuaVariabile=10; // variabile non dichiarata prima => errore di battitura,ecc.
</java>
Se il simbolo indicato è un'oggetto e non sono presenti errori di battitura, molto probabilmente non si è compilato l'oggetto stesso. Si ricompili il sorgente della variabile non trovata e si riprovi.
- A tempo di compilazione compare l'errore
duplicated class
questo errore puo' essere generato da diverse cause. In generale il compilatore trova due definizioni della stessa classe e quindi non sa quale utilizzare. Il primo tentativo da fare è quello di cancellare tutte le classi (file .class) e ricompilare il progetto. Se ancora si verificano dei problemi, si controlli di non avere alcun sorgente nelle directory in cui vengono inserite le classi compilati. Un'altra causa di errore puo' derivare da una impostazione del classpath errata: si verifichi che il classpath non punti a directory ove sono presenti classi omonime.
Individuare gli errori mediante le eccezioni
Le eccezioni rappresentano errori di esecuzione. Il sistema di eccezioni di Java è molto completo e complesso, per una sua trattazione si rimanda alle lezioni del prof.Cabri. Tuttavia, anche senza conoscere i dettagli di funzionamento del meccanismo ad eccezioni, è possibile utilizzarle per scovare gli errori nei programmi e quindi correggerli. Ecco come si presenta l'output di un programma che genera un'eccezione.
Exception in Thread "main": java.lang.ArrayIndexOutOfBoundsException: 10 at jaas.Untitled1.main(Untitled1.java:18) Exception in thread "main"
La prima riga riporta la classe che identifica il tipo di eccezione/errore (in questo caso java.lang.ArrayIndexOutOfBoundsException). E' possibile cercare nella documentazione Javadoc il significato attribuito alla classe di eccezione, ossia le condizioni in cui tale errore si verifica. La seconda riga indica il punto in cui l'eccezione si è verificata, in modo che il programmatore possa trovare corrispondenza nel codice sorgente. L'indicazione sull'errore è formattata nel seguente modo: package.nome_classe.nome_metodo(nome_file:riga)
ossia viene indicato il nome della classe nella quale si è verificato l'errore, il nome del metodo (l'etichetta che precede le parentesi), il nome del file e la riga di codice che ha generato l'errore. Basta aprire nome_file e portarsi alla riga riga per individuare la riga di codice che ha generato il problema.
Come produrre la documentazione Javadoc
Per produrre la documentazione Javadoc si puo' utilizzare il comando javadoc da una finestra prompt dei comandi o, in alternativa, si puo' impostare l'ambiente Eclipse affinchè esegua javadoc direttamente. In entrambi i casi è consigliata la configurazione del PATH in maniera opportuna per gli strumnti del compilatore Java.
Da prompt dei comandi:
- spostarsi nella directory che contiene i sorgenti Java interessati
- digitare javadoc -private *.java
- il risultato dovrebbe essere come nella figura sotto riportata. Al termine i file HTML con la documentazione saranno presenti nella directory corrente.
Visualizzazione in formato leggibile del bytecode di una classe
Fra gli strumenti del JDK è presente javap che consente di disassemblare una classe compilata in formato umanamente leggibile. Ecco un esempio di decompilazione:
Compiled from "Cliente.java"
public class Cliente extends java.lang.Object{
public Cliente(java.lang.String,java.lang.String,java.lang.String,int);
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #2; //Field nome:Ljava/lang/String;
9: aload_0
10: aload_2
11: putfield #3; //Field cognome:Ljava/lang/String;
14: aload_0
15: aload_3
16: putfield #4; //Field indirizzo:Ljava/lang/String;
19: aload_0
20: iload 4
22: putfield #5; //Field telefono:I
25: return
public java.lang.String getNome();
Code:
0: aload_0
1: getfield #2; //Field nome:Ljava/lang/String;
4: areturn
public java.lang.String getCognome();
Code:
0: aload_0
1: getfield #3; //Field cognome:Ljava/lang/String;
4: areturn
public java.lang.String getIndirizzo();
Code:
0: aload_0
1: getfield #4; //Field indirizzo:Ljava/lang/String;
4: areturn
public java.lang.String getCognome();
Code:
0: aload_0
1: getfield #3; //Field cognome:Ljava/lang/String;
4: areturn
public java.lang.String getIndirizzo();
Code:
0: aload_0
1: getfield #4; //Field indirizzo:Ljava/lang/String;
4: areturn
public int getTelefono();
Code:
0: aload_0
1: getfield #5; //Field telefono:I
4: ireturn
public void cambioIndirizzo(java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #4; //Field indirizzo:Ljava/lang/String;
5: return
public void cambioTelefono(int);
Code:
0: aload_0
1: iload_1
2: putfield #5; //Field telefono:I
5: return
}
Per ottenere un output simile al precedente e' sufficiente lanciare il comando javap con l'opzione -c, specificando la classe (compilata) da disassemblare:
javap -c Cliente
Mediante lo strumento javap i programmatori possono analizzare l'output di una compilazione studiando cosi' il comportamento del compilatore e le eventuali ottimizzazioni possibili.
Classi base per la creazione di programmi di rete
La creazione di programmi di rete, ossia di programmi capaci di comunicare con altri programmi per mezzo di connessioni di rete, richiede la conoscenza di alcuni concetti fondamentali riguardanti i Thread e le Socket. Tuttavia, ricordando che Java gestisce tutto l'input/output per mezzo di stream, e' possibile nascondere i dettagli relativi alle connessioni in classi di piu' alto livello, che possono essere utilizzate nei propri programmi. Le classi presentate di seguito servono appunto a facilitare la creazione di applicazioni di rete client/server nascondendo la gestione dei thread e delle socket al programmatore, che deve quindi preoccuparsi solo di gestire opportunamente gli stream dati.
La struttura del package e' mostrata nella seguente figura:
Come si puo' notare, la classe BaseNetClass funge da capostipite della gerarchia, fornendo solo alcuni servizi comuni alle classi che implementano il client e il server (rispettivamente BaseClient e BaseServer). La classe BaseServer incapsula un server con il proprio thread di esecuzione, l'unica informazione necessaria per la creazione di un server e' il numero di porta sul quale questo dovra' restare in ascolto. Una volta avviato, il server restera' in ascolto fino al sopraggiungere di una connessione, che sara' opportunamente servita, per poi tornare ad attendere altre connessioni. E' possibile arrestare il server mediante il metodo stop(). La classe BaseClient fornisce i servizi base per la gestione di un client; il suo metodo principale, connectTo(..), consente di stabilire la connessione verso un server in esecuzione. Entramnbe le classi, client e server, utilizzano delle implementazioni dell'interfaccia ConnectionManager per trattare opportunamente la connessione. Appena il server riceve una connessione, invoca il metodo manageConnection(..) dell'interfaccia ConnectionManager passando gli stream (ObjectInputStream e ObjectOutputStream) agganciati alla connessione. Similarmente, appena il client riesce a stabilire una connessione con il server, invoca lo stesso metodo su una implementazione di ConnectionManager. E' quindi sufficiente implementare l'interfaccia di cui sopra per gestire il protocollo di comunicazione fra client e server. Tipicamente client e server avranno una implementazione di ConnectionManager differente, ma in casi molto semplici (come l'esempio qui di seguito proposto) l'implementazione potrebbe anche essere la stessa. Il seguente esempio mostra l'utilizzo delle classi sopra descritte per implementare un semplice programma client/server dove client e server si scambiano un messaggio:
<java>
// creo i connection manager relativi alla connessione ConnectionManager server = new MyConnectionManager("Messaggio dal server"); ConnectionManager client = new MyConnectionManager("Messaggio dal client");
// creo il server, che esegue su un thread proprio BaseServer bServer = new BaseServer(3663, server, true);
// creo il client BaseClient bClient = new BaseClient(client);
// dico al client di collegarsi alla porta del server sull'host locale bClient.connectTo("localhost",3663);
// a questo punto la gestione delle connessioni e' automatica! // posso stampare i messaggi che il client e il server si sono scambiati System.out.println("IL SERVER HA RICEVUTO: "+((MyConnectionManager)client).getMessaggio()); System.out.println("IL CLIENT HA RICEVUTO: "+((MyConnectionManager)server).getMessaggio());
// dico al client di chiudere la connessione bClient.closeConnection();
</java>
dove la classe MyConnectionManager ha la seguente implementazione:
<java> public class MyConnectionManager implements ConnectionManager{
private String messaggio; public MyConnectionManager(String messaggio){ this.messaggio = messaggio; }
public void manageConnection(SocketAddress remoteAddress, ObjectInputStream input, ObjectOutputStream output, boolean canRead, boolean canWrite){
try{ // scrivo il messaggio verso l'altro capo della connessione if( canRead ) output.writeObject(messaggio);
// leggo il messaggio: faccio un ciclo per una read non bloccante do{ this.messaggio = (String) input.readObject(); }while( this.messaggio == null && canWrite);
}catch(IOException e){ System.err.println("Errore in scrittura/lettura"); e.printStackTrace(); } catch(ClassNotFoundException e){ System.err.println("Errore in ricerca classe letta"); e.printStackTrace();
} }
public String getMessaggio(){ return this.messaggio; }
} </java>
Tale programma produce come output (si tenga presente che le classi di rete qui fornite stampano, per default, molti messaggi diagnostici):
[note.net.BaseServer - main] Devo creare un nuovo thread demone per questo server [note.net.BaseServer - main] Avvio il thread del server [note.net.BaseServer - main] thread del server avviato [note.net.BaseClient - main] Tentativo di connessione a localhost/127.0.0.1:3663 [note.net.BaseClient - main] Gestisco la connessione attraverso il connection manager net.test.MyConnectionManager [note.net.BaseClient - main] Stream di output pronto [note.net.BaseClient - main] Stream di input pronto [note.net.BaseServer - BaseServerThread] Socket server creata, entro nel ciclo di attesa [note.net.BaseServer - BaseServerThread] Attendo una nuova connessione... [note.net.BaseServer - BaseServerThread] connessione ricevuta! [note.net.BaseServer - BaseServerThread] Client:/127.0.0.1:1932 [note.net.BaseServer - BaseServerThread] Gestisco la connessione attraverso il connection manager net.test.MyConnectionManager [note.net.BaseServer - BaseServerThread] Stream di output pronto [note.net.BaseServer - BaseServerThread] Stream di input pronto [note.net.BaseClient - main] Gestione connessione terminata [note.net.BaseServer - BaseServerThread] Gestione connessione terminata IL SERVER HA RICEVUTO: Messaggio dal client IL CLIENT HA RICEVUTO: Messaggio dal server [note.net.BaseServer - BaseServerThread] Risveglio di eventuali thread sospesi su richiesta di stream [note.net.BaseServer - BaseServerThread] Attendo una nuova connessione...
Di conseguenza i passi fondamentali per creare applicazioni di rete velocemente sono:
- creare una implementazione di ConnectionManager per il client e una per il server; utilizzare gli stream forniti per scrivere e leggere oggetti;
- avviare il server: creare un oggetto BaseServer legato ad una porta e fornire il relativo ConnectionManager;
- connettere il client, tramite il metodo connectTo;
- una volta terminata la connessione (ossia alla fine di invio/ricezione di dati), chiudere la connessione client ed eventualmente stoppare il server.