|
Java Tutorial - Parte 1 0.1
Un tutorial per esempi
|
Fin dalla antichità l'uomo ha cercato di produrre macchine che rendessero più agevole il calcolo aritmetico: nonostante la grande potenzialità del cervello umano, il calcolo aritmetico è, comunque, una operazione complessa da portare a termine; provate per esempio ad eseguire una divisione con dividendi di sei o sette cifre oppure divisori con tre o quattro cifre e vi renderete conto che il tempo impiegato per l'operazione è abbastanza lungo.
Le macchine usate nei secoli passati come l'abaco o il regolo calcolatore avevano un funzionamento esclusivamente meccanico e non possono nemmeno lontanamente essere paragonate ai moderni elaboratori perchè la loro struttura più intima era completamente diversa: possedevano infatti una architettura analogica in contrapposizione alla archituttura digitale delle moderne macchine.
Possiamo perciò affermare che l'era del calcolo elettronico comincia nella prima metà del secolo scorso quando furono inventate le prime macchine con architettura digitale come l'E.N.I.A.C. ed il Mark I: a quell'epoca non era ancora stato inventato il transistor e questi calcolatori erano principalmente composti da valvole termoioniche collegate tra loro mediante contatti saldati manualmente.
Questi calcolatori erano enormi, pesavano qualche tonnellata ed erano estremamente difficili da programmare ma, soprattutto, molto lenti nelle elaborazioni e soffrivano, a causa della loro architettura, di surriscaldamento.
L'avvento del transistor rivoluzionò totalmente questo settore: al giorno d'oggi un microprocessore può contenere milioni di transistor in pochi centimetri quadrati con un peso di pochi grammi: il basso consumo elettrico, inoltre, è stato la soluzione al problema del surriscaldamento anche se alcuni moderni processori devono dissipare il loro calore perchè spinti a frequenze di clock elevatissime.
Trascurando i calcolatori a funzionamento meccanico, tutti gli altri, fin dal primo calcolatore a valvole, si basano sulla stessa struttura principale, sebbene molto semplificata, descritta nella figura sottostante:
La CPU (Central Processing Unit = Unità Centrale di Elaborazione) è il vero cervello del calcolatore. E' composta da migliaia o addirittura milioni di transistor ed è in grado di eseguire le istruzioni che il programmatore ha stabilito. La CPU è composta da diversi registri che prelevano i dati e le istruzioni dalla memoria principale, detta RAM. I dati vengono elaborati nei registri del processore ed il risultato viene posto in un altro registro oppure in memoria RAM. Il numero di registri della CPU è molto limitato, per esempio le moderne CPU x86 hanno:
E' ovvio che i dati ed i programmi devono essere memorizzati da qualche altra parte poichè sarebbe impossibile contenerli tutti nei registri della CPU.
La RAM (Random Access Memory = Memoria ad Accesso Casuale) è la memoria principale del calcolatore direttamente accessibile alla CPU ed è quindi nella RAM che vengono memorizzati i dati ed i programmi da dove verranno prelevati dalla CPU ed inseriti nei suoi registri per la elaborazione. La CPU è solo in grado di indirizzare la memoria RAM quindi per poter prelevare dati ed istruzioni, questi ultimi devono risiedere in RAM. Essendo elettronica, la memoria RAM è velocissima: il tempo di accesso ad essa si misura in nanosecondi. Il suo più evidente svantaggio è che è volatile: quando viene a mancare la corrente elettrica, la RAM si scarica e perde tutti i dati.
Per ovviare alla perdita dei dati da parte della RAM, è necessario che dati e programmi siano memorizzati su un supporto permanente. Questi tipi di supporti non sono elettronici come la RAM ma magnetici oppure ottici in modo che essi possano preservare il loro stato anche in assenza di corrente elettrica. Le memorie di massa sono gli hard disk, i CD-ROM e DVD-ROM, le memorie flash (quelle delle penne USB), etc.
Dati e programmi vengono caricati dalle memorie di massa nella RAM per poter poi essere eseguiti dalla CPU. Anche se non così veloci come la RAM, le moderne memorie di massa hanno tempi di accesso piuttosto bassi, che si misurano in millisecondi.
Per interagire con il calcolatore, cioè per poter inserire dati e programmi e per vederne i risultati, sono necessari dispositivi particolari detti di I/O (Input/Output). Appartengono a questa categoria il monitor (dispositivo di solo output ad eccezione dei moderni monitor touch screen), la tastiera ed il mouse (dispositivi di solo input), tavolette grafiche, penne, altoparlanti e telecamere, joystick etc. Di tutti i dispositivi collegati al bus di sistema i dispositivi di I/O sono i più lenti (pensate ad esempio alla tastiera) anche se alcuni di essi, in particolare i monitor, sono piuttosto veloci.
Sia i dispositivi di I/O che le memorie di massa (che possono essere equiparati, da un punto di vista logico, ai dispositivi di I/O) sono collegati alla CPU attraverso delle porte di I/O. Lo svantaggio principale in questa organizzazione è che le operazioni di I/O coinvolgono la CPU per poter caricare i dati dal dispositivo alla memoria principale (la RAM) e viceversa. Mentre per alcuni di questi dispositivi (per esempio la tastiera) questo non è un problema (l'utente più veloce riesce a battere una media di 2-3 caratteri al secondo), per altri, come le memorie di massa che hanno un throughput (=ampiezza di trasferimento) di decine o anche centinaia di kilobytes al secondo questo potrebbe impegnare la CPU per molto tempo.
Per sollevare la CPU da questa incombenza, i dispositivi particolarmente veloci come per esempio gli harddisk, sono collegati ad uno speciale circuito detto Direct Memory Access (Accesso Diretto alla Memoria) che permette alla CPU di assegnare ad esso un blocco di memoria dove eseguire il trasferimento di grandi quantità di dati senza alcun intervento da parte della CPU.
I mattoni fondamentali di ogni programma per computer in qualunque linguaggio esso sia realizzato sono quattro:
visualizza, disegna oppure calcola n valori di temperatura giornaliera per calcolarne la mediaSicuramente avrete sentito parlare della diatriba analogico vs. digitale e sulle argomentazioni che vorrebbero il mondo analogico qualitativamente migliore e più rispondente alla realtà rispetto al digitale ma (trascurando la diatriba, che non è lo scopo di questa guida), che cosa significa digitale?
La parola deriva dal vocabolo inglese digit che significa cifra, numero; a sua volta il vocabolo inglese deriva dal latino digitus che significa dito.
In informatica, tutto ciò che attiene l'elaborazione elettronica attraverso i computer è digitale nel senso inglese del termine e significa che i computers possono comprendere e manipolare esclusivamente quantità numeriche ovverossia, solo ed esclusivamente numeri.
Se per caso state pensando che ciò non è vero, in quanto state leggendo questa guida scritta in un alfabeto umano, ebbene avrete una delusione: poichè i computer possono solo gestire quantità numeriche, l'uomo ha stabilito una corrispondenza tra un codice numerico e le lettere dell'alfabeto.
Questa codifica prende il nome di character set (=insieme di caratteri) così, ad esempio, si è stabilito che il codice 65 corrisponda alla lettera 'A' maiuscola, il codice 66 alla 'B' maiuscola, e così via.
I due codici che ho scritto sopra sono effettivamente in uso e rappresentano il celebre codice ASCII (American Standard Code for Information Interchange= Codice Standard Americano per lo Scambio delle Informazioni) ed è uno dei primi codici usati nei computer anche se, per noi europei, questo codice viene spesso chiamato US-ASCII per fare espressamente riferimento al codice usato negli Stati Uniti.
La codifica US-ASCII è composta da 128 codici, da ZERO a 127, e non è in grado di rappresentare gli alfabeti europei come quello latino, che usa le lettere accentate, oppure quello greco od il cirillico.
Per questo motivo sono nati altri standards di codifica basati su US-ASCII che estendono il character set usando i codici da 128 a 255 per codificare le lettere degli alfabeti europei. Questi codici sono stati standardizzati da un organismo internazionale, (ISO = International Standards Organization), e, benchè noi europei ci riferiamo a questo codice come al codice ASCII, in realtà, quando lo facciamo, ci riferiamo al set di caratteri specifico del nostro alfabeto.
Per essere precisi, il codice ASCII usato dagli alfabeti europei, essendo diverso tra le nazioni, dovrebbe essere chiamato con il nome stabilito dall'ISO come, per esempio:
Nei moderni linguaggi come Java, la distinzione tra i vari set di caratteri non è più un problema: verso la fine degli anni '90 il consorzio Unicode ed ISO si unirono per stabilire uno standard di caratteri a cui viene data la codifica ISO 10646 e che usa 31 bits per la codifica dei caratteri degli alfabeti; con i suoi oltre 2 miliardi di codici, lo standard Unicode è in grado di rappresentare tutti gli alfabeti del mondo, compresi quelli arcaici.
Comunque, anche associando un codice numerico alle lettere dell'alfabeto non si spiega come mai leggendo un qualsiasi testo in alfabeto latino è possibile leggere le lettere che compongono l'alfabeto e non il codice numerico.
Questo è permesso grazie al cosidetto font che stabilisce una relazione tra il codice numerico, per esempio il 84 che corrisponde alla 'T' maiuscola, e la sua immagine sullo schermo. Ancora una volta la immagine di una lettera, per esempio la 'T' maiuscola, è una interpretazione numerica di un simbolo umano. In pratica, il simbolo viene suddiviso in piccoli quadrati detti pixel ognuno dei quali può valere ZERO (bianco) o 1 (nero) e quello che noi vediamo altro non è che una serie di ZERO e di UNO come possiamo vedere nella figura sottostante, che è un ingrandimento di una porzione di schermo:
Anche le immagini a colori, come le fotografie, sono rappresentate dai computer come quantità numeriche. Ogni fotografia è composta da piccolissimi "puntini" detti pixel (contrazione di picture element = elemento dell'immagine) nei quali il colore del singolo puntino è determinato dalla combinazione dei tre colori fondamentali: il rosso, il verde ed il blu, da cui ha origine la codifica RGB (Red, Green, Blu). Ogni singolo pixel viene rappresentato con una quantità numerica da ZERO a 255 di questi tre colori: così, per esempio, per avere un pixel rosso intenso la tripletta di colori sarà: red=255, green=0, blu=0.
Se il pixel deve essere viola scuro allora la tripletta prevederà una componente del rosso ed una del blu, ma la quantità di colore sarà più bassa come per esempio: 128,0,128.
Anche i browser sono in grado di rappresentare i colori a seconda delle componenti RGB. Per esempio, lo sfondo sottostante è stato ottenuto con le quantità indicate più sopra per il colore viola (red=128, green=0, blu=128):
Una ulteriore complicazione nel gestire i dati ed i programmi per computer è data dal fatto che, oltre a comprendere solo quantità numeriche, il computer non è in grado di ragionare secondo il nostro sistema di numerazione: quello decimale. Poichè il sistema decimale si basa su dieci simboli numerici - da ZERO a NOVE - ma il computer non è in grado di distinguere se non due soli stati - ON/OFF, acceso/spento, UNO/ZERO - esso usa un sistema di numerazione diverso dal nostro, basato su due sole cifre (appunto lo ZERO e l'UNO) detto sistema binario.
Ogni singolo interruttore elettronico presente nel computer può assumere il valore ZERO (spento) oppure UNO (acceso) ed è chiamato bit che è la contrazione di binary digit, ossia, cifra binaria.
L'insieme di 8 bit formano un byte che, per la maggior parte dei computer moderni, è anche detto char (un carattere dell'alfabeto). Non a caso, infatti, il codice ASCII (ad eccezione del US-ASCII, che usa 7 bit) utilizza 8 bit, ossia 1 byte, per codificare gli alfabeti umani. Questo non si applica a Java poichè, come scritto in precedenza, esso usa la codifica Unicode per rappresentare i caratteri degli alfabeti: Maggiori info su questo argomento in I tipi di dato in Java.
In questa sezione il lettore avrà una paronamica sui vari linguaggi di programmazione per computer, i loro vantaggi e svantaggi, lo scopo per cui sono stati sviluppati e, perchè no, il lettore potrebbe decidere di imparare un linguaggio diverso da Java perchè più rispondente alle proprie necessità.
Per prima cosa risponderemo subito ad alcune domanda che vi assillano fin da quando avete iniziato a leggere questa guida:
Domanda: Qual'è il migliore linguaggio di programmazione?
Risposta: Non esiste il migliore in assoluto!
Domanda: Perchè così tanti linguaggi di programmazione?
Risposta: Per soddisfare esigenze diverse
Domanda: Qual'è il linguaggio più veloce in assoluto?
Risposta: Il codice macchina
Domanda: Qual'è il linguaggio più facile da imparare?
Risposta: Dipende. Alcuni linguaggi sono nati proprio per i principianti e si presume siano i più adatti a questo scopo.
Come abbiamo avuto modo di comprendere nei capitoli precedenti, il computer esegue le istruzioni che vengono impartite dal programmatore sui dati da egli stesso forniti e ottiene dei risultati. Sia le istruzioni, che i dati, che i risultati vengono gestiti dal computer esclusivamente in formato numerico e, di più, questi numeri sono espressi nell'unico sistema che il computer è in grado di capire: il sistema binario.
Per complicare ancora di più la situazione, le istruzioni che il computer è in grado di capire sono molto elementari: nel vocabolario del computer non esistono parole come visualizza, stampa oppure ruota una immagine.
Qui si seguito, potete vedere uno spezzone di codice cosidetto disassemblato di un programma:
Evidenziamo il fatto che il codice disassemblato non è il codice binario puro ma una traduzione in un linguaggio umano delle istruzioni che la CPU ha nel suo vocabolario e che comprende direttamente. Questo linguaggio - che per inciso non ha niente di umano - si chiama assembly da cui deriva il nome disassemblato.
Per pura informazione, ogni riga del codice assembly vista più sopra è formata da tre colonne:
Il fatto che la CPU possa eseguire solo istruzioni così semplici non deve far pensare che essa sia limitata: è vero che le istruzioni sono elementari ma tante istruzioni elementari messe insieme e ripetute per molte volte possono compiere azioni decisamente complesse. Se a questo aggiungiamo che una CPU è in grado di eseguire centinaia di milioni di queste istruzioni in un secondo allora possiamo comprendere perchè un computer è in grado di eseguire calcoli che per un cervello umano sarebbero assolutamente fuori portata.
Per nostra fortuna, l'uomo si è ben presto reso conto che programmare un computer in assembly è un compito alquanto arduo. Sono nati perciò i linguaggi cosidetti problem oriented (=orientati al problema da risolvere) in contrapposizione all'assembly cosidetto machine oriented (= orientato alla macchina).
Ogni linguaggio, per così dire umano, (che d'ora in avanti chiameremo col nome di linguaggio simbolico) è stato sviluppato per facilitare il compito di programmare il computer ma, tuttavia, ognuno di essi si è specializzato in una particolare materia. Troviamo perciò linguaggi nati per risolvere formule matematiche come il FORTRAN (contrazione di FORmula TRANslator) oppure il COBOL (COmmon Business Oriented Language = Linguaggio Comune Orientato agli Affari), oggi caduto in disuso.
Per i principianti potrebbe essere conveniente imparare ad usare il linguaggio nato proprio per questo scopo: il BASIC (Beginner's All-purpose Simbolic Instruction Code = Codice simbolico adatto a tutti gli scopi per principianti).
L'elenco è lungo, e non mi soffermerò oltre: in Internet troverete sicuramente altro materiale per approfondire i vantaggi e gli svantaggi di questo o quell'altro linguaggio di programmazione. Voglio tuttavia attirare l'attenzione del lettore sul fatto che i diversi linguaggi di programmazione simbolici sono tutti comprensibili all'uomo ma, di contro, assolutamente incomprensibili alla macchina. Come può il computer, allora, eseguire i nostri programmi se sono scritti in un linguaggio a lui incomprensibile?
La risposta è semplice: il programma scritto in linguaggio simbolico dovrà essere tradotto in linguaggio macchina. Avremo perciò due versioni dello stesso programma:
Dobbiamo distinguere tre modalità di traduzione:
Appartengono al primo tipo, quello dei linguaggi compilati, il C/C++, il FORTRAN, il Pascal.
Al tipo interpretato appartiene invece il BASIC. Sempre a questo tipo appartengono anche i linguaggi di scripting come il Perl, il PHP, il Python.
Nei linguaggi semi-compilati troviamo invece il Java ed il C#.
Uno dei vantaggi dei linguaggi semi-compilati come il Java è che uniscono una decente velocità di esecuzione al fatto di essere multipiattaforma: lo stesso bytecode può funzionare benissimo su diverse piattaforme e/o sistemi operativi senza dover essere ricompilato purchè esista una virtual machine per quella piattaforma. Torneremo sulla velocità di esecuzione di Java in uno dei prossimi capitoli: Java è un linguaggio lento?
Di contro, i programmi in codice nativo, benchè più veloci in fase di esecuzione, devono comunque essere ricompilati per la specifica piattaforma hardware/software sulla quale devono girare ma i problemi non sono finiti qui: considerate le profonde differenze tra una piattaforma e l'altra ben difficilmente un programma scritto per un dato ambiente hardware e software riesce ad essere compilato su una altra piattaforma se non con sostanziali modifiche al codice sorgente.
NOTA: benchè è prassi comune indicare come compilazione la traduzione in linguaggio macchina di un sorgente scritto in un linguaggio "compilato" come il C, il termine compilazione non è appropriato. Nella sezione Il build di un eseguibile verrà introdotto il concetto di come si ottiene un programma eseguibile da un linguaggio compilato.
Argomento precedente - Argomento successivo - Indice Generale