Primi passi con java

Premessa

Questo è il primo di una serie di post che hanno come obbiettivo la descrizione delle principali caratteristiche del linguaggio di programmazione java. Per scelta si è deciso di tralasciare completamente la specifica formale dei vari aspetti relativi alla sintassi del linguaggio, in quanto questa sarà evidente dagli esempi che saranno presentati durante i vari tutorial.

Introduzione

Il linguaggio Java è un linguaggio di programmazione sviluppato originariamente dalla SUN Microsystem, allo scopo di creare un linguaggio estremamente semplice e facile da apprendere. Nello sviluppo del linguaggio gli ingegneri della SUN seguirono diverse linee guida che sono tutt’oggi le caratteristiche principali di java.

La prima e più importante è che il codice compilato, detto bytecode, è eseguibile su ogni tipologia di piattaforma (hardware e software) senza necessità di ricompilazione. Questo perché il bytecode è eseguito all’interno di una macchina virtuale detta Java Virtual machine (JVM) che attualmente è disponibili per ogni tipo di sistema operativo e per un numero molto elevato di hardware differenti.

Java è un linguaggio a oggetti (OO) puro, ovvero non è possibile programmare in modo non object oriented, come invece accade ad esempio con il C++ dopo possono coesistere elementi OO ed elementi procedurali. Java quindi fornisce i costrutti necessari alla implementazione di ereditarietà e polimorfismo, ma non supportando concetti più complessi e “pericolosi” come l’ereditarietà multipla.

La gestione della memoria, per motivi di sicurezza e di semplicità di programmazione, è effettuata dalla JVM per mezzo di un efficiente Garbage Collector, il quale si occupa dell’allocazione e  deallocazione delle aree di memoria svincolando di fatto il programma da tale gestione (differentemente da altri linguaggi come C++).

infine una caratteristica fondamentale di Java è che esso mette a disposizione un meccanismo per il multithreading, attraverso il quale è possibile, all’interno di una stessa applicazione, eseguire contemporaneamente più task in parallelo.

Ma il vero successo di java è stato determinato dall’introduzione da parte di SUN della piattaforma J2EE (Java 2 Enterprise Edition) proponendo una architettura completamente open source (non proprietaria) per lo sviluppo orientata al “Server Side Computing”. Tale piattaforma, oltre ad ereditare le caratteristiche di java, fornisce:

  • Un modello di sviluppo semplificato per la realizzazine di applicazioni web;
  • Una architettura altamente scalabile – La piattaforma fornisce la scalabilità;
  • La piattaforma consente l’integrabilità di soluzioni pre esistenti in ambienti legacy;
  • Uno standard aperto per specifiche e portabilità;
  • Un modello di sicurezza in grado di proteggere dati in applicazioni Internet;

Struttura di un programma

Un’applicazione java può essere composta da una o più classi che cooperano tra loro. Tra tali classi la più importante è quella che contiene il metodo main che è il punto di ingresso di esecuzione del programma. Un semplice programma Java, formato da una sola classe, assume la seguente struttura:

Una classe java è definita per mezzo della parola chiave class, ed il nome deve coincidere con il nome del file memorizzato su disco (nel nostro casoNomeClasse.java). La keyword public ne definisce la visibilità, in questo caso, totale da parte di altre porzioni di codice. Si tratta di uno specificatore di accesso al quale si aggiungono quelli protected e private.

La classe è poi seguita da un blocco di codice contenuto all’interno delle parentesi graffe in cui sono dichiarate le variabili, i metodi ed in casi particolari altre classi.

Per quanto riguarda le regole di naming delle variabili e metodi, il formalismo introdotto da Sun prevede che tutti nomi di classi devono iniziare per lettera maiuscola (così come tutte le lettere iniziali delle parole composte), mentre i nomi di variabili e dei metodi devono seguire la forma canonica, ovvero prima lettera minuscola (e per le parole composte le iniziali maiuscole).

Nella nostra classe di esempio è poi presente anche il metodo main, necessario all’esecuzione del programma.  Tale metodo possiede come parametro un array di stringhe, args[], che corrisponde ai parametri passati dalla riga di comando quando viene eseguita l’applicazione.

Interfacce, classi astratte ed ereditarietà

Uno dei fondamenti alla base della programmazione OO è l’ereditarietà. In java l’ereditarietà si implementa estendendo classi concrete o astratte oppure implementando interfacce.

Una classe astratta è simile ad una classe concreta, ma non implementa almeno uno dei suoi metodi. Tali classi hanno la parola chiave abstract nella lodo dichiarazione ed eventualmente in uno dei metodi che dichiara. Dal punto di vista formale può avere attributi e metodi implementati ma non può essere istanziata. Il suo scopo infatti è quello di imporre dei metodo alle sottoclassi che la estendono mettendo a fattore comune una logica specifica che essa implementa.

 Per specificare che la classe A eredita o deriva da B si utilizza la parola chiave extends:

È importante notare che in Java ogni classe in cui non sia definito esplicitamente un padre, eredita automaticamente dalla classe Object (è il compilatore che introduce automaticamente la relazione).

Un’interfaccia è analoga ad una classe ma, a differenza di questa, si limita a dichiarare i metodi, senza implementarli (tutti i metodi di un’interfaccia sono implicitamente abstract). Formalmente si utilizza quando si vuole fornire una specifica di un comportamento. In una interfaccia possono essere definite costanti (cioè variabili static
final), ma non variabili.

