Java Cap1
Java Cap1
Dalla quercia alla tazzina di caffè - L’attività della scrittura del codice non lascia molto spazio alla
fantasia dei programmatori. Fatta eccezione per poche cose, come i nomi delle variabili e delle funzioni,
il resto del codice ha sempre parole chiave e costrutti obbligati in modo rigido dalla sintassi. Un’altra
delle cose su cui i programmatori possono dare sfogo alla fantasia è il nome da dare ad un linguaggio:
se il linguaggio C non ha un nome particolarmente fantasioso (il suo predecessore è il linguaggio B…), la
storia del nome del Java è più interessante. Originariamente il nome che Gosling diede a questo
linguaggio fu Oak, che significa quercia: infatti dalla finestra del suo ufficio Gosling vedeva proprio una
bella quercia. Purtroppo Oak era già il nome di un altro linguaggio, per cui Gosling decise di cambiare il
nome in Java. Java è una qualità di caffè proveniente dall’omonima isola indonesiana, quello che veniva
servito nel bar frequentato da Gosling e i suoi colleghi e nel quale avvenivano le discussioni sulle
caratteristiche del linguaggio che stava nascendo. Se si osserva un file prodotto in Java, si può notare
che il suo magic number (la sequenza di byte identificativa del tipo di file) è in esadecimale 0xCAFEBABE.
1
Corso di Informatica 2 – Francesco De Vincenzi
Una delle caratteristiche che si voleva dare al linguaggio Java era quello di essere “più semplice”.
Scopriremo che il Java non è affatto semplice, nel senso che ha un’infinità di estensioni, librerie, strumenti
e di conseguenza può essere utilizzato in innumerevoli situazioni e campi di applicazione. Ma Java avrebbe
dovuto essere più intuitivo, contenere meno tecnicismi ed essere più vicino al modo di pensare di un essere
umano. Se pensiamo ai vari aspetti di un linguaggio di programmazione in relazione alla loro vicinanza o
lontananza rispetto all’effettivo funzionamento di un calcolatore, metteremmo il C più in basso ed il Java
più in alto: il C consente al programmatore di fare operazioni che hanno una azione diretta su processore e
Ram: si pensi ai puntatori, che permettono di accedere a una posizione della memoria indicandone il valore
oppure si pensi all’istruzione GOTO (che è tanto sconsigliata, ma in C comunque esiste) per saltare
direttamente ad un punto di un programma, quindi cambiare di fatto il valore del Program Counter senza
passare per una istruzione controllo e di salto condizionato. Java non consente al programmatore di fare
queste operazioni (i puntatori in realtà ci sono, come scopriremo, ma sono “nascosti” al programmatore e
soprattutto su di essi non sono permesse operazioni aritmetiche. Viceversa il Java, come vedremo, è un
linguaggio orientato agli oggetti (da cui la sigla OOP, Object Oriented Programming), il che significa che il
programmatore è invitato a organizzare il suo programma ragionando sugli elementi che lo compongono
pensandoli come oggetti, concreti o astratti, così come li osserva nella realtà: una persona, un tavolo, un
contenitore, un contratto di lavoro, una bicicletta, una partita di calcio…
Il concetto che stiamo analizzando si chiama astrazione: l’astrazione è una proprietà che indica il livello di
vicinanza o lontananza di un pensiero logico rispetto alla sua realtà fisica. Facciamo un esempio: un
programmatore deve realizzare un programma per la gestione degli acquisti di merce in un negozio. Se il
programmatore ragiona in termini di byte che vengono manipolati dal processore, è a livello di astrazione
basso; se invece ragiona pensando all’ insieme delle merci e alle persone le quali acquistano dei beni, è a
livello di astrazione alto.
Questo schema mostra come mettere in scala vari linguaggi di programmazione in base al loro livello di
astrazione; per ogni linguaggio c’è un frammento di codice esemplificativo:
Livelli di astrazione
Ciascun linguaggio ha un insieme di strumenti che il programmatore può usare per realizzare il proprio
codice. Il C, ad esempio, ha i tipi (short, int…), i puntatori, le strutture dati (array, struct,…) le funzioni, e così
via. Quelli sono gli strumenti base che il programmatore tiene a mente quando si accinge a realizzare un
programma. Avendo questi strumenti, il programmatore sarà indotto ad organizzare il programma in una
determinata maniera, che sarebbe stata diversa con altri linguaggi ed altri strumenti. Questo concetto
viene indicato col termine paradigma di un linguaggio di programmazione.
Esistono molti paradigmi diversi, i quali hanno avuto più o meno fortuna nel corso degli anni: alcuni sono
più specifici per determinati settori della programmazione, altri più general purpose, come quello del C.
Vediamo in questa tabella i più comuni paradigmi:
Dunque, Java è un linguaggio orientato agli oggetti. Tale paradigma è spesso indicato con la sigla OOP, che
sta per Object Oriented Programming. Per studiare le caratteristiche di questo paradigma dedicheremo
diversi capitoli.
3
Corso di Informatica 2 – Francesco De Vincenzi
Un programma scritto in C consiste in generale di un listato unico: dopo aver importato le varie librerie, il
programmatore scrive tutte le funzioni di cui ha bisogno, per poi terminare col programma principale. Tutto
il programma viene salvato in un unico file con estensione .c (o .cpp). In Java il programma viene strutturato
in modo diverso: il programmatore individua gli elementi che dovranno interagire in esecuzione e per
questi ne dà una descrizione. Questi elementi potranno essere di tipo diverso: per ciascuno di questi tipi,
scriverà un file con il codice che lo implementa (con estensione .java). Anticipiamo che questo codice
prende il nome di classe. Quindi non c’è una situazione di “programma principale” e “allegati”, ma di
insieme di componenti che interagiscono. Lo schema che segue rappresenta graficamente questa
differenza tra il C ed il Java:
Libreria1 Libreria1
Libreria2
Classe Java
Classe Java (file.java)
(file.java)
Programma C
(file.c)
Classe Java Classe Java
(file.java) (file.java)
Il termine modulo (e quindi la modularità, ovvero la proprietà di essere modulare) si riferisce proprio alla
caratteristica di un programma Java di essere pensato non come un blocco unico, ma come un insieme di
moduli separati che interagiscono; tali moduli vengono chiamati appunto classi. I vantaggi della modularità
sono molti:
- riuso: una classe appartenente ad un progetto già realizzato può essere copiata ed inserita in un
nuovo progetto
- lavoro in team: la modularità facilita la divisione logica dei compiti, per cui in un team di
sviluppatori ciascuno può lavorare a classi diverse, dopo aver concordato con il resto del team in
che modo le classi devono interagire
- controllo errori: se un programma è scomposto in porzioni più piccole, è più semplice analizzare e
testare i singoli blocchi di codice alla ricerca di eventuali bug
- estensione: con un meccanismo che affronteremo più avanti, detto ereditarietà, sarà possibile
realizzare un classe “evolvendo” una classe già esistente
4
Corso di Informatica 2 – Francesco De Vincenzi
Questo è uno dei punti di originalità del linguaggio Java ed anche il motivo della sua immediata popolarità e
versatilità di impiego in vari campi.
Abbiamo discusso di come il linguaggio C sia a livello di astrazione più vicino alla macchina rispetto a Java.
Un programma scritto in C può fare operazioni anche di basso livello sui singoli bit della memoria. Questo
forte legame del linguaggio con la macchina su cui viene eseguito è un punto di forza per le prestazioni del
programma, che in sostanza è stato scritto e compilato proprio per quella macchina su cui va in esecuzione,
ma ne è anche un limite nel senso che il programma eseguibile generato da quella compilazione non è
utilizzabile su una architettura (hardware+sistema operativo) diversa da quella. In parole povere, se
compilo un programma C su un PC con installato sopra Windows 10 poi non posso prendere l’eseguibile (il
.exe) generato ed eseguirlo su un PC con Linux e a maggior ragione su un McIntosh con MacOS. Quello che
devo fare è riprendere il codice sorgente che ho scritto e ricompilarlo sulla nuova piattaforma. Infatti il
compilatore effettua una conversione delle istruzioni adatte al set di istruzioni del processore su cui si trova
ed effettua dei collegamenti tra il programma e le librerie del sistema operativo installato sulla macchina
stessa.
Gli anni in cui è stato sviluppato il Java sono anche gli anni in cui iniziava ad affermarsi il Web, ed in tale
contesto in cui diverse piattaforme venivano messe in contatto era necessario un linguaggio che potesse
essere indifferentemente eseguito su tutte le apparecchiature connesse alla rete. Java al suo tempo vinse la
sfida, con un’idea semplice ma efficace: il codice sorgente Java viene compilato e trasformato in un codice
che può essere eseguito non da un processore reale, ma da un programma che emula un processore, una
macchina virtuale. Sviluppando su ogni tipo di piattaforma una macchina virtuale adatta, si riesce a far
eseguire lo stesso programma Java su tutte le piattaforme. Ecco uno schema che sintetizza il tutto:
Codice sorgente
JAVA
(file .java)
Compilazione
Bytecode
(file .class)
Interpretazione
Il codice sorgente Java scritto da un programmatore viene salvato in uno (o più) file di testo con estensione
.java. Il programma viene compilato dal compilatore java, il quale produce un file con estensione
.class per ciascun file sorgente compilato. Questo file .class contiene il cosiddetto bytecode, ovvero
una serie di istruzioni scritte in un linguaggio di basso livello, paragonabile ad un linguaggio macchina. Il
fatto è che però queste istruzioni non sono destinate al processore(che su ogni tipo di macchina ha un
instruction set); in realtà queste istruzioni sono date in pasto ad un programma, chiamato macchina
virtuale java (JVM: Java Virtual Machine), il quale le interpreta ad una ad una, eseguendo le corrispondenti
operazioni sul processore reale su cui è in esecuzione. Quindi esiste una JVM diversa per ciascuna
architettura: Windows 32 bit, Windows 64 bit, Linux, Unix, MacOs, Android,… Ciascuna JVM si può pensare
divisa in due parti: una parte “superiore”, uguale per tutte, è quella che analizza il bytecode per capire cosa
deve eseguire; una parte “inferiore”, diversa per ogni architettura, che conosce la macchina su cui è in
esecuzione ed attiva le operazioni corrette del processore.
Le prime JVM erano degli interpreti a tutti gli effetti: le istruzioni venivano preparate al volo al momento
dell’esecuzione. Questo è però sempre stato causa di lentezza nell’esecuzione di applicazioni Java. Oggi
le JVM hanno un compilatore JIT (Just in Time), ovvero un programma che al momento dell’esecuzione
traduce tutto il bytecode in un programma eseguibile. Questa scelta richiede qualche istante in più in
fase di avvio, ma prestazioni migliori nel corso dell’esecuzione di un programma Java.
Grazie a questa versatilità, a Java viene associato lo slogan WORE: Write Once, Run Everywhere (“scrivi una
volta, esegui dappertutto”). In effetti il primo effetto tangibile della portabilità di Java sono state le Applet:
piccoli programmi grafici scritti in Java, che venivano eseguiti all’interno di pagine Web, quindi scritte
appositamente per essere eseguite su qualsiasi dispositivo. Oggi le Applet sono di fatto scomparse,
sostituite prima da applicazioni Flash, poi da HTML5 e Javascript, ma Java offre diversi altri strumenti
software e resta un linguaggio, come recita lo splash screen di installazione, eseguito su “bilioni di
dispositivi”: computer laptop, desktop, tablet, palmari, smartphone etc.
Il programma java.exe dovrebbe essere richiamabile da qualsiasi posizione nel pc (cioè, non bisogna
navigare fino alla cartella in cui è installato) perché il percorso del file viene inserito nella variabile
d’ambiente PATH del sistema operativo (se java.exe non viene eseguito, un problema possibile è proprio
l’assenza del percorso di java.exe nel PATH).
Il compilatore Java si trova nella stessa directory e, come detto, si chiama javac.exe. Per compilare un file
java basta avviare il programma javac specificando il nome del file java da compilare (ad esempio: javac
prova.java).
Se il sistema operativo non trova questo file, è possibile che Java non sia installato completamente sul
proprio computer. In questo caso occorre scaricare (gratuitamente) l’ultima versione di Java dal sito della
Oracle: l’ultima versione rilasciata è la 12.