Abbiamo visto come definire nuovi tipi di dato.
In alcuni casi, abbiamo già un tipo che va quasi bene, ma manca qualcosa.
Esempio: Point è un punto sul piano, manca la coordinata z
Point p; p=new Point(); p.z=12; // non funziona: i punti non hanno // la componente z
Vediamo come si fa ad aggiungere componenti a tipi già definiti.
Si parte da una classe già definita
Si elencano le componenti che mancano.
Nel nostro caso, manca una componente int di nome .z
Quando si estende una classe, in realtà di crea una classe nuova.
Non si può usare il nome di un tipo già esistente.
Nel nostro caso, non si può usare Point
Scegliamo per esempio il nome SPoint
Il nome del nuovo tipo è SPoint
Sappiamo quali sono le componenti.
Questo è un programma che usa la classe.
import java.awt.*; class TriDist { public static void main(String args[]){ SPoint p, g; p=new SPoint(); g=new SPoint(); p.x=10; p.y=12; p.z=5; } }
Vogliamo definire la classe in modo che questo programma sia corretto.
Voglio dire una cosa del genere:
Crea nuova classe SPoint, che ha le stesse cose di Point piu': | componente intera di nome .z
Crea nuova classe SPoint, che ha le stesse cose di Point piu': | componente intera di nome .z
Nel linguaggio di programmazione, questo si traduce cosí:
class SPoint extends Point { int z; }
Il tipo Point viene usato
quindi occorre fare import java.awt.*;
Definizione completa:
import java.awt.*; class SPoint extends Point { int z; }
Va messa in un file SPoint.java
Si compilano tutti i file
javac SPoint.java javac TriDist.java
Il file che contiene le istruzioni è TriDist
java TriDist
Informalmente, abbiamo detto:
Formalmente, questo meccanismo si chiama ereditarietà
Si crea una nuova classe; quella di partenza resta inalterata.
Si può ancora usare la classe di partenza, che non è stata modificata.
import java.awt.*; class DuePunti { public static void main(String args[]){ SPoint p; p=new SPoint(); p.x=12; p.y=3; p.z=-43; Point f; f=new Point(); f.x=12; f.y=3; // f.z=-43; e' un errore: gli oggetti // Point non hanno questa componente } }
Il tipo Point si comporta come al solito.
Data la definizione, passare al tipo
class SPoint extends Point { int z; }
Traduzione meccanica:
Crea nuova classe SPoint, che ha le stesse cose di Point piu': | componente intera di nome z
Dal momento che Point ha le due componenti x e y
Crea nuova classe SPoint con | componente intera di nome x | componente intera di nome y | componente intera di nome z
Siano date le due classi seguenti:
Abcd.java | Efgh.java | |||
|
|
Dire come sono fatti i dati di tipo Abcd e Efgh
Scrivere un programma che usa queste due classi.
Gli oggetti di tipo Abcd hanno
due componenti
x ed y, di tipo intero.
Quando si crea una classe che estende un'altra,
quella precedente resta inalterata
Se a è di tipo Abcd,
allora a.d e a.g non esistono.
Efgh.java crea una nuova classe,
non modifica la classe Abcd
Gli oggetti della nuova classe hanno le stesse componenti di Abcd, più d reale e g intero.
Efgh.java equivale a dire:
Crea un nuovo tipo Efgh con | componente intera di nome x | componente intera di nome y | componente reale di nome d | componente intera di nome g
Le prime due componenti sono definite in Abcd.java
Le ultime due componenti sono definite in Efgh.java
Sia Abcd che Efgh sono tipi validi.
Posso definire variabili e creare oggetti di questi due tipi.
class Prova { public static void main(String args[]){ Abcd a; a=new Abcd(); Efgh b; b=new Efgh(); // a ha le componenti x e y a.x=12; a.y=3; // a.d=10.2; e' un errore! // b ha anche d g b.x=4; b.y=-1; b.d=10.2; b.g=3; System.out.println(a.x); } }
Notare che a.d=10.2 è un errore:
gli oggetti di tipo Abcd non hanno la
componente d
Questa componente c'è solo per la classe
Efgh
(che estende Abcd)
Estendere la classe Studente in modo che includa il nome del corso di laurea (es. Ingegeria Gestionale, ecc.) e il numero di esami sostenuti.
Nuova classe = nuovo nome
Scelgo NuovoStud
Componenti: gli stessi di Studente più corso ed esami
crea una nuova classe NuovoStud con tutte le parti di Studente e | componente String di nome corso | componente int di nome esami
Basta un qualsiasi programma che usa la classe.
class DBUniv { public static void main(String args[]){ NuovoStud x; x=new NuovoStud(); x.nome="Ciccio"; x.media=18; x.corso="Ing. gestionale"; x.esami=2; System.out.println(x.nome); System.out.println(x.media); } }
La nuova classe ha due componenti in più rispetto a Studente
Si ottiene dicendo quali campi in più ci sono rispetto alla classe che viene estesa.
class NuovoStud extends Studente { String corso; int esami; }
In ordine:
javac Studente.java javac NuovoStud.java javac DBUniv.java java DBUniv
Solo l'ultimo file contiene le istruzioni.
I metodi vengono ereditati
Fanno letteralmente le stesse cose!
I metodi non vengono estesi
Per il programmatore può essere ovvio che va cambiata anche la z
Per il calcolatore non ci sono cose ovvie
Perchè?
Il calcolatore non sa quale è il
significato delle componenti
(per lui sono solo zone di memoria)
La componente z potrebbe indicare il colore,
per quello che ne sa lui
(e quindi move non la deve modificare)
Vedremo poi come modificare i metodi
Estendere la classe Rectangle in modo che ogni rettangolo contenga anche il punto centrale (usare Point)
Voglio una classe che ha tutte le componenti di Rectangle più un punto.
Crea nuova classe RectPoint con tutte le componenti di Rectangle e: | componente Point di nome centro
Dato che la classe è cosí semplice, la traduciamo subito:
import java.awt.*; class RectPoint extends Rectangle { Point centro; }
Attenzione! È un oggetto con dentro una variabile di tipo oggetto
La new va fatta per tutti gli oggetti
Gli oggetti di tipo RectPoint sono simili ai rettangoli, ma hanno una componente in più, di tipo Point
import java.awt.*; class ProvaRectPoint { public static void main(String args[]){ RectPoint r; r=new RectPoint(); r.setBounds(23,12,4,5); // modifica solo le componenti // originarie x y width height r.centro=new Point(); r.centro.move(1,2); System.out.println(r.x); System.out.println(r.centro.x); } }
Dopo r=new RectPoint(); esiste la variabile r.centro (la casellina) ma non esiste l'oggetto (il rettangolone con le due caselline x y dentro)
Prima di poter usare r.centro.x oppure r.centro.move(), devo fare new Point()
Ma (1,2) non è il centro! (ci torniamo dopo)
RectPoint r;
Viene creata solo la variabile
Non esistono r.x, r.y, ecc e non si può fare r.setBounds()
r=new RectPoint();
Viene creato l'oggetto RectPoint, con le sue componenti dentro.
Ci sono sia le componenti di Rectangle che quelle definite in più per RectPoint
r.setBounds(23,12,4,5);
Vengono messi quei valori nell'oggetto
L'oggetto puntato da r ora esiste e si può usare.
La variabile r.centro però non punta nessun oggetto.
r.centro=new Point();
Viene creato l'oggetto punto:
A questo punto, si può fare la move oppure usare le componenti per mettere i valori dentro.
Notare la posizione di r.x e di r.centro.x
Anche se sono due "componenti x", una è una componente di un rettangolo, l'altra del punto che fa parte del rettangolo.
Quando si hanno dubbi sulla componenti, fare un diagramma del genere.
Chi definisce la classe pensave di usare il punto per memorizzare il centro.
Il programma può mettere un punto qualsiasi in r.centro
Per mettere il centro, si può fare cosí:
import java.awt.*; class ProvaCentro { public static void main(String args[]) { RectPoint r; r=new RectPoint(); r.setBounds(23,12,4,5); // modifica solo le componenti // originarie x y width height r.centro=new Point(); r.centro.move(r.x+r.width/2,r.y+r.height/2); System.out.println(r.x); System.out.println(r.centro); } }
Non c'è niente che impedisca a chi scrive il programma di sbagliare.
Con i metodi, è possibile evitare che questo avvenga.
Con il meccanismo di estensione, è anche possibile ridefinire le componenti.
Esempio: classe originaria Studente
class Studente { String nome; int media; }
Non mi piace perchè la media è intera ma dovrebbe essere reale.
class MStud extends Studente { double media; }
Nella classe MStud, la componente media è reale.
Le altre componenti sono come nella classe di partenza.
class ProvaMStud { public static void main(String args[]){ MStud s; s=new MStud(); s.nome="Pippo"; // stringa come al solito s.media=18.01; // valore reale } }
Definire una classe che estende Rectangle in questo modo:
Diamo direttamente la soluzione:
import java.awt.*; class RectCol extends Rectangle { double x; double y; int colore; }
Le componenti x ed y vengono ridefinite come reali
colore viene definito come intero
Per le componenti, non ci sono problemi.
I metodi possono avere un comportamento inatteso.
import java.awt.*; class ProvaRectCol { public static void main(String args[]){ RectCol r, q; r=new RectCol(); r.x=12.2; r.y=4.2; r.width=12; r.height=32; // r.setBounds(12.2, 4.2, 12, 32); // errore: gli argomenti // devono essere interi r.setBounds(1, -2, 12, 32); System.out.println(r.x); System.out.println(r.getX()); } }
Sembra ovvio che i primi due argomenti di setBounds possano essere reali.
Invece, setBounds vuole comunque due interi.
Viene stampato:
12.2 1.0
In altre parole, r.x ed r.getX() non sono più la stessa cosa.
(nota: il metodo getX restituisce un reale, per questo viene stampato 1.0 e non 1)
Le componenti di un oggetto non possono venire cancellate o ridefinite facendo una estensione.
Quello che succede è che vengono soltanto aggiunte nuove componenti con lo stesso nome.
nuova classe RectCol | componente intera x | componente intera y | componente intera width | componente intera height | | componente double x | componente double y | componente intera color
Ci sono due componenti di nome x
Una è ereditata da Rectangle (ossia esiste solo perchè c'è in Rectangle)
L'altra è definita nella estensione, ossia solo in RectCol
All'interno dell'oggetto, si sa quali parti sono del tipo originario e quali sono definite nell'estensione.
Ci sono due r.x. Quale viene usata?
Regole di accesso in caso di conflitti:
Quindi: r.x è la variabile di tipo double, perchè è introdotta dalla estensione.
Invece, i metodi di Rectangle, come setBounds() e getX(), usano la componente x intera, perchè questa sta nella "parte Rectanngle" dell'oggetto.
Per evitare confusione: se si ridefiniscono le componenti, vanno ridefiniti anche i metodi che le usano.