Importante: Il sito di Fondamenti di Informatica C e' stato spostato sul nuovo portale wiki di Agent Group. Pertanto, questo sito non verrˆ pi aggiornato..
Il nuovo sito all'indirizzo http://mars.ing.unimo.it/wiki

Progetto per l'esame di Fondamenti C

Pagina delle FAQ sulla tesina: alla seguente pagina sono raccolte le risposte alle domande e dubbi posti dagli studenti al docente ed ai tutor.
E' disponibile il testo (file tesina07-08.pdf) della tesina da svolgere per l'anno accademico 07-08. Questo testo verra' discusso e ulteriormente spiegato durante l'esercitazione in laboratorio di Giovedi 10 Maggio 2008.

Alcune istruzioni per installare JMF (Java Media Framework) su Mac OS X: JMF on OS X

Esercitazioni 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).

In caso di problemi contattare Raffaele Quitadamo: raffaele.quitadamo@unimore.it
(ricevimento solo su appuntamento, da fissare via e-mail)


Il materiale delle esercitazioni verrà aggiunto progressivamente durante il corso.

Indice esercitazioni:

Esercitazione 1 (ADT)
Esercitazione 2 (OOP)
Esercitazione 3 (ereditarietà)
Esercitazione 4 (input/output)
Esercitazione 5 (grafica)
Esercitazione 6 (grafica con componenti GUI)
Esercitazione 7 (liste)
Esercitazione 8 (thread)
Esercitazione 9 (BST)

Suggerimenti:

Come ottenere le parentesi graffe su una tastiera italiana
Errori comuni che possono capitare nell'esecuzione di programmi Java
Individuare gli errori interpretando le eccezioni
Come produrre la documentazione Javadoc
Confronto fra le prestazioni di versioni successive delle JVM SUN
Visualizzazione in formato leggibile del bytecode di una classe
Classi base per la creazione di programmi di rete



ESERCITAZIONE 1
Data dell'esercitazione Riassunto degli argomenti trattati
10/04/2008,
laboratorio base
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).
Download
Lucidi introduttivi: intro_es1.pdf

Realizzazione di un dato astratto in C:

main1.c
cc.c
cc.h
Realizzazione di un tipo di dato astratto in C: main2.c
ccADT.c
ccADT.h
Materiale aggiuntivo:
OOP in C: uso di strutture pubbliche e private
Variante alla soluzione precedente
con strutture annidate:

ESERCITAZIONE 2
Data dell'esercitazione Riassunto degli argomenti trattati
17/04/2008,
laboratorio base
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.
Download
Realizzazione di un conto corrente semplice: testo dell'esercizio
ContoCorrente.java
StartCC1.java
(classe che contiene il main)
Realizzazione di un conto corrente con titolare: testo dell'esercizio
ContoCorrente2.java
Cliente.java
StartCC2.java
(classe che contiene il main)

ESERCITAZIONE 3
Data dell'esercitazione Riassunto degli argomenti trattati
24/04/2008,
laboratorio base

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).

Download
Lucidi introduttivi: intro_es3.pdf
Gerarchia di persone: testo dell'esercizio
Persona.java
Studente.java
Lavoratore.java
Prova.java
Implementazione di classi
per la gestione di automobili
e dei relativi motori:
testo dell'esercizio
schema delle classi
Motore.java
MotoreDiesel.java
MotoreBenzina.java
MotoreDiesel2500.java
MotoreBenzina3000.java
Optional.java
Automobile.java
Berlina.java
Coupe.java
Main.java

ESERCITAZIONE 4
Data dell'esercitazione Riassunto degli argomenti trattati
08/05/2008,
laboratorio base
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.
Download
Lucidi introduttivi: intro_es4.pdf

Esempio di input da stdin e
output su file binario:

testo dell'esercizio
ProvaFileBinario.java

Input/Ouput di famiglie
su file di testo e binario:

testo dell'esercizio
Persona.java
Famiglia.java
FileHandler.java
TextFileHandler.java
BinFileHandler.java
Main.java
Materiale aggiuntivo:
Esempio di lettura input utente
tramite menu testuali:
Main2.java

Esercizi di salvataggio parametri su file di testo
e serializzazione oggetti Persona/Famiglia su file binario
(esercitazione 2004):

esercitazione4-04.zip

ESERCITAZIONE 5
Data dell'esercitazione Riassunto degli argomenti trattati
15/05/2008,
laboratorio base
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.
Download
Disegno di un uomo stilizzato:

Pannello: ManPanel.java
Main: Prova.java

Programma per disegnare grafici di funzioni:

testo dell'esercizio
schema delle classi

Funzione.java
Sin.java
Retta.java
XQuadro.java
Exp.java
PannelloFunzione.java
PannelloFunzione2.java
Main.java

libreria copmleta jar
(ottenuta col comando "jar -cvf funzion.jar -C funzioni /")
Documentazione Javadoc della libreria completa
(ottenuta col comando "javadoc -private *.java -d docs")
Lucidi sull'importazione di librerie in formato Jar

Versione del programma precedente con menù
per la selezionare della funzione:
FinestraFunzioni.java (al posto di Main.java)
WindowCloser.java

ESERCITAZIONE 6
Data dell'esercitazione Riassunto degli argomenti trattati
22/05/2008,
laboratorio base
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.
Viene fornito come materiale aggiuntivo una esercitazione simile ma che prevede la gestione di lettere di testo.
Download
Programma di gestione note: testo dell'esercizio

