Ereditarietà: nozioni avanzate

Cose viste finora:

Ora: due precisazioni, più cose particolari


Precisazione sui metodi ereditati

I tipi dei parametri e del valore di ritorno non vengono cambiati:

class Sopra {
  Sopra esempio(Sopra x) {
    ...
  }
}

Se faccio una classe estesa:

class Sotto extends Sopra {
}

Il metodo esempio ha sempre Sopra come parametro e valore di ritorno

È come se il metodo fosse scritto, identico, nella classe di sotto

class Sotto extends Sopra {
  Sopra esempio(Sopra x) {
    ...
  }
}

Notare i tipi dei valori di ritorno e argomenti
(sono sempre di tipo Sopra)


Cosa cambia

Il metodo ereditato si invoca su un oggetto della sottoclasse,
ma parametri e valori di ritorno sono come prima.

  Sopra a, b, c;
  Sotto z;

  a=b.esempio(c);
  a=z.esempio(c);

Come oggetto di invocazione posso usare un Sopra o un Sotto
(il metodo è sia in Sopra che in Sotto)

I parametri e il valore di ritorno rimangono Sopra


Costruttore

Il costruttore è un metodo che ritorna un oggetto

Il fatto che i costruttori non si ereditano si può vedere cosí:

il costruttore della sovraclasse restiuisce sempre un oggetto della sovraclasse

Quindi, il costruttore rimane un costruttore della sovraclasse, ossia non si eredita


Precisazione sui diritti

Quando si ridefinisce un metodo, non si possono restringere gli accessi

Se un metodo è public in una classe, nella classe estesa non può essere private o con accesso di pacchetto

Se è con accesso di pacchetto, nella classe estesa non può essere private

Se è protected, allora deve rimanere tale, oppure diventare public


Ha importanza?

Capita di ridefinire classi predefinite, che hanno metodi public

import java.awt.*;

class NewPoint extends Point {
  void move(int x, int y) {
    ...
  }
}

Viene segnalato questo errore:

move(int,int) in NewPoint
cannot override move(int,int) in
java.awt.Point;
attempting to assign weaker access
privileges; was public

Il problema: il metodo move ha modificatore public in Point

In NewPoint, sto dando diritti di pacchetto

È un accesso più restrittivo, quindi non si può fare


Soluzione

Soluzione: dichiaro move come public nella classe estesa

import java.awt.*;

class NewPoint extends Point {
  public void move(int x, int y) {
    ...
  }
}

Quando appare questo errore, basta mettere public il metodo ridefinito


Argomenti nuovi


Variabili e oggetti

class NewPoint extends Point {
  ...
}

Posso anche fare:

  NewPoint q=new NewPoint();
  Point p;
  p=q;

Regola generale:

in una variabile di un tipo, posso sempre mettere l'indirizzo di un oggetto di una sottoclasse

Rappresentazione grafica

Serve a far capire quali classi sono estensioni di altre

Ogni riferimento a oggetto si può mettere in una variabile del suo tipo, oppure di un tipo ottenuto seguendo le freccie.


E il contrario?

Non si può fare!

  Point p=new Point();

  NewPoint q;

  q=p;		// errore!

Non si può mettere l'indirizzo di un oggetto di una sovraclasse in una sottoclasse

Regola mnemonica basata su Object: più avanti


Ridefinizione di componenti e metodi

class Sopra {
  int x;

  void metodo() {
    System.out.println("Sono Sopra");
  }
}

Poi la estendo, con nuove componenti e metodi:

class Sotto extends Sopra {
  String x;

  void metodo() {
    System.out.println("Sono Sotto");
  }
}

Ridefinizione di componenti

Le componenti vecchie sono ancora lí:

Ora vediamo come si accedono


Oggetti e variabili

Cosa succede se metto un riferimento a un oggetto in una variabile dell'altro?

class Prova {
  public static void main(String args[]) {

    Sotto b;
    b=new Sotto();

    Sopra o;

    o=b;		// valido
  }
}

Posso mettere gli oggetti in variabili di sovraclassi (ma non il contrario)


In memoria

