gianluca.aguzzi@unibo.it
angelo.filaseta@unibo.it
Questo materiale è ampiamente basato su quello realizzato dai Prof. Mirko Viroli e Roberto Casadei, che ringrazio.
Ogni errore riscontratovi è esclusiva responsabilità degli autori di questo documento.
extends
protected
super
final
su classi e metodiÈ un meccanismo che consente di definire una nuova classe specializzandone una esistente, ossia
Lamp
), realizzare un televisore (Tv
)SimpleLamp
), realizzare una lampadina con controllo livello di intensità luminosa (AdvancedLamp
)Device
diversi e specializzatiCounter
public class Counter {
private int value;
public Counter(final int initialValue) {
this.value = initialValue;
}
public void increment() {
this.value++;
}
public int getValue() {
return this.value;
}
}
Counter
public class UseCounter {
public static void main(String[] s) {
final Counter c = new Counter(0);
System.out.println(c.getValue()); // 0
c.increment();
c.increment();
System.out.println(c.getValue()); // 2
}
}
MultiCounter
Counter
Counter
, offre un metodo multiIncrement(int)
public class MultiCounter {
private int value;
public MultiCounter(final int initialValue) {
this.value = initialValue;
}
public void increment() {
this.value++;
}
public int getValue() {
return this.value;
}
/* Nuovo metodo */
public void multiIncrement(final int n) {
for (int i = 0; i < n; i++) {
this.increment();
}
}
}
MultiCounter
public class UseMultiCounter {
public static void main(String[] s) {
final MultiCounter mc = new MultiCounter(10);
System.out.println(mc.getValue()); // 10
mc.increment();
mc.increment();
System.out.println(mc.getValue()); // 12
mc.multiIncrement(10);
System.out.println(mc.getValue()); // 22
}
}
MultiCounter2
public class MultiCounter2 {
private Counter counter;
public MultiCounter2(final int initialValue) {
this.counter = new Counter(initialValue);
}
public void increment() {
this.counter.increment();
}
public int getValue() {
return this.counter.getValue();
}
/* Nuovo metodo */
public void multiIncrement(final int n) {
for (int i = 0; i < n; i++) {
this.counter.increment();
}
}
}
Counter
(via delega) ma non abbiamo veramente “ridotto” la quantità di codice scrittoN
punti, dovremo prima localizzare questi N
punti e poi ivi applicare la modifica)class C extends D { ... }
C
eredita campi/metodi di D
C
(la “visibilità” di un campo è ortogonale alla sua “presenza” in una classe)D
superclasse, o classe base, o classe padreC
sottoclasse, o classe figlia, o specializzazioneD
(basta il codice binario)MultiCounter
/* Si noti la clausola extends */
public class MultiCounter extends Counter {
/*
* I costruttori vanno ridefiniti. Devono tuttavia richiamare
* quelli ereditati dalla sopraclasse
*/
public MultiCounter(int initialValue) {
super(initialValue);
}
// increment e getValue automaticamente ereditati
// si aggiunge multiIncrement
public void multiIncrement(final int n) {
for (int i = 0; i < n; i++) {
this.increment();
}
}
}
MultiCounter
come estensione di Counter
multiIncrement()
super
, che chiama un costruttore (non privato) della classe padreUseMultiCounter
continua a funzionare!MultiCounter
è simile ad un oggetto di Counter
increment()
e getValue()
value
(che in effetti è incrementato), anche se essendo privato è inaccessibile dal codice della classe MultiCounter
Counter
: metodo multiIncrement()
e ridefinizione del costruttoreextends
” (specializzazione)
protected
public
e private
protected
?private
BiCounter
– contatore bidirezionaledecrement
counter
ExtendibleCounter
/* Il nome ExtendibleCounter è di comodo, più propriamente
andrebbe chiamata semplicemente Counter */
public class ExtendibleCounter {
/* campo value protetto */
protected int value;
public ExtendibleCounter(final int initialValue) {
this.value = initialValue;
}
public void increment() {
this.value++;
}
public int getValue() {
return this.value;
}
}
MultiCounter
public class MultiCounter extends ExtendibleCounter {
public MultiCounter(final int initialValue) {
super(initialValue);
}
public void multiIncrement(final int n) {
// Ora realizzabile più efficientemente
if (n > 0) {
this.value = this.value + n;
}
}
}
BiCounter
Counter
(i.e. increment
+ getValue
) senza un rendere accessibile in scrittura alle sottoclassi il campo value
(direttamente o via setter)public class BiCounter extends ExtendibleCounter {
public BiCounter(final int initialValue) {
super(initialValue);
}
public void decrement() {
/* Ora this.counter è accessibile */
this.value--;
}
}
super
increment()
LimitCounter
public class LimitCounter extends ExtendibleCounter {
/* Aggiungo un campo, che tiene il limite */
protected final int limit;
public LimitCounter(final int limit) {
super(0);
this.limit = limit;
}
public boolean isOver() {
return this.getValue() == this.limit;
}
/* Overriding del metodo increment() */
public void increment() {
if (!this.isOver()) {
super.increment();
}
}
}
LimitCounter
public class UseLimitCounter {
public static void main(String[] s) {
final LimitCounter c = new LimitCounter(5);
System.out.println(c.getValue()); // 0
System.out.println(c.isOver()); // false
c.increment();
c.increment();
System.out.println(c.getValue()); // 2
System.out.println(c.isOver()); // false
c.increment();
c.increment();
c.increment();
c.increment();
c.increment();
c.increment();
c.increment();
System.out.println(c.getValue()); // 5
System.out.println(c.isOver()); // true
}
}
LimitCounter
LimitedLamp
(via estensione) che contiene un contatore, e che ha un tempo di vita basato sul numero di accensioni ammesseEcoDomusController
si compone di $n$ LimitedLamp
, e ha la possibilità di verificare se tutte le lampadine sono esaurite, e di accendere la lampadina alla quale è rimasto più tempo di vitaEcoDomusController
componesse $n$ SimpleLamp
e $n$ LimitCounter
LimitedLamp
realizza alcuni metodi per delegazione al suo contatoreLimitCounter
public class LimitCounter extends ExtendibleCounter {
private final int limit;
public LimitCounter(final int initialValue, final int limit) {
super(initialValue);
this.limit = limit;
}
public boolean isOver() {
return this.getDistanceToLimit() == 0;
}
public int getDistanceToLimit() {
return this.limit - this.value;
}
public void increment() {
if (!this.isOver()) {
super.increment();
}
}
}
SimpleLamp
public class SimpleLamp {
private boolean switchedOn;
public SimpleLamp() {
this.switchedOn = false;
}
public void switchOn() {
this.switchedOn = true;
}
public void switchOff() {
this.switchedOn = false;
}
public boolean isSwitchedOn() {
return this.switchedOn;
}
}
LimitedLamp
public class LimitedLamp extends SimpleLamp {
private LimitCounter counter;
public LimitedLamp(final int limit) {
super(); // Questa istruzione è opzionale
this.counter = new LimitCounter(0, limit);
}
public void switchOn() {
if (!this.isSwitchedOn()) {
// incremento solo se è una vera accensione
this.counter.increment();
}
if (!this.counter.isOver()) {
super.switchOn();
}
}
public int getRemainingLifeTime() { // delegazione a counter
return this.counter.getDistanceToLimit();
}
public boolean isOver() { // delegazione a counter
return this.counter.isOver();
}
}
EcoDomusController
public class EcoDomusController {
/* Compongo n LimitedLamp */
final private LimitedLamp[] lamps;
public EcoDomusController(final int size, final int lampsLimit) {
this.lamps = new LimitedLamp[size];
for (int i = 0; i < size; i++) {
this.lamps[i] = new LimitedLamp(lampsLimit);
}
}
public LimitedLamp getLamp(final int position) {
return this.lamps[position];
}
private LimitedLamp toBeUsedNext() {
LimitedLamp best = null;
for (final LimitedLamp lamp : this.lamps) {
if (!lamp.isSwitchedOn() &&
( best == null ||
lamp.getRemainingLifeTime() > best.getRemainingLifeTime())) {
best = lamp;
}
}
return best;
}
/* Accendo una lampadina spenta, scegliendola in modo economico */
public void switchOnOne() {
final LimitedLamp lamp = this.toBeUsedNext();
if (lamp != null) {
lamp.switchOn();
}
}
/* Verifico se sono tutti accesi */
public boolean allOver() {
for (final LimitedLamp lamp : this.lamps) {
if (!lamp.isOver()) {
return false;
}
}
return true;
}
public String toString() {
String s = "";
for (final LimitedLamp lamp : this.lamps) {
s += (lamp.isSwitchedOn() ? "on" : "off");
s += "(" + lamp.getRemainingLifeTime() + ")" + " | ";
}
return s;
}
}
UseEcoDomusController
public class UseEcoDomusController {
public static void main(String[] s) {
// Simulazione sessione di lavoro
final EcoDomusController controller;
controller = new EcoDomusController(5, 10);
System.out.println(controller);
// off(10) | off(10) | off(10) | off(10) | off(10) |
final LimitedLamp l = controller.getLamp(0);
l.switchOn();
l.switchOff();
l.switchOn();
System.out.println(controller);
// on(8) | off(10) | off(10) | off(10) | off(10) |
controller.switchOnOne();
controller.switchOnOne();
controller.switchOnOne();
controller.switchOnOne();
System.out.println(controller);
// on(8) | on(9) | on(9) | on(9) | on(9) |
}
}
class A { }
class B extends A { } // OK
B
ha un costruttore di default, che invoca il costruttore senza argomenti (quello di default in questo caso) di A
class A { A() { out.print("A"); } }
class B extends A { } // OK
B
ha un costruttore di default, che invoca il costruttore senza argomenti di A
class A { A(int x) { out.print("A" + x); } }
class B extends A { } // ERROR
B
ha un costruttore di default, che vorrebbe invocare il costruttore di A
senza argomenti, ma non lo trova! Il compilatore restituisce un errore.class A { }
class B extends A { B() { out.print("B"); } } // OK
// Stessa cosa di:
class B extends A { B() { super(); out.print("B"); } }
B
ha un costruttore definito, che invoca implicitamente il costruttore senza argomenti (quello di default in questo caso) di A
class A { A() { out.print("A"); } }
class B extends A { B() { out.print("B"); } } // OK
// Stessa cosa di:
class B extends A { B() { super(); out.print("B"); } }
B
ha un costruttore definito, che invoca implicitamente/esplicitamente il costruttore senza argomenti di A
class A { A(int x) { out.print("A" + x); } }
class B extends A { B() { out.print("B"); } } // ERROR
// Qua occorre fare:
class B extends A { B() { super(7); out.print("B"); } }
B
ha un costruttore definito, che vorrebbe implicitamente invocare il costruttore di A
senza argomenti, ma non lo trova! Il compilatore restituisce un errore.super
), altrimenti il costruttore di default verrà chiamato, se c’ènew
class A {
protected int i;
public A(int i) {
System.out.println("A().. prima " + this.i);
this.i = i;
System.out.println("A().. dopo " + this.i);
}
}
class B extends A {
protected String s;
public B(String s, int i) {
super(i);
System.out.println("B().. prima " + this.s + " " + this.i);
this.s = s;
System.out.println("B().. dopo " + this.s + " " + this.i);
}
public static void main(String[] s) {
B b = new B("prova", 5); // Cosa succede?
}
}
super
)super
C
può includere una invocazione del tipo super.m(..args..)
m
, ossia viene eseguito il metodo m
della superclasse
n
(su this
), allora si ritorna a considerare la versione più specifica a partire dalla classe di partenza C
class C {
protected int i;
void m() {
System.out.println("C.m.. prima " + i);
this.i++;
System.out.println("C.m.. dopo " + i);
}
}
class D extends C {
D(int i) {
this.i = i;
}
void m() {
super.m();
System.out.println("D.m.. dopo " + this.i);
}
public static void main(String[] s) {
new D(5).m(); // Cosa succede?
}
}
class E {
protected int i;
void m() {
this.i++;
this.n();
}
void n() {
this.i = this.i + 10;
}
}
class F extends E {
void n() {
this.i = this.i + 100;
}
public static void main(String[] s) {
F f = new F();
f.i = 10;
f.m();
System.out.println("" + f.i);
}
}
LimitCounter
public class LimitCounter extends ExtendibleCounter {
private final int limit;
public LimitCounter(final int initialValue, final int limit) {
super(initialValue);
this.limit = limit;
}
public boolean isOver() {
return this.getDistanceToLimit() == 0;
}
public int getDistanceToLimit() {
return this.limit - this.value;
}
public void increment() {
if (!this.isOver()) {
super.increment();
}
}
}
increment()
su un UnlimitedCounter
?LimitCounter
LimitCounter
si chiama this.isOver()
che chiama this.getDistanceToLimit()
this.getDistanceToLimit()
eseguita è quella di UnlimitedCounter
public class UnlimitedCounter extends LimitCounter {
public UnlimitedCounter() {
super(0, Integer.MAX_VALUE);
}
public int getDistanceToLimit() {
// Quindi il contatore non scade mai
return Integer.MAX_VALUE;
}
}
UnlimitedCounter
public class UseUnlimitedCounter {
public static void main(String[] s) {
final UnlimitedCounter uc = new UnlimitedCounter();
System.out.println("isOver: " + uc.isOver()); // false
System.out.println("LifeTime: " + uc.getDistanceToLimit());
uc.increment();
uc.increment();
uc.increment();
System.out.println("isOver: " + uc.isOver()); // false
System.out.println("LifeTime: " + uc.getDistanceToLimit());
}
}
C
ne ha una, ed è accessibile ai suoi oggettiC
, associa il codice corrispondente da eseguire, ossia la classe che riporta il bodythis.
e super.
Come sono fatte le tabelle relative alle classi LimitedCounter
e UnlimitedCounter
nell’esempio precedente?
final
super
è possibile prendere classi esistenti e modificarle con grande flessibilitàfinal
final
anche metodi e intere classifinal
è un metodo che NON può essere ri-definito per overridingfinal
non può essere estesafinal
, ad esempio String
protected
a public
)public
a protected
, o da public
a private
)final
$\Rightarrow$ sono tutte conseguenze del principio di sostituibilità
Object
java.lang.Object
Object
Object
è la radice della gerarchia di ereditarietà di JavaObject
Fornisce alcuni metodi di utilità generale
toString()
, che stampa informazioni sulla classe e la posizione in memoria dell’oggettoclone()
, per clonare un oggettoequals()
e hashCode()
, usati nelle collectionnotify()
e wait()
, usati nella gestione dei threadequals
è definito nella classe Object
e può essere sovrascrittoequals
è usato per confrontare due oggetti==
equals
per definire un confronto semanticoclass Person {
private String name;
private int age;
// ...
@Override
public boolean equals(Object o) {
return age == person.age && Objects.equals(name, person.name);
}
}
@Override
@Override
class Person {
// ...
@Override
public String toString() {
// ...
}
}