La modalità di creare oggetti che vediamo in questo capitolo è quella che ci permette sia di scrivere meno codice ripetitivo che di (potenzialmente) avere meno errori creando modelli della realtà e utilizzando parti di programma già scritte.
Le persone, gli studenti e i professori
Una realtà che ci è familiare è quella scolastica: vogliamo rappresentare
le persone che hanno a che fare con la scuola tramite la classe Persona
.
Queste persone possono essere i commessi del bar, il personale negli uffici,
quelli che fanno il servizio al corridoio, il dirigente e così via.
Di queste persone ci interessa il nome, il cognome, l'anno di nascita e il numero di telefono.
La classe, con relativi costruttori, sarà una cosa del tipo:
public class Persona {
public String nome;
public String cognome;
public int annoNascita;
public String telefono;
public Persona() {
nome = "indefinito";
cognome = "indefinito";
annoNascita = -1;
telefono = "indefinito";
}
public Persona(String nome, String cognome, int annoNascita, String telefono) {
this.nome = nome;
this.cognome = cognome;
this.annoNascita = annoNascita;
this.telefono = telefono;
}
}
Prendiamo in considerazione ora gli alunni: senza dubbio sono persone! Sono persone un po' particolari perché oltre le caratteristiche comuni a tutte le persone, questi frequentano una classe, possono frequentare le lezioni di religione oppure di attività alternativa e quindi possiamo individuare le nuove proprietà: la prima è la classe frequentata, l'altra è la situazione in merito alla religione. Nei linguaggi ad oggetti possiamo indicare il fatto che gli studenti sono persone con particolari caratteristiche tramite il concetto di ereditarietà: si va a creare una nuova classe, in questo caso Studente, e questa non la si fa a partire da zero ma si fa in modo che abbia già in partenza tutto ciò che è nella classe Persona. In altre parole la classe Studente estende la classe Persona. Esplicitiamo tutti questi concetti tramite il seguente:
public class Studente extends Persona {
public String classe;
public boolean religione; // false in caso si avvalga dell'attività alternativa
public Studente() {
super(); // Per la spiegazione di super() guarda il paragrafo che segue
classe = "non assegnata";
religione = true;
}
public Studente(String nome, String cognome, int annoNascita, String classe, boolean religione) {
super(nome, cognome, annoNascita, "");
this.classe = classe;
this.religione = religione;
}
}
In questo esempio Persona
è la superclasse
(o classe padre) di Studente
che viene detta classe figlia.
La parola super
sta ad indicare la superclasse,
la chiamata del metodo super()
è la chiamata del costruttore della superclasse.
Si utilizza super()
in modo tale da invocare il costruttore per le proprietà definite
nella superclasse (che magari potrebbero essere private),
lasciando qui il solo lavoro di dare un valore alle ultime definite.
Si osserva che ci sono 2 invocazioni di super()
: una senza argomenti per chiamare
il primo costruttore di Persona
, quello senza argomenti
e l'altra con quattro argomenti per chiamare il secondo costruttore di Persona
, quello con quattro argomenti.
Osserviamo che nelle 2 classi precedenti tutti i metodi e le proprietà sono definiti pubblici.
Nel capitolo oggetti, al paragrafo Organizzare gli oggetti, si diceva però
che lasciare le proprietà e alcuni particolari metodi liberamente usabili da altre classi potrebbe
non essere un bene. Mettendo il modificatore private
sulle
proprietà della classe Persona
si otterrebbe un errore in quanto la classe estesa non potrebbe
accedere alle proprietà della classe padre. La classe derivata è a tutti gli effetti
un'altra classe e quindi nulla di ciò che è definito privato traspare.
La soluzione viene nella parola chiave protected
:
sta ad indicare che quanto segue (metodo o proprietà) è visibile solo a tutti i metodi della classe stessa
(ovviamente), a tutte le classi che estendono questa e a tutte quelle classi presenti nello stesso pacchetto.
Nel momento in cui andremo a dichiarare oggetti, dovremo porre attenzione al livello di visibilità di ogni
metodo e proprietà.
La classe persona a questo punto sarà:
public class Persona {
protected String nome;
protected String cognome;
protected int annoNascita;
protected String telefono;
public Persona() {
nome = "indefinito";
cognome = "indefinito";
annoNascita = -1;
telefono = "indefinito";
}
public Persona(String nome, String cognome, int annoNascita, String telefono) {
this.nome = nome;
this.cognome = cognome;
this.annoNascita = annoNascita;
this.telefono = telefono;
}
/* da aggiungere i metodi get/set per le proprietà */
}
Una ulteriore attenzione nel momento della progettazione di una classe va fatta su quali proprietà e metodi si vuole siano visibili alle classi che estenderanno la classe che ora stiamo costruendo. Può essere che alcune proprietà e/o metodi preferiamo renderli privati e quindi la classe che andrà ad estenderla non potrà usarli direttamente.
Allo stesso modo si potrà realizzare la classe Professore
estensione di Persona
che tiene conto di due caratteristiche
aggiuntive: la materia insegnata e lo stipendio.
public class Professore extends Persona {
protected String materia;
private double stipendio;
public Professore() {
super();
materia = "?";
stipendio = 0;
}
public Professore(String nome, String cognome, String materia, double stipendio) {
super(nome, cognome, -1, "");
this.materia = materia;
this.stipendio = stipendio;
}
// permette di leggere la proprietà stipendio che è privata
public double getStipendio(){
return stipendio;
}
// volutamente non c'è setStipendio
}
Chi ha progettato la classe Professore
ha ad esempio deciso che
lo stipendio una volta creato l'oggetto non si può più modificare
ma si può soltanto leggere.
In sintesi
Quando quello che vogliamo fare non è creare una nuova classe ma aggiungere/modificare delle funzionalità di una oggetto esistente va usata la parola
chiave extends
nella dichiarazione della classe seguita dal nome della classe che si vuole ampliare/cambiare. In questo caso l'oggetto definito avrà
tutte le proprietà e tutti i comportamenti di quello originale con le aggiunte o le modifiche indicate nella classe.
In caso che la parola chiave
extends
non sia presente la classe che si sta definendo extenderà
java.lang.Object
. Ovviamente questo meccanismo forma delle catene:
la classe A estende la classe B che a sua volta estende C e così via. Quando
si invoca un metodo nell'oggetto di tipo A questo potrebbe essere definito
sia in A... che in B che in C. Java cercherà prima il metodo in A,
se non c'è guarderà in B, se non c'è guarderà in C e alla fine in Object.
super
è il riferimento alla classe padre o superclasse e quindi
super()
è il riferimento al relativo costruttore, all'interno delle parentesi ci
saranno gli argomenti del caso.
Quanti tipi ha un oggetto?
La risposta immediata è: uno! Purtroppo però non è del tutto vero: ovviamente un oggetto è del tipo che si è scritto nella dichiarazione:
Studente mario = new Studente();
mario
è di tipo Studente
ma... non è anche una
Persona
? Si, lo è, in effetti è perfettamente legale scrivere sotto alla riga precedente
Persona anonimo = mario;
In questo modo però utilizzando la variabile anonimo
potrò utilizzare soltanto i metodi definiti in Persona
o una sua superclasse,
non più quelli di Studente
.
È altresì possibile usare la variabile mario
in tutti i contesti in cui
serve di usare una Persona
. Questo spiega perché usando JavaFX per aggiungere
un elemento ad una griglia usando il metodo add()
si possono inserire molte cose
diverse, la dichiarazione del metodo dice:
add(Node figlio, int indiceColonna, int indiceRiga)
Sia Button
che Label
sono sottoclassi di Node
quindi possono essere utilizzate nel metodo add, in verità Label (e neanche Button)
non è una sottoclasse diretta di Node, la gerarchia completa è:
Object→Node→Parent→Region→Control→Labeled→Label Però vista la gerarchia
Label
è anche un Node
.
Esercizio
Primo.java
package it.esempio;
public class Primo {
boolean p;
int x;
public int leggiVal(){
return x;
}
public String descrivi(){
return "n:"+x;
}
}
Secondo.java
package it.esempio;
public class Secondo extends Primo {
String c;
public Secondo(int k, boolean p, String u){
x=k;
y=p;
c=u;
}
public String descrivi(){
return c+x+c;
}
}
Considera il seguente frammento di programma inserito in un'altra classe:
Secondo o;
o = new Secondo(3,false,"#");
String m=o.descrivi();
Quanto vale m?