Nell'esempio di sopra, ho due variabili di tipi diversi che puntano allo stesso oggetto.


Componenti e metodi

Dopo aver fatto o=b, cosa succede quando faccio o.x oppure o.metodo()?

variabile
tipo Sopra
oggetto
tipo Sotto
class Prova {
  public static void main(String args[]) {
    Sopra o;
    o=new Sotto();	// valido

    o.x=12;		// ok
    o.x="abcd";		// errore!

    o.metodo();		// stampa "Sono Sotto"
  }
}

Le componenti sono quelle del tipo della variabile.

Il metodo è quello del tipo dell'oggetto


Tipo della variabile e dell'oggetto

Metto il riferimento a un oggetto in una variabile di una sovraclasse

Ho due tipi (tipo dell'oggetto e della variabile)

Cosa succede quando uso una componente oppure un metodo?

componenti
sono quelli della variabile
metodi
sono quelli dell'oggetto

Cast fra oggetti

Oggetto in una variabile di sovraclasse: si può sempre fare

Il contrario: serve il cast

    Sopra o;
    Sotto b;

    b=new Sotto();
    o=b;

    Sotto c;
    c=b;		// ok

    c=o;		// errore
    c=(Sotto) o;	// ok
  }
}

Se voglio rimettere l'oggetto in una variabile dello stesso tipo, devo rifare il cast


Riepilogo

Sulle variabili:

  1. gli oggetti si possono mettere in variabili delle sovraclassi
  2. a questo punto, si possono rimettere nella sottoclasse originaria solo con un cast

Se la variabile è di un tipo, ma l'oggetto è di un altro:

  1. le componenti sono quelle della variabile
  2. i metodi sono quelli dell'oggetto

Altre classi

Esistono molte classi che non abbiamo visto

Esempio:


La classe Object

Tutte le classi Java sono sottoclassi di Object:

Se una classe non ha extends AltraClasse, è come se avesse extends Object


Object: conseguenze

Per quello che si è detto finora:


Regola facile

L'indirizzo di un oggetto si può sempre mettere in una variabile di una... sovraclasse? sottoclasse?

Regola facile per ricordare cosa si può fare:

tutti gli oggetti si possono memorizzare in una variabile Object

Dato che Object è sovraclasse di tutte le altre...


Metodi di Object

La class Object non ha componenti

Ha alcuni metodi che ci interessano (ne ha altri):

clone
crea una copia di questo oggetto
equals
confronta l'oggetto con un altro
toString
converte l'oggetto in stringa

Nota: equals confronta l'indirizzo degli oggetti

Quindi, ritorna true solo se i due oggetti sono lo stesso oggetto

toString ritorna la stringa con il nome della classe e l'indirizzo dell'oggetto


Ridefinire i metodi di Object

Di solito, i metodi equals e toString vengono ridefiniti

toString dovrebbe convertire l'oggetto in stringa.

equals confronta gli oggetti


Ridefinire toString

class Studente {
  String nome;
  int esami;
  double media;
}

Questa classe eredita il metodo toString da Object

Però il metodo di Object restituisce solo l'indirizzo dell'oggetto, non il suo contenuto

  Studente s;
  s=new Studente("Pippo", 12, 19.2);

  System.out.println(s.toString());

Stampa:

Studente@256a7c

Ridefinizione di toString: dovrebbe restituire i dati dello studente


Ridefinizione di toString

Deve restituire una stringa che rappresenta l'oggetto

Di solito, si ottiene concatenando i dati

class Studente {
  String nome;
  int esami;
  double media;

  public String toString() {
    return "["+this.nome+" "+
           this.esami+" "+
           this.media+"]";
  }
}

il + fra stringhe restituisce la concatenazione

Notare public

Dato che il metodo di Object ha il modificatore public, anche questo lo deve avere


Uso di toString

Posso stampare i dati di uno studente:

class ProvaStudente {
  public static void main(String args[]) {
    Studente s;
    s=new Studente();

    s.nome="Pippo";
    s.esami=1;
    s.media=19;

    System.out.println(s.toString());
  }
}

Metodo sovraccarico

Se println viene invocato su un oggetto, invoca toString su di esso