La sintassi per implementare una interfaccia prevede l’utilizzo della parola chiave implements.

Come mostrato nell’esempio una classe può implementare più interfacce ma può estendere solamente una classe astratta o concreta. Questo perché in java è stata eliminato il concetto di ereditarietà multipla e l’utilizzo delle interfacce permette di realizzare strutture gerarchiche più o meno complesse pur mantenendo la regola di base: “un solo padre per ogni figlio”.

I package

Un package è un contenitore che può raccogliere al suo interno una serie di classi o altri package. Strutturalmente seguono il modello delle directory del filesystem, le quali possono contenere sia file che altre directory. Scopo dei package è di mettere ordine nel codice, suddividendolo logicamente e raggruppandolo in classi affini. Oltre a migliorare organizzazione per progetto essi  impedendo situazioni di conflitti tra nomi.

L’organizzazione delle classi secondo una logica a package comporta il salvataggio dei file corrispondenti in una struttura gerarchica del file system isomorfa a quella dei package, utilizzando i medesimi nomi. Quindi se una classe è contenuta nel package it.inspiredsoft.utils.NomeClasse allora dovrà essere salvata in una directory secondo la struttura it/inspiredsoft/utils/.

A seguito dell’utilizzo dei package, una classe che deve utilizzare un’altra classe esterna al proprio package la deve importare esplicitamente, utilizzando la parola chiave import,  o utilizzarne il nome completo per referenziarla.

Per creare un package è necessario utilizzare la keyword package come prima riga della nostra classe.

Anche il JDK è organizzato in package. I più importanti sono:

  • java.lang: classi base del linguaggio, delle quali non è necessario effettuare l’importazione essendo automatica (Object, Thread, Throwable, System, String, Math, etc.);
  • java.io: classi per la gestione dell’I/O (FileInputStream, FileOutputStream);
  • java.util: classi di utilità (Date, Random, …);
  • java.net: classi di supporto alle applicazioni di rete (socket, URL, …);

Specificatori di accesso

Come visto precedentemente java prevede tre possibili specificatori di accesso che possono essere applicati a classi, metodi o variabili, secondo le seguenti modalità:

  • public: applicato ad una proprietà o a un metodo all’interno di una classe A gli conferisce visibilità totale. Sarà quindi visibile al di fuori della classe A, sia dalle classi che la estendono, sia da quelle che la utilizzano importandola, anche se esterne al package di A.
  • private: questo specificatore è di fatto il simmetrico di public, ovvero blocca ogni tipo di visibilità. Metodi o proprietà della classe A dichiarati privati hanno visibilità limitata alla sola classe A, nemmeno le classi che la estendono possono accedervi.
  • protected: i metodi e le proprietà dichiarate protette potranno essere solamente da classi che appartengono allo stesso package. All’esterno del package nessuno potrà accedere a tali elementi, mentre le classi figlie li erediteranno.

E’ possibile anche non specificare alcun modificatore di accesso. In questo caso siamo nella situazione di default dove la visibilità si limita alla classe e al package.

Nell’estendere una classe non è possibile modificare la visibilità dei metodo e delle proprietà ereditate.

I modificatori static e final

Il significato del modificatore static è differente a seconda se applicato ad un metodo o ad una proprietà. Applicato ad una proprietà comporta che tale proprietà sarà condivisa fra tutte le istanze di tale classe. In altri termini la variabile occupa la medesima area di memoria indipendentemente dalla specifica istanziazione di classe.

Lo specificatore static applicato invece a un metodo indica che esso può essere invocato anche se la classe di cui fa parte non è stata istanziata. Un esempio è il metodo main descritto precedentemente. All’interno di un metodo statico sarà possibile invocare esclusivamente altri metodi statici della stessa classe o accedere a variabili statiche.

Anche lo specificatore final assume un significato differente a seconda che venga abbinata a un metodo o a una proprietà. Nel secondo caso infatti definisce un valore costante che non può più essere modificato. Ad esempio:

Invece un metodo definito come final non può essere sovrascritto (override) nelle eventuali classi figlie.

Esempio pratico

A conclusione di questo post presentiamo un esempio pratico, l’implementazione della classe Punto. Limitandoci al caso bidimensionale, tale classe sarà caratterizzata da due coordinate X ed Y. Una possibile implementazione è la seguente:

Le due proprietà a ed y sono state definite private, in accordo con il principio dell’information hiding. Questo implica che il solo modo per accedervi e modificarle è attraverso i metodi della classe. La loro valorizzazione infatti avviene esclusivamente attraverso il costruttore della classe.

Supponiamo ora di voler determinare la distanza tra due punti. Per farlo decidiamo di creare un metodo statico all’interno della classe Point. Il risultato sarà:

Nel codice abbiamo utilizzato una classe di utilità Math appartenente alla JDK e che contiene metodi per l’esecuzione di operazioni matematiche di base.

Inoltre notiamo che, nonostante le proprietà a ed y siano private, il metodo statico può accedervi perché definito all’interno della setta classe Point.

Scriviamo ora il codice necessario per l’utilizzo della classe Point e per l’invocazione del metodo per il calcolo della distanza.

Per la istanziazione della classe si utilizza l’operatore new seguito dal nome della classe e dai parametri formali di invocazione del solo costruttore definito. Per l’invocazione del metodo distance è sufficiente riferirsi alla classe in quanto l’operazione non necessita che vi sia una istanza della stessa. L’output prodotto è:

Distanza tra (4.0,6.0) e (9.0,12.0) = 7.810249675906654