Ereditarietà

Per il momento abbiamo:

Vogliamo:

class Point {
  int x;
  int y;
  Point (int x1, int y1) {
    x = x1;
    y = y1;
  }
}
class ColouredPoint extends Point {
  String colour;
  ColouredPoint (int x, int y, String colour) {
    this.x = x;
    this.y = y;
    this.colour = colour;
  }
}
ColouredPoint pt = new ColouredPoint(3, 4, "red");
pt.x += 1;
pt.colour = "blue";
Errore:
Point pt = new ColouredPoint(3, 4, "red");
pt.colour = "blue";

Cast

Point pt = new ColouredPoint(3, 4, "red");
((ColouredPoint) pt).colour = "blue";
class Point {
  ...
  int distanceToPoint() {
    return (int) Math.sqrt(x * x + y * y);
  }
  ...
}

Instanceof

<EXPRESSION> instanceof <NAME >

(new Point(3, 4)) instanceof Object è vero

(new ColouredPoint(3, 4, "true)) instanceof Point è vero

(new ColouredPoint(3, 4, "true)) instanceof ColouredPoint è vero

(new Point(3, 4)) instanceof ColouredPoint è falso

((Point) (new ColouredPoint(3, 4, "red"))) instanceof ColouredPoint è vero

class Point {
  ...
  boolean equals(Object o) {
    if (o instanceof Point)  {
      return (((Point) o).x == x) && (((Point) o).y == y);
    } else { 
      return false;
    }
  }
  ...
}

Null

null instanceof Object è vero

 String pt = null;
pt instanceof String è falso

Overriding

Ridefinire i metodi della subclass:
class ColouredPoint extends Point {
  ...
  boolean equals(Object o) {
    if (o instanceof ColouredPoint)  {
      ColouredPoint pt = (ColouredPoint)o;
      return (pt.x == x) && (pt.y == y) && colour.equals(pt.colour);
    } else { 
      return false;
    }
  }
  ...
}

Super

class ColouredPoint {
  ColouredPoint (int x, int y, String colour) {
    super(x, y);
    this.colour = colour;
  }
  boolean equals(Object o) {
    if (o instanceof ColouredPoint) {
      return super.equals(o) && colour.equals(((ColouredPoint) o).colour);
    } else { 
      return false;
    }
  }
}
super(...) deve essere la prima istruzione.

Estensione

class A {
  A(){
  }
  int method() {
    return 1;
  }
}
class B extends A {
  B() {
  }
}
Tutto ciò che può fare un oggetto di tipo A, anche un oggetto di tipo B può farlo.
new A().method() è valido e vale 1.
Dunque new B().method() è valido e vale 1.

Il contrario non è vero.

class A {
  A(){
  int method() {
    return 1;
  }
}
class B extends A {
  B() {
  int method2() {
    return 2;
  }
}
Tutto ciò che può fare un oggetto di tipo B, forse un oggetto di tipo A non può farlo.
new B().method2() è valido e vale 2.
Ma new A().method2() è un errore (NoSuchMethod) che viene rilevato a tempo di compilazione.

Se c'è bisogno, si può anche cambiare il comportamento riscrivendo il metodo (overriding):

class A {
  A(){
  }
  int method() {
    return 1;
  }
}
class B extends A {
  B() {
  }
  int method() {
    return 2;
  }
}
new A().method() vale sempre 1
ma adesso new B().method() vale 2.

Questo è vero anche quando l'oggetto è un parametro (overloading):

class C {
  C() {
  }
  int method(A a) {
    return 2;
  }
}
new C().method(new A()) è valido e vale 2.
Dunque new C().method(new B()) è valido e vale 2.
In questo caso si prende sempre il metodo il più specifico:
class C {
  C() {
  }
  int method(Object a) {
    return 2;
  }
  int method(A a) {
    return 3;
  }
}
Il tipo A è più specifico di Object.
new C().method(new B()) vale 3.

La scelta del metodo può non essere unica:

class C {
  C() {
  }
  int method(B a) {
    return 1;
  }
}
class D extends C {
  D() {
  }
  int method(A a) {
    return 2;
  }
}
Per new D().method(new B()), i valori 1 e 2 sono possibili. In questo caso, un errore (Ambiguous) viene rilevato a tempo di compilazione.

Tipi

Un oggetto ha due tipi che possono essere diversi:

vero tipo o tipo dinamico (Run time type)

tipo nel programma o tipo statico (Compile time type)

Point pt = new ColouredPoint(3, 4, "red");
Il tipo statico di pt è Point.

Il tipo dinamico di pt è ColouredPoint.

int method (Point pt) {
  ...
}
Il tipo statico di pt è Point.

Il tipo dinamico pt è sconosciuto.

Selezionare i metodi

Quale tipo (dinamico o statico) è usato per scegliere quale metodo è eseguito:

Golden Rules

Overloading: Tipo statico

Overriding: Tipo dinamico

object1.method(object2)

  1. Trova il tipo statico di object1
  2. Nella class che definisce il tipo statico di object1, trova il metodo più specifico che accetta il tipo statico di object2.
  3. Nell'esecuzione del metodo selezionato al passo 2, il corpo eseguito sarà quello del tipo dinamico di object1
  4. .
Due errori (NoSuchMethod e Ambiguous) possono essere rilevati a tempo di compilazione nella scelta del passo 2. Nessun errore può essere rilevato a tempo di esecuzione. Il tipo dinamico è più specifico del tipo statico (tutto ciò che può fare un oggetto del tipo statico di object1, può farlo anche un oggetto del tipo dinamico di object), questo implica che il metodo scelto al passo 2 è valido.

Esempi

class A {
  int method(A a) {
    return 1;
  }
  int method(B a) {
    return 2;
  }
}
class B extends A {
  int method(A a) {
    return 3;
  }
  int method(B a) {
    return 4;
  }
}
e
A a1 = new A();
B a2 = new B();
A a3 = new B();
a1.method(a1) vale 1
a1.method(a2) vale 2
a1.method(a3) vale 1
a2.method(a1) vale 3
a2.method(a2) vale 4
a2.method(a3) vale 3
a3.method(a1) vale 3
a3.method(a2) vale 4
a3.method(a3) vale 3

Con:

class A {
  int method(A a) {
    return 1;
  }
}
class B extends A {
  int method(A a) {
    return 3;
  }
  int method(B a) {
    return 4;
  }
}
e
A a1 = new A();
B a2 = new B();
A a3 = new B();
a1.method(a1) vale 1
a1.method(a2) vale 1
a1.method(a3) vale 1
a2.method(a1) vale 3
a2.method(a2) vale 4
a2.method(a3) vale 3
a3.method(a1) vale 3
a3.method(a2) vale 3
a3.method(a3) vale 3

Con:

class A {
  int method(A a) {
    return 1;
  }
  int method(B a) {
    return 2;
  }
}
class B extends A {
  int method(B a) {
    return 4;
  }
}
e
A a1 = new A();
B a2 = new B();
A a3 = new B();
a1.method(a1) vale 1
a1.method(a2) vale 2
a1.method(a3) vale 1
a2.method(a1) vale 1
a2.method(a2) vale 4
a2.method(a3) vale 1
a3.method(a1) vale 1
a3.method(a2) vale 4
a3.method(a3) vale 1

Con:

class A {
  int method(B a) {
    return 2;
  }
}
class B extends A {
  int method(A a) {
    return 3;
  }
  int method(B a) {
    return 4;
  }
}
e
A a1 = new A();
B a2 = new B();
A a3 = new B();
a1.method(a1) vale NoSuchMethod
a1.method(a2) vale 2
a1.method(a3) vale NoSuchMethod
a2.method(a1) vale 3
a2.method(a2) vale 4
a2.method(a3) vale 3
a3.method(a1) vale NoSuchMethod
a3.method(a2) vale 4
a3.method(a3) vale NoSuchMethod

Con:

class A {

  int method(A a) {
    return 1;
  }
  int method(B a) {
    return 2;
  }
}
class B extends A {
  int method(A a) {
    return 3;
  }
}
e
A a1 = new A();
B a2 = new B();
A a3 = new B();
a1.method(a1) vale 1
a1.method(a2) vale 2
a1.method(a3) vale 1
a2.method(a1) vale 3
a2.method(a2) vale Ambiguous
a2.method(a3) vale 3
a3.method(a1) vale 3
a3.method(a2) vale 2
a3.method(a3) vale 3

Overloading

È il tipo statico che decide.
class Print {
  static String toString(Object o) {
    return "Object";
  }
  static String toString(Point pt) {
    return "(" + pt.x + ", " + pt.y + ")";
  }
  static String toString(ColouredPoint pt) {
    return "(" + pt.x + ", " + pt.y + ")" + ":" + colour;
  }
}
Point pt = new Point(3, 4);
Print.toString(pt) vale (3, 4)
ColouredPoint pt = new CoulouredPoint(3, 4, "red");
Print.toString(pt) vale (3, 4):red

Point pt = new CoulouredPoint(3, 4, "red");
Print.toString(pt) vale (3, 4)

Un'alternativa per Print

class Print {
  static String toString(Object o) {
    return "Object";
  }
  static String toString(Point pt) {
    return "(" + pt.x + ", " + pt.y + ")";
  }
  static String toString(ColouredPoint pt) {
    return toString((Point) pt) + ":" + colour;
  }
}
Ambiguità
class Obj {
  static void method (Point pt1, ColouredPoint pt2) {
    ..
  }
  static void method (ColouredPoint pt1, Point pt2) {
    ..
  }
}
ColouredPoint pt = new ColouredPoint(3, 4, "red");
Obj.method(pt, pt);
quale codice è selezionato? Errore Ambiguous.

Overriding

È il tipo dinamico che decide.
class Point {
  ...
  String toString() {
    return "(" + x + ", " + y + ")";
  }
  ...
}
class ColouredPoint extends Point {
  ...
  String toString(ColouredPoint pt) {
    return super.toString() + ":" + colour;
  }
  ...
}
Point pt = new Point(3, 4);
pt.toString() vale (3, 4)
ColouredPoint pt = new CoulouredPoint(3, 4, "red");
pt.toString() vale (3, 4):red

Point pt = new CoulouredPoint(3, 4, "red");
pt.toString() vale (3, 4):red

Overloading e Overriding

È un mix tra il tipo statico e il tipo dinamico. Bisogna fare attenzione.
class Print {
  String toString(Point pt) {
    return "(" + pt.x + ", " + pt.y + ")";
  }
}
class ColourPrint extends Print {
  String toString(Point pt) {
    return super.toString(pt) + ":black";
  String toString(ColouredPoint pt) {
    return super.toString(pt) + ":" + colour;
  }
}
Print pr = new ColourPrint();
Point pt = new CoulouredPoint(3, 4, "red");
pr.toString(pt) vale (3, 4):black

Che cosa non va con:

class Point {
  ...
  boolean equals(Point pt) {
      return (pt.x == x) && (pt.y == y);
    } else { 
      return false;
    }
  }
  ...
}
class ColouredPoint extends Point {
  ...
  boolean equals(ColouredPoint pt) {
      return super.equals(pt) && colour.equals(pt.colour);
    } else { 
      return false;
    }
  }
  ...
}
Due oggetti diversi possono essere equals:
ColouredPoint pt1 = new ColouredPoint(3, 4, "red");
Point pt2 = new ColouredPoint(3, 4, "blue");
pt1.equals(pt2) è vero

Metodi statici

Per le metodi statici solamente il tipo statico conta.
class A {
  static int getVal() {
    return 1;
  }
}

class B extends A {
  static int getVal() {
    return 2;
  }
}
Con
  A a = new A();
  B b = new B();
  A c = new B();
abbiamo che a.getVal() e c.getVal() vale 1 e che b.getVal() vale 2.

Un metodo non statico non può essere overriding con un metodo statico. Questo non è valido:

class A {
  int getVal() {
    return 1;
  }
}

class B extends A {
  static int getVal() {
    return 2;
  }
}

Abstract

Si può posporre la definizione d'un metodo. Ad esempio, gli alberi binari le cui foglie sono etichettate con una stringa:
class Tree {
}
class Node extends Tree {
  Tree right;
  Tree left;
  Node (Tree left, Tree right) {
    this.left = left;
    this.right = right;
  }
}
class Leaf extends Tree {
  String s;
  Leaf (String s) {
    this.s = s;
  }
}
new Node(new Node(new Leaf("a"), new Leaf("b")), new Leaf("c")) è un albero.

Adesso vogliamo calcolare il numero delle foglie di un albero. Questo non va bene:

class Node extends Tree {
  ...
  int getSize() {
    return left.getSize() + right.getSize();
}
class Leaf extends Tree {
  ...
  int getSize() {
    return 1;
  }
}
Perché left è di tipo Tree e Tree non definisce il metodo getSize.

Soluzione:

abstract class Tree {
  abstract int getSize();
}
class Node extends Tree {
  ...
  int getSize() {
    return left.getSize() + right.getSize();
}
class Leaf extends Tree {
  ...
  int getSize() {
    return 1;
  }
}
Una class che ha almeno un metodo abstract deve essere dichiarata abstract.

Non si può fare new su una class abstract.

Final

Si può vietare che un'estensione di una class faccia un overriding di un metodo usando la parola chiave final
class Node extends Tree {
  ...
  final int getSize() {
    ...
  }
}
Questo indica che tutte le class che estendono Node usano per getSize il metodo definito nella class Node. Una conseguenza è che con i metodi finali si può scegliere il metodo da eseguire staticamente. Dunque il codice dovrebbe essere più efficace (nessun overriding a tempo di esecuzione).

I campi con il modificatore final sono costanti:

class A {
  final float pi = 3.1415926;
}
In questo caso, è anche meglio dirlo static final:
class A {
  static final pi = 3.14151926;
}

Array

Gli arrays sono oggetti.

(new ColouredPoint[2]) instanceof Point[] è vero

Point[] pts = new ColouredPoint[10];
pts[0] = new ColouredPoint(3, 4, "blue");
((ColouredPoint) pts)[0].color = "red";
Problema:
Point[] pts = new ColouredPoint[10];
pts[0] = new Point(3, 4);
o
class foo {
  void putFirst (Point[] pts) {
    pts[0] = new Point(3, 4);
  }
  void start () {
    ColouredPoint[] pt = new ColouredPoint[3];
    putFirst(pt);
  }
}
Questi due esempi sono errati ma sono accettati dall'analisi statica dei tipi (static type-checking). L'errore è scoperto solo a tempo di esecuzione. Questo è l'unico caso in cui un errore di tipo viene rilevato a tempo di esecuzione.

Esercizi

D1. Scrivere un metodo in che verifica se una stringa è in un albero.

D2. Scrivere un metodo maxLength che ritorna una delle stringhe che ha la massima lunghezza in un albero.

D3. Scrivere un metodo listOf che ritorna l'array contenente tutte le stringhe in un albero.

D4. Scrivere un metodo equals che prede un oggetto e verifica se l'albero è uguale all'oggetto.

D5*. Modificare le classe Tree, Node e Leaf in tale modo che gli alberi possono avere un numero arbitrario di figli. Aggiornare in conseguenza i metodi in, maxLength, listOf e equals.

D6*. Definire una classa astratta List e due sottoclassi Cons e Nil per rappresentare le liste di numeri interi tale che la lista composta dei numeri 1, 2 e 3 può essere creata tramite l'espressione new Cons(1, new Cons(2, new Cons(3, new Nil()))).

Aggiungere in List:

D7*. Definire una classa astratta Path e due sottoclassi Move e Select per rappresentare un camino in un albero tale che il camino new Move(2, new Move(1, new Select())) rappresenta il verso il primo figlio del secondo figlio dell'albero. Aggiungere in la classe Tree dell'esercizio C5:

D8*. Definire una classa astratta Multi e due sottoclassi Single e Empty per rappresentare dei multinsiemi sui numeri interi tale che il multinsieme {1, 1, 3, 3, 3} può essere creato tramite l'espressione new Single(1, 2, new Single(3, 3, new Empty())).

Aggiungere in Multi:

D9*. Definire una classa astratta Exp e tre sottoclassi Int, Plus e Mult per rappresentare le espressioni arimetiche tale che l'espressione (1 + 2) * 3 possa essere rappresentata come Exp a = new Mult(new Plus(new Int(1), new Int(2)), new Int(3))}.

Quindi aggiungere nella classe Exp:


Laurent Théry
Last modified: Fri Jan 30 01:01:48 MET 2004