class ProvaStudente {
  public static void main(String args[]) {
    Studente s;
    s=new Studente();

    s.nome="Pippo";
    s.esami=1;
    s.media=19;

    System.out.println(s);
  }
}

Funziona anche cosí


Ridefinire equals

Più complesso

Firma di equals in Object:

  public boolean equals(Object o) {
    ...
  }

Il parametro è un Object!

Quando si eredita:

class Studente {
  ...

  public boolean equals(Object o) {
    ...
  }
}

Viene restituito true solo se l'oggetto passato è lo stesso

Di solito, voglio che ritorni true se le componenti hanno gli stessi valori


Sovrascrivere equals

Se definisco un metodo equals(Studente) sto usando il sovraccarico:

class Studente {
  ...

  public boolean equals(Studente o) {
    ...
  }
}

È come se avesse tutti e due i metodi (quello nuovo e quello ereditato)

class Studente {
  ...

		// ereditato
  public boolean equals(Object o) {
    ...
  }

		// nuovo
  public boolean equals(Studente o) {
    ...
  }
}

Non si fa in questo modo!


Sovrascrivere per davvero

Devo scrivere un metodo che abbia questa firma:

  public boolean equals(Object o)

Dato che o è una variabile Object, non posso usare o.nome, o.esami, ecc:

  return o.esami==this.esami ... // errore

Cast

Prima faccio il cast, e poi confronto:

class Studente {
  ...

  public boolean equals(Object o) {
    Studente s;
    s=(Studente) o;

    return ((s.nome.equals(this.nome)) &&
            (s.esami==this.esami) &&
            (s.media==this.media));
  }
}

Questo sovrascrive il metodo equals


Perchè devo per forza sovrascrivere?

Alcune classi rappresentano insiemi di oggetti

Alcuni metodi di queste classi invocano il metodo equals

Il metodo che viene invocato è equals(Object), ossia il metodo che ha un Object come argomento

Non viene usato l'eventuale metodo equals(Studente)


Regola generale

Per ogni classe:

public String toString() { ... }
restituisce una stringa con dentro le componenti dell'oggetto di invocazione
public boolean equals(Object o) { ... }
fa il cast di o nel tipo dell'oggetto, e poi confronta componente per componente

Metodo equals: controllo null

Va prima controllato che il parametro non sia null

  public boolean equals(Object o) {
    if(o==null)
      return false;

    Studente s;
    s=(Studente) o;

    return ((s.nome.equals(this.nome)) &&
            (s.esami==this.esami) &&
            (s.media==this.media));
  }

Metodo equals: controllo oggetti

Se s.nome vale null, l'invocazione di equals dà errore

Soluzione: se s.nome vale null, allora devo solo controllare se this.nome vale null

Per gli oggetti: se il primo vale null, faccio il confronto diretto, altrimenti faccio il confronto con equals

  public boolean equals(Object o) {
    if(o==null)
      return false;

    Studente s;
    s=(Studente) o;

    if(s.nome==null) {
      if(this.nome!=null)
        return false;
    }
    else
      if(!s.nome.equals(this.nome))
        return false;

    return ((s.esami==this.esami) &&
            (s.media==this.media));
  }
}

Per il seguito...

I riferimenti a oggetti si possono tutti memorizzare nelle variabili Object

Memorizzate i metodi di Studente: per la altre classi, saranno sempre fatti in questo modo

class Studente {
  String nome;
  int esami;
  double media;

  public String toString() {
    return "["+this.nome+" "+
           this.esami+" "+
           this.media+"]";
  }

  public boolean equals(Object o) {
    if(o==null)
      return false;

    Studente s;
    s=(Studente) o;

    return ((s.nome.equals(this.nome)) &&
            (s.esami==this.esami) &&
            (s.media==this.media));
  }
}

Ridefinire clone

Firma del metodo:

  Object clone()

Va ridefinito, se lo voglio usare

Non lo facciamo (usiamo solo il clone per classi predefinite)


Il cast dopo clone

Se faccio:

  Point q=p.clone();

Il risultato di clone è un Object, mentre Point è una sua sottoclasse

