Corso JAVA
Corso linguaggio Java dalle basi - appunti per l'insegnamento
Last updated
Corso linguaggio Java dalle basi - appunti per l'insegnamento
Last updated
Lo scopo di questo tutorial vuole essere un tour per imparare le basi del linguaggio Java. E' una raccolta di fonti spero organizzata in modo utile e in parte di esercizi da me proposti durante due anni di esperienza didattica.
La cosa piรน utile รจ armarsi di pazienza e molta buona volontร (e non preoccuparsi se non si capisce tutto subito, รจ normale).
Quando si incomincia un viaggio, รจ bene avere una mappa mentale del territorio che si attraverserร . Cosรฌ รจ importante anche nel caso di un viaggio intellettuale, come imparare a scrivere un programma.
In questo caso รจ importante avere una minima idea di cosa รจ un computer e di come funziona.
Il processore รจ l'elemento attivo che opera eseguendo le istruzioni in linguaggio macchina che sono contenute nella memoria.
La memoria รจ costituita da una sequenza di celle o parole, ognuna delle quali รจ capace di contenere un certo numero di bit. Tale numero รจ detto lunghezza della parola. Ogni parola รจ identificata da un numero detto indirizzo.
Il processore contiene al proprio interno dei registri, capaci anch'essi di memorizzare un certo numero di bit come le celle di memoria. Il numero e il tipo dei registri posseduto varia da un tipo ad un altro di processore, ma praticamente tutti i processori possiedono un registro particolare detto contatore di programma o PC (Program Counter), che รจ destinato a contenere l'indirizzo della parola di memoria che contiene la prossima istruzione da eseguire.
Il processore opera eseguendo un ciclo fondamentale di interpretazione delle istruzioni, che puรฒ essere schematizzato nel modo seguente:
legge in memoria l'istruzione contenuta all'indirizzo contenuto nel registro PC;
interpreta l'istruzione letta e la esegue;
determina il nuovo valore di PC e torna al passo 1.
E' importante tenere presenti alcune osservazioni rispetto a questo ciclo:
all'inizio (cioรจ all'avviamento del processore) il registro PC deve contenere l'indirizzo della prima istruzione eseguibile dal programma;
durante l'interpretazione dell'istruzione il processore puรฒ accedere a celle di memoria che contengono gli operandi; la memoria contiene quindi sia le istruzioni che gli operandi del programma;
nel determinare il nuovo valore del PC normalmente il processore suppone che la prossima istruzione sia contenuta nella cella successiva di memoria, quindi si limita ad incrementare il valore del PC stesso; perรฒ se l'istruzione appena eseguita รจ di salto, allora il prossimo valore del PC viene determinato dall'istruzione stessa.
Le istruzioni che possono essere eseguite sono molto elementari, ad esempio:
trasferisci il contenuto di una cella di memoria in un registro o viceversa;
somma il contenuto di due registri (o analogamente altre operazioni aritmetiche o logiche);
salta all'esecuzione di un'istruzione posta a un certo indirizzo (questa istruzione modifica il registro PC);
come sopra, ma il salto deve essere eseguito solamente se vale una certa condizione (salto condizionato); una tipica condizione รจ che un certo registro abbia un contenuto uguale (oppure maggiore o minore) di zero;
Le istruzioni di salto (condizionato e non) sono di fondamentale importanza per realizzare in linguaggio macchina i costrutti di controllo (cicli, if-then, ecc...) di un linguaggio di programmazione. Si tenga presente che, anche se la maggior parte dei processori possiede un numero elevato di istruzioni, un linguaggio macchina completo, nel senso di un linguaggio sufficiente per realizzare tutte le funzioni richieste dalla traduzione di qualsiasi linguaggio di alto livello, puรฒ essere costituito da 5 o 6 istruzioni solamente, di cui almeno una deve essere di salto condizionato. Moltissime operazioni possono essere infatti realizzate utilizzando istruzioni piรน elementari (ad esempio la sottrazione tramite la negazione e somma, la moltiplicazione tramite somme ripetute, ecc...).
I diversi processori differiscono moltissimo in base al tipo di istruzioni che possono eseguire, ma possiamo affermare che tutti i processori operano secondo lo schema illustrato sopra e che i tipi di istruzione che eseguono sono sufficienti per svolgere tutte le funzioni richieste da un programma eseguibile ottenuto dalla traduzione (compilazione e collegamento) di un programma qualsiasi scritto in un linguaggio di programmazione di alto livello.
La CPU utilizza quasi tutto il tempo a caricare le istruzioni dalla memoria ed ad eseguirle. Tuttavia la CPU e la memoria principale sono solo due dei tanti componenti in un computer reale. Un sistema completo contiene altri device (periferiche) come:
Un hard disk o disco allo stato solido per immagazzinare programmi e file. (La memoria principale contiene solo una piccola parte di informazioni in paragone e le immagazzina solo fino a quando il sistema รจ acceso. Un disco fisso o un disco allo stato solido รจ utilizzato per l'immagazzinamento permanente di grosse quantitร di informazioni, ma i programmi devono essere caricati nella memoria principale prima di essere eseguiti. Un hard disk immagazzina i dati su un disco magnetico, mentre un disco allo stato solido รจ un device puramente elettronico senza parti in movimento.)
una tastiera o un mouse per input dell'utente;
un monitor o una stampante per mostrare l'output di un computer;
un device audio per permettere al computer di emettere suoni;
una network interface (interfaccia di rete) che permette a un computer di comunicare con altri computer che sono connessi in rete, sia wireless o con cavi;
uno scanner che converte immagini in insieme di byte che possono essere immagazzinati e modificati dal computer.
La lista dei device รจ aperta, un computer รจ costruito in modo da essere facilmente espandibile aggiungendo altri device. In qualche modo la CPU deve comunicare e controllare questi device. La CPU puรฒ solo fare questo eseguendo delle istruzioni in linguaggio macchina (la CPU sa fare solo questo). Il modo in cui funziona รจ che per ogni device nel sistema, c'รจ un device driver (detto anche solo driver), che รจ un software che la CPU esegue quando deve interagire con il driver. Installare un nuovo device su un sistema generalmente consiste in due step: aggiungere fisicamente il device al computer, e installare il device driver. Senza il driver, il device fisico sarebbe inutilizzabile, perchรฉ la CPU non sarebbe in grado di comunicare con esso.
Un computer in quanto ha molti device รจ organizzato collegando questi device da uno o piรน bus. Un bus รจ un insieme di cavi che trasportano diversi tipi di informazioni tra i device connessi da questi cavi. I cavi portano dati, indirizzi e segnali di controllo (comandi). L' indirizzo serve per dirigere il dato a un device particolare o a un registro particolare del device. Il segnale di controllo potrebbe servire per dare comandi al device. Un semplice esempio di computer potrebbe essere:
Ora, device come periferiche, mouse e network interface possono produrre input che devono essere processati dalla CPU. Come fa la CPU a sapere che ci sono dei dati? Un modo semplice, ma non molto soddisfacente, potrebbe essere quello di continuare al controllare da parte della CPU se sono arrivati nuovi dati. Fino a quando non trova il dato e lo processa. Questo metodo si chiama polling, perchรฉ la CPU fa il 'polls' (sondaggio) del device di input continuamente per vedere se hanno dei dati di input arrivati. Sfortunatamente sebbene il polling sia molto semplice, รจ anche molto inefficiente. La CPU puรฒ sprecare inutilmente tanto tempo aspettando che arrivi l'input.
Per evitare questa inefficienza, gli interrupt sono generalmente utilizzati. Un interrupt รจ un segnale inviato dal device alla CPU. La CPU risponde a un segnale di interrupt, mettendo da parte quello che stava facendo in modo da rispondere immediatamente all'interrupt. Una volta che ha gestito l'interrupt, cioรจ ha fatto tutte le azioni necessarie che richiedeva la periferica che ha mandato il segnale di interrupt, solo allora ritorna a fare quello che stava facendo prima che l'interrupt accadesse. Ad esempio quando voi pigiate un tasto della tastiera del computer, un interrupt della tastiera รจ inviato alla CPU. La CPU risponde a questo interrupt interrompendo cosa stava facendo, leggendo il tasto che abbiamo premuto, processandolo, a poi ritornando al task (compito) che stava compiendo prima che avessimo pigiato il tasto.
E' da capire che questo รจ un processo puramente meccanico: Un device segnala un interrupt semplicemente inviano un impulso elettrico su un cavo. La CPU รจ costruita in modo, che quando arriva il segnale, la CPU salvi abbastanza informazioni di quello che stava facendo, cosรฌ da poter tornare poi a riprendere quello che stava facendo. Questa informazione consiste nel contenuto di alcuni registri importanti della CPU come il program counter. Allora la CPU salta in alcuni zone di memoria predeterminate e comincia ad eseguire le istruzioni li immagazzinate. Queste istruzione fanno parte dell' interrupt handler (codice che gestisce l'interrupt) che fa le operazioni per rispondere all'interrupt. (Questo interrupt handler รจ parte del software del device driver della periferica che ha inviato l'interrupt). Al termine del interrupt handler c'รจ un istruzione che dice alla CPU di tornare a cosa stava facendo; lo fa ripristinando lo stato precedentemente salvato.
Gli interrupt permettono alla CPU di gestire eventi asincroni. Nel ciclo normale fetch-and-execute (carica l'istruzione ed eseguila), le cose avvengono nell'ordine predeterminato; ogni cosa che accade รจ "sincronizzata" con tutte le altre. Il meccanismo degli interrupt permette alla CPU di gestire in modo efficiente eventi che accadono in modo "asincrono", cioรจ, in momenti non predefiniti.
Un altro esempio di come gli interrupt sono usati, consideriamo cosa succede quando la CPU ha bisogno di accedere a dati che sono immagazzinati sull'hard disk. La CPU puรฒ accedere ai dati direttamente solo se sono nella memoria principale. I dati sul disco devono essere copiati nella memoria principale prima che possano essere acceduti. Sfortunatamente, rispetto alla scala di velocitร con cui la CPU opera, l'hard disk รจ molto lento. Quando la CPU ha bisogno di dati dal disco, la CPU invia un segnale all'hard disk dicendo di prendere i dati e prepararli. (Il segnale รจ inviato in modo sincrono, sotto il controllo di un programma normale) A questo punto, invece di aspettare per un tempo indefinito e lungo (rispetto ai tempi della CPU) che l'hard disk completi l'operazione, la CPU va avanti ed esegue qualche altro task (compito - programma). Quando l'hard disk ha i dati pronti, esso invia un segnale di interrupt alla CPU. L'interrupt handler puรฒ a questo punto leggere i dati richiesti.
******************
Si potrebbe notare che tutto ciรฒ a senso se la CPU ha diversi task (compiti) da svolgere. Se non avesse niente di meglio da fare, essa potrebbe utilizzare il tempo a fare polling per aspettare l'input o aspettare che si completino le operazioni sul disco. Tutti i moderni computer sono multitasking per completare molti task contemporaneamente. Alcuni computer possono essere utilizzati da piรน persone contemporaneamente. Visto che la CPU รจ cosรฌ veloce, puรฒ servire velocemente diversi utenti, riservando poche frazioni di secondo a ogni utente alternativamente. Questa applicazione del multitasking รจ chiamata time sharing. Ma i moderni personal computer con anche solo un singolo utente utilizzano il multitasking. Per esempio, l'utente potrebbe essere impegnato a scrivere un documento mentre l'orologio continua a mostrare l'ora e un file รจ in fase di download.
Ognuno dei task (compiti) individuali su cui la CPU sta lavorando รจ chiamata un thread. (O un processo; ci sono delle differenze tecniche tra i thread e i processi, ma non รจ fondamentale spiegarle qui, dato che i thread sono la modalitร utilizzata da Java). Molte CPU possono eseguire letteralmente piรน di un thread contemporaneamente (in parallelo) - queste CPU contengono piรน "cores", ognuno dei quali puรฒ eseguire un thread, ma c'รจ sempre un limite sul numero dei thread che possono essere eseguiti contemporaneamente. Siccome ci sono spesso piรน thread di quelli che possono essere eseguiti simultaneamente, il computer deve avere la possibilitร di spostare l'esecuzione da un thread a un altro, come per il timesharing il computer sposta la sua attenzione da un utente a un altro. In genere, un thread che รจ in esecuzione continuerร finchรฉ una di queste situazioni non accade:
Il thread puรฒ volontariamente yield (cedere) il controllo, per dare a un altro thread la possibilitร di essere eseguito;
Un thread potrebbe stare ad aspettare che qualche evento asincrono accada. Per esempio potrebbe richiedere qualche dato dal disco, o potrebbe aspettare che l'utente digiti qualche tasto sulla tastiera. Mentre sta aspettando, il thread รจ bloccato, e altri thread, se ci sono, hanno la possibilitร di essere eseguiti. Quando l'evento di input/output avviene (il dato da disco รจ pronto o l'utente ha premuto un tasto), un interrupt "sblocherร " il thread cosรฌ che potrร riprendere l'esecuzione;
Il thread potrebbe aver utilizzato tutto il suo slice di tempo e quindi essere sospeso per permettere ad altri thread di essere eseguiti. Molti sistemi operativi possono forzatamente sospendere un thread in questo modo; sistemi operativi che possono fare questo sono detti utilizzare il preentive multitasking. Per fare il preentive multitask, il computer necessita di uno speciale timer che genera un interrupt a intervalli regolari, tipo 100 al secondo. Quando l'interrupt del timer avviene, la CPU ha la possibilitร di fare lo switch da un thread a un altro, indipendentemente dalla volontร del thread che sta attualmente in esecuzione. Tutti i moderni computer, desktop o laptop, anche gli smartphone e tablet, utilizzano il preentive multitasking.
Gli utenti normali e anche i programmatori, non hanno a che fare con interrupt e interrupt handler. Si possono concentrare unicamente sui differenti task che vogliono che il computer esegua; i dettagli di come il computer gestisce per ottenere che tutti i task siano eseguiti non รจ per loro importante. Infatti, molti utenti, cosรฌ come molti programmatori, possono ignorare i thread e il multitsking del tutto. Tuttavia, i thread stanno diventando sempre piรน importanti cosรฌ come i computer stanno diventando sempre piรน potenti e come hanno iniziato a fare sempre piรน uso del multitasking e multiprocessing. In effetti, l'abilitร di lavorare con i thread sta diventando rapidamente una competenza essenziale per programmatori. Per fortuna, Java ha un buon supporto per i thread, che fanno parte del linguaggio Java come uno dei concetti fondamentali.
Cosรฌ come รจ importante in Java e nei moderni linguaggi di programmazione, il concetto di eventi asincroni. Anche se i programmatori non hanno in genere a che fare con gli interrupt direttamente, essi spesso devono scrivere event handlers, che come gli interrupt handlers, sono invocati in modo asincrono quando un certo evento accade. Questa "event-driven programming" ha un sapore molto diverso rispetto alla tradizionale programmazione sincrona.
***********************
A proposito, il software che implementa il meccanismo dell'interrupt handling, si occupa della comunicazione con l'utente e con l'hardware, e controlla quale thread (o processo) puรฒ andare in esecuzione, รจ chiamato il sistema operativo. Il sistema operativo รจ il software di base, essenziale senza il quale il computer non potrebbe funzionare. Altri programmi, come gli editor di testi o i web browser, dipendono dai servizi del sistema operativo. I sistemi operativi comuni per computer sono Linux, le diverse versioni di Windows e Mac OS. I sistemi operativi per smartphone e tablet sono Android e iOS.
Il linguaggio macchina consiste di semplici istruzioni che possono essere eseguite direttamente dalla CPU del computer. Tuttavia, quasi tutti i programmi, sono scritti in linguaggi di alto livello, come Java, Python o C++. Un programma scritto in un linguaggio di alto livello non puรฒ essere eseguito direttamente su nessun computer. Prima, deve essere tradotto in linguaggio macchina. Questa traduzione puรฒ essere fatta da un programma chiamato compilatore. Un compilatore prende un programma scritto in un linguaggio di alto livello e lo traduce in un programma in linguaggio macchina. Una volta che la traduzione รจ fatta, il programma in linguaggio macchina puรฒ essere eseguito qualsiasi numero di volte, ma puรฒ essere eseguito su un solo tipo di computer (poichรฉ ogni tipo di computer ha il proprio linguaggio macchina). Se il programma deve essere eseguito su un altro tipo di computer, deve essere ricompilato (ritradotto), utilizzando un compilatore differente, nel linguaggio macchina appropriato per quel tipo di computer.
C'รจ un'alternativa a compilare un programma scritto in un linguaggio di alto livello. Invece di utilizzare un compilatore, che traduce tutto il programma in un sol colpo, si puรฒ utilizzare un interprete, che traduce istruzione per istruzione. Un interprete รจ un programma che agisce come una CPU, come in un ciclo fetch-and-execute.
Consiglio di leggere prima queste due fonti per avere una visione piรน generale e un po' di approfondimento:
Gli step nella scrittura di un programma Java come illustrate sopra:
Step 1: Scriviamo il codice sorgente "Xxx.java".
Step 2: Compiliamo il codice sorgente "Xxx.java" in bytecode Java portabile "Xxx.class"utilizzando il compilatore Java tramite il comando javac Xxx.java
.
Step 3: Esegui il bytecode "Xxx.class", utilizzando il Java Runtime tramite il comandojava Xxx
.
PATH
nel vostro ambiente WindowsUna volta che avete scaricato il JDK (Java Development Kit) dal sito della Oracle, ma dalla versione 11 di Java, la JDK di Oracle รจ principalmente da utilizzare per scopi commerciali. Si puรฒ utilizzare la versione free OpenJDK dal sito:
Oppure l'OpenJDK puรฒ essere scaricata da AdoptOpenJDK a questo indirizzo:
Con le istruzioni per l'istallazione.
Dal prompt dei comandi deve essere possibile eseguire il programma javac
, il compilatore java, che serve per compilare il vostro sorgente da codice scritto in linguaggio java a bytecode (.class) - lo step 1.
Quando installate il JDK (Java Development Kit) sul vostro PC, installate sia il compilatore javac
(compila i file .java
in .class
) sia l'interprete java
, la vera e propria virtual machine, che esegue il bytecode (i file .class
).
Ad esempio sul mio PC ho installato il JDK in:
C:\dev\jdk-11.0.1
sotto la directory
C:\dev\jdk-11.0.1\bin
ci sono i programmi javac
e java
.
Come per qualsiasi altro programma per far si che si possano eseguire da qualsiasi directory che sia diversa dalla directory in cui รจ stato installato, รจ necessario configurare la variabile d'ambiente PATH
con il percorso agli eseguibili, javac
e java
.
Nel nostro caso dovremo aggiungere al PATH
il percorso C:\dev\jdk-11.0.1\bin
.
Il fatto di dover settare la variabile d'ambiente PATH
perchรฉ un programma sia trovato vale sia per sistemi operativi Windows, Linux e Unix. Cambia solo la sintassi tra i vari sistemi su come settare la variabile d'ambiente.
Tornando al nostro caso su Windows, il primo modo รจ di creare uno script .bat
che eseguite dal prompt dei comandi, contenete in questo caso:
set PATH=%PATH%;C:\dev\jdk-11.0.1\bin
In questo modo setto il PATH
aggiungendo ai percorsi precedenti il nuovo percorso per trovare il JDK.
Per verifica potere dal prompt vedere il nuovo PATH
:
Digitare da prompt: echo %PATH%
per vedere il contenuto del PATH
.
Potere vedere che in coda รจ stato aggiunto il percorso C:\dev\jdk-11.0.1\bin
come volevamo.
Ora potete eseguire javac
e il comando viene trovato.
Ad esempio digitando da prompt:
N.B: se aprite un altro prompt di comandi il PATH
qui non รจ con il nuovo PATH
che avete configurato: dovete eseguite anche qui lo script per aggiungere al path il percorso a javac
e java
.
Tutto ciรฒ รจ molto scomodo.
Il metodo alternativo e in genere utilizzato del programmatore รจ quello di configurarlo tra le variabili d'ambiente di Windows tramite il pannello di controllo:
Pannello di controllo
-> Sistema e sicurezza
-> Sistema
-> Impostazioni di sistema avanzate
Qui si apre un pannello Proprietร di sistema e cliccate sul tab 'Avanzate' e da qui su il bottone 'Variabili d'ambiente'.
Qui settate le variabili d'ambiente che vi interessano con nuova o modifica.
Nel sosto caso settiamo la variabile d'ambiente PATH
che รจ quella che ci interessa.
Ad esempio, con un editor, tipo Notepad++, si puรฒ scrivere il seguente file Hello.java
e salvarlo ad esempio in una directory myProject
, precedentemente creata, sotto la nostra directory home.
In seguito compilarlo con il comando javac Hello.java
ed eseguirlo con java Hello
, come si vede dell'output:
Come si vede in seguito alla compilazione, javac
, viene creato un file .class, Hello.class
, il file bytecode del sorgente .java.
Con il comando java Hello
, viene attivata la JVM che esegue il codice bytecode Hello.class
e in questo caso produce in output il messaggio, "Hello, world from Italy!".
ESERCIZI
e
introduce il concetto anche dei package, ma utile come esercizio per prendere dimestichezza con compilazione ed esecuzione di programmi da riga di comando.
Eseguite il tutorial . Se fosse necessario configurare l'ambiente per eseguire la compilazione con javac
per il vostro ambiente.