Sappiamo come definire nuove classi.
In alcuni casi, abbiamo già una classe 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
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. Ingegneria 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; }
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; }
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); } }
Prima di poter usare r.centro.x oppure r.centro.move(), devo fare new Point()
Ma (1,2) non è il centro!
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 l'incapsulamentoè 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.
Definire una classe PointCol che estende Point con l'aggiunta di una componente intera colore e con un metodo che calcola la distanza del punto dall'origine.
Si aggiunge il metodo alla classe.
import java.awt.*; class PointCol extends Point { int colore; void stampaDistanza() { double d; d=Math.sqrt(this.x*this.x+this.y+this.y); System.out.println(d); } }
Per ora diciamo solo che fare class NomeClasse extends AltraClasse inserisce all'interno della classe NomeClasse tutte le componenti e i metodi di AltraClasse
In realtà, è più complicato.
Realizzare la classe SPoint che estende Point con una componente intera z, e che la in più il metodo setToOrigin che pone il punto di invocazione nell'origine (tutte e tre le componenti vanno a zero).
Nuova componente+nuovo metodo.
import java.awt.*; class SPoint extends Point { int z; void setToOrigin() { this.x=0; this.y=0; this.z=0; } }
Tutti i metodi della classe Point sono anche metodi di SPoint.
Quindi su un oggetto SPoint posso invocare il metodo move
Fa la però esattamente la stessa cosa che fa su un Point: cambia le componenti x e y.
import java.awt.*; class SPoint extends Point { int z; void setToOrigin() { this.move(0,0); this.z=0; } }
Si può fare perchè this è un oggetto della classe SPoint, e la classe SPoint ha il metodo move
Creo due punti, e su uno invoco il metodo.
class ProvaOrigin { public static void main(String args[]){ SPoint p, r; p=new SPoint(); r=new SPoint(); p.move(3,4); p.z=-3; r.move(-1,-1); r.z=-1; p.setToOrigin(); System.out.println(p.x); System.out.println(r.x); } }
Realizzare la classe RectPoint che estende Rectangle con una componente centro di tipo Point, e ha in più un metodo setCentro che mette nel centro l'effettivo centro del rettangolo.
Si assuma che il punto sia già stato creato.
I valori del rettangolo sono this.x this.y this.width this.heigh this.centro
I valori calcolati vanno messi nel punto this.centro
import java.awt.*; class RectPoint extends Rectangle { Point centro; void setCentro() { this.centro.x=this.x+this.width/2; this.centro.y=this.y+this.height/2; } }
Prima metto i valori del rettangolo, poi creo il punto e poi invoco il metodo
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.setCentro(); System.out.println(r.centro); } }
Viene stampato il centro
Nota: l'oggetto punto va creato. Se non lo crea il metodo, lo deve creare il programma.
Definire il metodo move per SPoint, che modifica tutte e tre le coordinate.
move è simile a setToOrigin, solo che i valori da mettere nelle componenti sono gli argomenti, e non 0
import java.awt.*; class SPoint extends Point { int z; void move(int a, int b, int c) { this.x=a; this.y=b; this.z=c; } }
class ProvaMove { public static void main(String args[]){ Point p; p=new Point(); SPoint r; r=new SPoint(); p.move(3,4); r.move(-1,-1,2); System.out.println(p.x); System.out.println(r.x); } }
Notare:
Se in SPoint non definisco move allora ho quello ereditato da Point
Il metodo move è stato ridefinito.
I costruttori non si ereditano.
Esempio: Point ha un costruttore a due argomenti:
Point p=new Point(2,3);
La classe SPoint ha solo il costruttore standard (quello a due argomenti)
Se voglio un costruttore, lo devo definire
import java.awt.*; class SPoint extends Point { int z; SPoint(int x, int y, int z) { this.x=x; this.y=y; this.z=z; } }
Estendere la classe Point in modo che abbia un nuovo costruttore, che prende come argomento un rettangolo, e mette nel punto il suo centro.
Nota: la nuova classe si chiama NewPoint
Per estendere serve un nuovo nome:
class NewPoint extends Point { ... }
Si comincia con l'intestazione del metodo come al solito.
import java.awt.*; class NewPoint extends Point { NewPoint(Rectangle r) { ... } }
Si tratta quasi di un metodo come gli altri.
import java.awt.*; class NewPoint extends Point { NewPoint(Rectangle r) { this.x=r.x+r.width/2; this.y=r.y+r.height/2; } }
Calcolo, sulla base degli argomenti, i valori da mettere nelle componenti dell'oggetto appena creato.
import java.awt.*; class ProvaNewPoint { public static void main(String args[]) { Rectangle r; r=new Rectangle(10,10,20,30); NewPoint q; q=new NewPoint(r); System.out.println(q); } }
I costruttori non si ereditano.
import java.awt.*; class ProvaNewPoint { public static void main(String args[]) { NewPoint p; p=new NewPoint(2,4); NewPoint q; q=new NewPoint(); System.out.println(p); } }
Le due new NewPoint(...) danno errore.
Se voglio un costruttore, lo devo definire.
import java.awt.*; class NewPoint extends Point { NewPoint() { } NewPoint(int x, int y) { this.x=x; this.y=y; } NewPoint(Rectangle r) { this.x=r.x+r.width/2; this.y=r.y+r.height/2; } }
Se voglio solo ``ereditare'' il costruttore: definisco il costruttore con super(args) come corpo:
import java.awt.*; class NewPoint extends Point { NewPoint() { super(); } NewPoint(int x, int y) { super(x, y); } NewPoint(Rectangle r) { this.x=r.x+r.width/2; this.y=r.y+r.height/2; } }
Di solito, conviene ridefinire il costruttore.
super(argomenti) invoca un costruttore della classe che è stata estesa
Quando si invoca il costruttore della superclasse, questa deve essere la prima istruzione del metodo.
import java.awt.*; class TriPoint extends Point { int z; TriPoint() { } TriPoint(int x, int y, int z) { super(x, y); this.z=z; } }
Quando un metodo non inizia con super(argomenti), si assume automaticamente super(), ossia il costruttore senza argomenti.
Tutti i costruttori, se non iniziano con super(argomenti), si considerano come se iniziassero con super().
Quindi, se si definisce una classe senza il costruttore senza argomenti, allora nelle sottoclassi bisogna chiamare esplicitamente super(argomenti) come prima istruzione.
Altrimenti, lui assume super(), che non esiste.
Cosa succede se cambio i diritti di accesso in una sottoclasse?
Si può sempre fare:
class Sopra { private int y; private int getY() { return this.y; } }
class Sotto extends Sopra { public int y; public int getY() { return this.y; } }
Funziona tutto.
Si può fare con le variabili, perchè tanto sono variabili nuove (e le vecchie esistono ancora)
Non si può fare con i metodi:
class Sopra { public int x; public int getX() { return this.x; } }
class Sotto extends Sopra { private int x; // ok private int getX() { // errore! return this.x; } }
La definizione del metodo getX in Sotto dà errore.