È il contrario che si può fare senza cast
(mettere un oggetto di una sottoclasse in una variabile della sovraclsse)

Per la cosa inversa, serve il cast:

  Point q=(Point) p.clone();

Ridefinire hashCode

È un altro metodo di Object

Ci servirà ridefinirlo

Cosí può andare:

  public int hashCode() {
    return 0;
  }

Si fa cosí

Per le variabili:

Ridefinizione:


A cosa serve equals?

Definire un metodo che funziona su tutte le classi

Esempio: ricerca in un vettore di oggetti

  static boolean presente(Object v[], Object e) {
    int i;

    for(i=0; i<v.length; i++)
      if(e.equals(v[i]))
        return true;

    return false;
  }

Funziona per tutti i vettori di oggetti

Attenzione a null:

L'elemento e potrebbe valore null

In questo caso, e.equals(...) è un errore


Un programma

Dato che ogni oggetto si può mettere in un Object, posso passare un punto a un metodo che si aspetta un Object

Lo stesso vale per i vettori

  public static void main(String args[]) {
    Point p=new Point(0,0);

    Point vp[]=new Point[5];
    // creazione del vettore

    System.out.println(presente(vp, p));


    Rectangle vr[]=new Rectangle[5];
    Rectangle r=new Rectangle(0,0,10,10);

    Rectangle vr[]=new Rectangle[5];
    // creazione del vettore

    System.out.println(presente(vr, r));
  }

Quando faccio e.equals nel metodo, viene invocato quello dell'oggetto

Quindi, se il parametro è un Point, viene invocato l'equals di Point

Se il parametro è un Rectangle, viene invocato il suo equals, ecc.


Metodi con oggetti arbitrari

D'ora in avanti, se possibile, facciamo metodi che ricevono Object

Questi possono ricevere come parametro un oggetto qualsiasi

Cosa importante: ridefinire equals


Insiemi di oggetti

Come definire oggetti di un certo tipo:
visto

Esempio: insiemi di punti

import java.awt.*;

class InsiemePoint {
  private Point elementi[];

  InsiemePoint() {
    this.elementi=new Point[0];
  }

  void stampa() {
    int i;

    for(i=0; i<this.elementi.length; i++)
      System.out.println(this.elementi[i]);
  }

  boolean verificaVuoto() {
    return this.elementi.length==0;
  }

  boolean presente(Point x) {
    int i;

    for(i=0; i<this.elementi.length; i++)
      if(this.elementi[i].equals(x))
        return true;

    return false;
  }

  void inserisci(Point x) {
    if(this.presente(x))
      return;

    Point nuovo[];
    nuovo=new Point[this.elementi.length+1];

    System.arraycopy(this.elementi, 0,
                     nuovo, 0,
                     this.elementi.length);

    nuovo[nuovo.length-1]=x;

    this.elementi=nuovo;
  }

  void elimina(Point x) {
    if(!this.presente(x))
      return;

    Point nuovo[];
    nuovo=new Point[this.elementi.length-1];

    System.arraycopy(this.elementi, 0,
                     nuovo, 0,
                     this.elementi.length-1);

    int i;
    for(i=0; i<this.elementi.length-1; i++)
      if(this.elementi[i]==x) {
        nuovo[i]=this.elementi[this.elementi.length-1];
        break;
      }

    this.elementi=nuovo;
  }
}

Altri insiemi

Se voglio un insieme di Studente, devo riscrivere tutta la classe

Soluzione: faccio un insieme di Object:

class InsiemeObj {
  private Object elementi[];

  InsiemeObj() {
    this.elementi=new Object[0];
  }

  void stampa() {
    int i;

    for(i=0; i<this.elementi.length; i++)
      System.out.println(this.elementi[i]);
  }

  boolean verificaVuoto() {
    return this.elementi.length==0;
  }

  boolean presente(Object x) {
    int i;

    for(i=0; i<this.elementi.length; i++)
      if(this.elementi[i].equals(x))
        return true;

    return false;
  }

  void inserisci(Object x) {
    if(this.presente(x))
      return;

    Object nuovo[];
    nuovo=new Object[this.elementi.length+1];

    System.arraycopy(this.elementi, 0,
                     nuovo, 0,
                     this.elementi.length);

    nuovo[nuovo.length-1]=x;

    this.elementi=nuovo;
  }

