ATTENZIONE: il nuovo sito di riferimento per questa guida e altre cose più
o meno interessanti è
il mio BLOG
I linguaggi di programmazione hanno come scopo principale quello di aiutare a "tradurre" le entità del mondo reale e i loro comportamenti in strutture informatiche quali variabili, liste, funzioni, puntatori, "struct", ecc. La programmazione a oggetti, OOP, rende molto più naturale creare questa "mappa", permettendo di manipolare oggetti che assomigliano molto alla loro controparte del dominio che si vuole rappresentare.
Una classe è praticamente un tipo di dato, paragonabile (alla lontana) alle struct del C. Una istanza (variabile) di una classe è un oggetto.
Una classe adatta a rappresentare una persona potrebbe essere definita così:
class Persona { String nome, cognome, 5 indirizzo; int nCivico; Persona(String nome, String cognome, 10 String indirizzo, int nCivico) { this.nome = nome; this.cognome = cognome; 15 this.indirizzo = indirizzo; this.nCivico = nCivico; } }Nota: i numeri ad inizio riga servono solo come riferimento, non sono codice java!
Dalla linea 3 si possono vedere dei campi di tipo String e uno di tipo intero.
In linea 8 si vede in azione il costruttore.
Il costruttore ha lo stesso nome della classe e si occupa di inizializzare
l'oggetto - viene chiamato quando si alloca memoria con new
.
Se non si definisce il costruttore per una classe, Java
ne fornisce uno predefinito, senza argomenti.
La parola chiave this è semplicemente un riferimento all'oggetto corrente; in questo caso viene usato per distinguere i campi dell'oggetto dai parametri passati al costruttore, per i quali ho voluto usare gli stessi nomi.
Un programma java inizia la sua esecuzione dal metodo main di una classe. Tutte le classi, ad esclusione delle classi annidate, possono avere un main; ciò è utile per testare la classe durante lo sviluppo, anche se allo scopo esistono strumenti più sofisticati, come JUnit.
In questo caso possiamo inserirlo nella definizione di Persona o in un'altra classe a cui sia visibile:
public static void main(String[] args) { Persona p = new Persona("Mario", "Rossi", 5 "via Di Qua", 5); System.out.println("Il mio nome e' " + p.nome); }Nella prima linea si vede la segnatura del metodo main, che usando varargs può essere sostituita da
public static void main(String... args)
.
In linea 3 viene allocata sullo stack la variabile p - un riferimento a Persona - alla quale viene subito assegnato un oggetto, creato sullo heap.
In linea 8 si accede direttamente al campo nome di p; in generale questa non è una buona cosa - ad esempio si può volere che una volta creato un oggetto Persona i suoi campi non siano modificabili, mentre con la classe definita sopra nulla vieta di scrivere qualcosa come:
p.nome = "Carlo";esattamente come succede con le struct del C.
In questo caso particolare la cosa può risultare abbastanza innocua, ma quando si ha a che fare con un programma di una certa dimensione è meglio non andare a toccare direttamente la struttura interna di un oggetto. C'è quindi bisogno di un meccanismo per controllare l'accesso a campi e metodi.
Modifichiamo leggermente la classe precedente:
class Persona { private String nome, cognome, indirizzo; private int nCivico; public Persona(...In questo modo i campi di una Persona possono essere letti e modificati solamente dai metodi della classe stessa, sono privati.
Il costruttore invece decidiamo di renderlo pubblico - non è obbligatorio - in modo da permettere a tutti di creare oggetti di tipo Persona.
Un costruttore privato ha senso per esempio se si vuole controllare il numero di oggetti creati; allo scopo si userà un metodo statico:
private static int numero = 0; private Persona(... ... /** Crea al massimo 10 persone */ public static Persona creaPersona(String nome, ...) { if(numero == 10) throw new TroppePersoneException(); numero++; return new Persona(nome, ...); }In questo modo il costruttore è accessibile solo all'interno della classe stessa; altre classi che vogliano creare oggetti di tipo Persona lo possono fare solo usando (fino a 10 volte) il metodo creaPersona:
Persona pino = Persona.creaPersona("Pino", ...);
Il senso del discorso precedente è che è
opportuno esporre meno codice possibile.
Ciò che conta non è l'implementazione, ma
l'interfaccia di una classe,
e infatti in java esistono le interfacce.
Ad esempio si supponga di voler definire il comportamento di una pila di numeri
senza specificarne l'implementazione.
Una pila deve permettere come minimo due operazioni, push e pop;
questo fatto si può descrivere così:
interface PilaDiNumeri { public void push(int n); public int pop(); }Per implementare un'interfaccia si usa implements:
class P implements PilaDiNumeri { /* codice... */ public P() { /* codice... */ } public void push(int n) { /* codice... */ } public int pop() { /* codice... */ } /** Ritorna l'elemento in cima * alla pila, senza rimuoverlo. */ public int top() { /* codice... */ } }Notate l'aggiunta di un costruttore e del metodo top - una volta implementati tutti i metodi dell'interfaccia si può arricchire a piacimento la classe.
Inoltre una classe può implementare più interfacce:
class P implements PilaDiNumeri, IterableNel prossimo capitolo si comprenderà la vera utilità delle interfacce, mentre in quello sulle collezioni si vedrà un'implementazione di pila generica.
Quando un campo o un metodo è dichiarato static "fa parte" della classe, e non di un oggetto in particolare.
Come esempio si consideri il precedente metodo creaPersona; non è possibile chiamarlo riferito a un oggetto, così:
/* NO! Persona alfio = gianni.creaPersona(... */ /* si' */ Persona alfio = Persona.creaPersona("Alfio", ...Un altro esempio di metodo statico è main, il metodo che viene eseguito quando si passa il nome di una classe al comando java:
java Persona
Quando una variabile è final non può essere modificata:
final float PI = 3.14f; /* NO! PI = 5; */Nel caso si vogliano usare delle costanti per specificare delle modalità, come in C, dovrebbero essere incluse in una classe fatta più o meno così:
/** Specifica le possibili modalita' * di scrittura di un file. */ class Mode { public final Mode RO = new Mode(), // read only RW = new Mode(), // read - write WO = new Mode(); // write only private Mode() {} }In questo modo è possibile passare a qualche metodo un valore di tipo Mode, che può assumere solo i valori RO, RW, WO, invece di qualsiasi valore intero, come si usa in C.
Questo era necessario fino a java 1.5, che ha introdotto le enum; la singola linea:
enum Mode { RO, RW, WO }si comporta esattamente come la classe precedente.
Una classe può essere definita all'interno di un'altra classe o di un metodo:
class FileDiTesto { ... class LineSeparator { ...Una classe di questo genere viene chiamata inner class o nested class.
Una inner class può essere anonima, nel caso ci sia bisogno di una classe "usa e getta"; come esempio il metodo seguente, definito dall'interfaccia Iterable, ritorna un Iterator, creato "al volo":
Iterator iterator() { return new Iterator() { boolean hasNext() { ... } Object next() { ... } void remove() { ... } } }Iterator è un'interfaccia, che in questo caso viene implementata in una classe senza nome, di cui si ritorna un'istanza.
Quando si chiede all'interprete java di eseguire una classe, ad esempio con
java Personajava esamina il classpath, una lista di directory o di file zip o jar in cui cercare il corrispondente file class; un esempio di classpath può essere
.:main.jar:/usr/local/javaclassesIl classpath può essere definito tramite la variabile d'ambiente CLASSPATH o passato ai tool java mediante l'opzione -cp:
java -cp . Personain assenza di indicazioni la directory corrente, ".", viene sempre inclusa, quindi nel comando precedente l'opzione -cp è superflua.
Vi sarete accorti che il compilatore java produce un file .class per ogni classe che trova nel sorgente, il che può essere scomodo, soprattutto nel caso in cui il vostro programma debba essere distribuito, o scaricato come applet.
Per ovviare al problema nel JDK è incluso il tool jar, che consente di archiviare in un singolo file tutti i .class - e tutti i file di supporto come immagini, suoni, ecc. - relativi a un programma.
Prima di tutto è necessario creare un file di testo, detto manifest, per indicare a jar, tra l'altro, quale è la classe principale che deve essere eseguita; nel caso di prima il file - supponiamo di chiamarlo "mf" - dovrà contenere la riga:
Main-Class: PersonaA questo punto si può impacchettare il tutto con:
jar cvf p.jar -m mf Persona.classed eseguirlo con:
java -jar p.jar
Le classi java possono essere organizzate gerarchicamente in
package.
Ad esempio supponiamo che in una directory inclusa nel classpath -
e che quindi sia visibile a java -
ci sia un'altra directory lib
, in cui è contenuto
il seguente file Ciao.java
:
package lib; public class Ciao { public static void ciao() { System.out.println("ciao ciao"); } }La prima linea dichiara che la classe Ciao fa parte del package lib.
Dopo aver compilato il file possiamo usare la classe Ciao da qualsiasi programma, direttamente con:
lib.Ciao.ciao();oppure importando la classe, in testa al file sorgente, un po' come si fa in C con la direttiva
#include
:
import lib.Ciao; ... Ciao.Ciao(); ...La libreria standard di java si usa proprio così, importando le classi desiderate, ad esempio:
import java.util.*; // importa tutte le classi del package java.utilIl package
java.lang
viene importato automaticamente, quindi
si può usare la classe String, per esempio, senza importare alcunché.
Prima abbiamo visto come controllare l'accesso ai membri di una classe tramite public e private. Se non si usano queste keyword, ad esempio:
int max(...) { ... }il metodo o il campo è accessibile solo all'interno dello stesso package della classe in cui è dichiarato.
Se in un file sorgente non viene dichiarato il package le classi contenute in quel file apparterranno al package di default, ossia al package di cui fanno parte le classi presenti nella stessa directory - a meno che queste non abbiano dichiarato di appartenere ad un altro package.