Nota.java
PannelloNota.java
Keys.java
WindowCloser.java
PostIt.java
FinestraNote.java
Main.java

documentazione javadoc
Immagine relativa al
programma in esecuzione

ESERCITAZIONE 7
Data dell'esercitazione Riassunto degli argomenti trattati
29/05/2008,
laboratorio base

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)

Download
Interfaccia: List.java
Lista generica ad array: ListArray.java
MainArray.java
Lista ad array estesa: testo dell'esercizio
ListArray2.java
Persona.java
DuplicateItemException.java
FullListException.java
ItemNotFoundException.java
MainArray2.java
Archivio zip di tutti i file:

esercitazione7-06.zip


Interfaccia: List.java
Lista generica a puntatori:

Node.java
SimpleList.java
DoubleList.java
MainPunt.java

Materiale aggiuntivo:
Gestione di una lista di automobili: esercitazione7-04.tar.gz

ESERCITAZIONE 8
Data dell'esercitazione Riassunto degli argomenti trattati
05/06/2008,
laboratorio base

Lezione ed esercitazione sui thread Java.

Download
Lucidi del seminario sui thread: seminarioThread.pdf

ESERCITAZIONE 9
Data dell'esercitazione Riassunto degli argomenti trattati
12/06/2008,
laboratorio base

Gestione di alberi binari: introduzione e problematiche nell'utilizzo di tecniche ricorsive.
Uso delle librerie BST per la gestione di un'anagrafica ordinata studenti.

Download
Albero binario per studenti: testo dell'esercizio
Tree.java
TreeAction.java
TNode.java
BST.java
DefaultComparator.java
Studente.java
DefaultAction.java
StudComparator.java
Main.java



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):







Principali errori nell'esecuzione di programmi Java






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 JCreator affinchè esegua javadoc direttamente.
In entrambi i casi è consigliata la configurazione del PATH in maniera opportuna per gli strumnti del compilatore Java (si vedano a tal proposito i lucidi
installazione_java.pdf).

Da prompt dei comandi:

  1. spostarsi nella directory che contiene i sorgenti Java interessati
  2. digitare javadoc -private *.java
  3. il risultato dovrebbe essere come nella figura sotto riportata. Al termine i file HTML con la documentazione saranno presenti nella directory corrente.



All'interno di Jcreator:
  1. impostare un tool per l'esecuzione di Javadoc: selezionare il menu configure, options e cliccare sulla voce tools.


  2. cliccare sul pulsante new e selezionare DOS command. Nella finestra di dialogo che si apre digitare javadoc *.java. Confermare con il pulsante OK e chiudere la finestra delle opzioni.


  3. selezionando il tool apposito dalla barra dei tool la compilazione Javadoc viene avviata, e la finestra di output mostra i risultati.







Confronto fra le prestazioni di versioni successive delle JVM SUN



Quelli che seguono sono alcuni semplici risultati di un confronto fatto su versioni differenti delle JVM prodotte da SUN. Tali risultati non sono da considerarsi definitivi e/o professionali, e sono forniti al solo scopo di dare un'idea dei tempi di esecuzione delle singole JVM.
Il frammento principale del codice Java utilizzato e' il seguente:
for(int i=0;i<500;i++)
for(int j=0;j<500;j++)
for(int k=0;k<500;k++)
Math.sqrt( (double) i+j+k );

ove si faceva variare l'indice comune ai cicli for per ottenere un numero complessivo di radici calcolate differente. I programmi sono stati compilati con il compilatore a corredo della JVM, al fine di usufruire di tutte le ottimizzazioni possibili. I test sono stati eseguiti su una workstation SUN Ultra Sparc con sistema operativo SUN Solaris 5.8

Test eseguito JVM 1.2.2 JVM 1.4.2 JVM 1.5.0 beta
50^3 26-28 msec 117-147 msec 152-164 msec
500^3 26,533 sec 27,178 sec 27,601 sec
500^3 + apertura di un JFrame 32,709 sec 31,059 sec 33,193 sec







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:



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, appartenenti al package fluca.net 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:

	


   // 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();




	
	


dove la classe MyConnectionManager ha la seguente implementazione:

	
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;
 }

}

	
	


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:
  1. creare una implementazione di ConnectionManager per il client e una per il server; utilizzare gli stream forniti per scrivere e leggere oggetti;
  2. avviare il server: creare un oggetto BaseServer legato ad una porta e fornire il relativo ConnectionManager;
  3. connettere il client, tramite il metodo connectTo;
  4. una volta terminata la connessione (ossia alla fine di invio/ricezione di dati), chiudere la connessione client ed eventualmente stoppare il server.

E' presente anche un sottopackage, fluca.net.secure che fornisce una implementazione di server e client con concetti di sicurezza basati su Grant. Un grant e' un oggetto che descrive alcune proprieta' che occorre avere per accedere ad una connessione (ad esempio uno username e una password). Il client invia il grant al server che lo controllo con il proprio, se i dati contenuti nel grant coincidono la connessione viene concessa, altrimenti rifiutata. Si veda la documentazione javadoc per maggiori dettagli.

E' possibile scaricare il file jar da importare nei propri progetti qui: fluca.net.jar.
La documentazione javadoc puo' essere consultata qui.
Infine, sono disponibili anche i file sorgenti.