  void elimina(Object x) {
    if(!this.presente(x))
      return;

    Object nuovo[];
    nuovo=new Object[this.elementi.length-1];

    System.arraycopy(this.elementi, 0,
                     nuovo, 0,
                     this.elementi.length-1);

    int i;
    for(i=0; i<this.elementi.length-1; i++)
      if(this.elementi[i]==x) {
        nuovo[i]=this.elementi[this.elementi.length-1];
        break;
      }

    this.elementi=nuovo;
  }
}

Come si usa

Esempio di programma:

import java.awt.*;

class ProvaIns {
  public static void main(String args[]) {
    InsiemeObj io;
    io=new InsiemeObj();

    io.inserisci(new Point(12,43));
    io.inserisci(new Point(3,-2));
    io.inserisci(new Point(4,12));
    io.inserisci(new Point(3,-2));

    io.elimina(new Point(3,-2));
    io.stampa();
  }
}

Quando faccio io.inserisci(new Point(12,43));:

In una variabile Object posso mettere un oggetto qualsiasi

Quindi si può fare


Nei metodi

Quando faccio presente(p) cosa succede?

  boolean presente(Object x) {
    int i;

    for(i=0; i<this.elementi.length; i++)
      if(x.equals(this.elementi[i]))
        return true;

    return false;
  }

x è una variabile Object

Ma contiene un riferimento a un oggetto Point

Viene invocato equals di Point


Se non ridefinisco equals

Il metodo equals di Object è fatto (più o meno) cosí:

  public boolean equals(Object o) {
    if(o==null)
      return false;

    return this==o;
  }

Vale true solo se si tratta dello stesso oggetto

Di solito, due oggetti vanno considerati uguali se hanno gli stessi valori

Se non faccio la ridefinizione, viene invocato il metodo equals di Object, che non funziona


Un problema...

In un insieme posso mettere sia punti che rettangoli!

import java.awt.*;

class Sia {
  public static void main(String args[]) {
    InsiemeObj io;
    io=new InsiemeObj();

    io.inserisci(new Point(12,43));
    io.inserisci("abcd");
    io.inserisci(new Rectangle(0,0,10,10));

    io.elimina(new Point(3,-2));
    io.stampa();
  }
}

Fin qui, va bene (è un insieme misto)


Oggetti definiti da noi

import java.awt.*;

class Sia {
  public static void main(String args[]) {
    InsiemeObj io;
    io=new InsiemeObj();

    io.inserisci(new Point(12,43));
    io.inserisci("abcd");
    io.inserisci(new Rectangle(0,0,10,10));
    io.inserisci(new Studente());

    if(io.presente(new Point(12,43))
      System.out.println("Ci sta");
  }
}

Quando si va a fare io.presente(new Point(12,43), viene invocato, a un certo punto, il confronto fra lo Studente e questo punto.

Ma equals di Studente è fatto cosí:

  public boolean equals(Object o) {
    if(o==null)
      return false;

    Studente s;
    s=(Studente) o;

    return ((s.nome.equals(this.nome)) &&
            (s.esami==this.esami) &&
            (s.media==this.media));
  }

Non si può fare il cast da Point a Studente (non sono sottoclassi l'uno dell'altro)

Viene prodotto un errore


La soluzione

Se due oggetti sono di classi diverse, equals deve dare false, e non tentare il cast

  public boolean equals(Object o) {
    if(o==null)
      return false;

    if(this.getClass()!=o.getClass())
      return false;

    Studente s;
    s=(Studente) o;

    if(s.nome==null) {
      if(this.nome!=null)
        return false;
    }
    else
      if(!s.nome.equals(this.nome))
        return false;

    return ((s.esami==this.esami) &&
            (s.media==this.media));
  }
}

Il metodo getClass ritorna il tipo dell'oggetto

Non serve sapere come funziona: il metodo equals va fatto sempre con queste tre parti prima: confronto con null confronto classi, e cast. Soltanto dopo viene il confronto vero e proprio.