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.
instanceof
)SE B
è un sottotipo di A
ALLORA ogni oggetto di B
può/“deve poter” essere utilizzabile dove ci si attende un oggetto di A
void m(A a) { /* ... */ }
B b = /* ... */;
A a = b; // OK
m(b); // OK
class B extends A { ... }
B
eredita tutti i membri (campi, metodi) di A
, e non può restringerne la visibilitàB
rispondono a tutti i messaggi previsti dalla classe A
(ed eventualmente a qualcuno in più)B
può essere passato dove se ne aspetta uno di A
, senza dare problemi (di “typing”)Poiché è possibile, corretto, ed utile, allora in Java si considera B
come un sottotipo di A
a tutti gli effetti!
D
che usi una classe C
…ci saranno punti in D
in cui ci si attende oggetti della classe C
si potranno effettivamente passare oggetti della classe C
, ma anche delle classi C1
, C2
,..,C5
, o di ogni altra classe successivamente creata che estende, direttamente o indirettamente C
class C { public void foo() { /*...*/ } }
class D { // rappresenta un generico "contesto"
C c;
public void m(C c) { c.foo(); }
}
class C1 extends C { }
class C2 extends C { }
D d = new D();
d.c = new C1(); // OK
d.m(new C2()); // OK
C
A tutti gli effetti, gli oggetti di C1
, C2
… (sottoclassi di C
) sono compatibili con gli oggetti della classe C
C
(in generale, qualcuno in più)Diamo alcune informazioni generali e astratte. Ogni JVM potrebbe realizzare gli oggetti in modo un po’ diverso. Questi elementi sono tuttavia comuni.
Inizia con una intestazione ereditata da Object
(16 byte circa), che include
Object
Via via tutti i campi della classe, a partire da quelli delle superclassi
C
è sottoclasse di A
…Allora un oggetto di C
è simile ad un oggetto di A
: ha solo informazioni aggiuntive in fondo, e questo semplifica la sostituibilità!
Person
public class Person {
private final String name;
private final int id;
public Person(final String name, final int id) {
super();
this.name = name;
this.id = id;
}
public String getName() {
return this.name;
}
public int getId() {
return this.id;
}
public String toString() {
return "P [name=" + this.name + ", id=" + this.id + "]";
}
}
Student
public class Student extends Person {
final private int matriculationYear;
public Student(final String name, final int id,
final int matriculationYear) {
super(name, id);
this.matriculationYear = matriculationYear;
}
public int getMatriculationYear() {
return matriculationYear;
}
public String toString() {
return "S [getName()=" + getName() +
", getId()=" + getId() +
", matriculationYear=" + matriculationYear + "]";
}
}
Teacher
import java.util.Arrays;
public class Teacher extends Person {
final private String[] courses;
public Teacher(final String name, final int id,
final String[] courses) {
super(name, id);
this.courses = Arrays.copyOf(courses, courses.length);
}
public String[] getCourses() {
// copia difensiva necessaria a preservare incapsulamento
return Arrays.copyOf(courses, courses.length);
}
public String toString() {
return "T [getName()=" + getName() +
", getId()=" + getId() +
", courses=" + Arrays.toString(courses) + "]";
}
}
UsePerson
public class UsePerson {
public static void main(String[] args) {
final var people = new Person[]{
new Student("Marco Rossi",334612,2013),
new Student("Gino Bianchi",352001,2013),
new Student("Carlo Verdi",354100,2012),
new Teacher("Mirko Viroli",34121,new String[]{
"OOP","PPS","PC"
})
};
for (final var p: people){
System.out.println(p.getName()+": "+p);
}
}
}
D
realizza una interfaccia I
, ma non eredita da un’altra classe C
Counter
può assumere vi sia un metodo increment()
; mentre una classe che eredita da LimitCounter
può assumere che increment()
sia soggetto a un limite di incrementi unitariclass C extends D1, D2, ... { ... }
D1
e D2
avessero proprietà comuni (cf. Triangle Problem e Diamond Problem)
I
, piuttosto che renderla una specializzazione di una classe C
interface Counter { ... }
interface MultiCounter extends Counter { ... }
interface BiCounter extends Counter { ... }
interface BiAndMultiCounter extends MultiCounter, BiCounter { ... }
class CounterImpl implements Counter { ... }
class MultiCounterImpl extends CounterImpl implements MultiCounter{ ... }
class BiCounterImpl extends CounterImpl implements BiCounter { ... }
class BiAndMultiCounterImpl extends BiCounterImpl implements BiAndMultiCounter { ... }
BiAndMultiCounterImpl
BiCounterImpl
, si delega via composizione ad un oggetto di MultiCounterImpl
Object
Object[]
new Object[]{ new SimpleLamp(), new Integer(10) }
Object[]
Object[]
import java.util.Arrays;
/* Tutti gli oggetti possono formare un elenco Object[] */
public class AObject {
public static void main(String[] s) {
final Object[] os = new Object[5];
os[0] = new Object();
os[1] = "stringa";
os[2] = Integer.valueOf(10);
os[3] = new int[] { 10, 20, 30 };
os[4] = new java.util.Date();
printAll(os);
System.out.println(Arrays.toString(os));
System.out.println(Arrays.deepToString(os));
}
public static void printAll(final Object[] array) {
for (final Object o : array) {
System.out.println("Oggetto:" + o.toString());
}
}
}
printAll()
, dentro al for
public static void printAll(final Object[] array) {
for (final Object o : array) {
System.out.println("Oggetto:" + o.toString());
}
}
o
è Object
o
varia di volta in volta: Object
, String
, Integer
, …instanceof
instanceof
e conversione di tipo/* Everything is an Object, ma quale?? */
public class AObject2 {
public static void main(String[] s) {
final Object[] os = new Object[5];
os[0] = new Object();
os[1] = Integer.valueOf(10);
os[2] = Integer.valueOf(20);
os[3] = new int[] { 10, 20, 30 };
os[4] = Integer.valueOf(30);
printAllAndSum(os);
}
/* Voglio anche stampare la somma degli Integer presenti */
public static void printAllAndSum(final Object[] array) {
int sum = 0;
for (final Object o : array) {
System.out.println("Oggetto:" + o.toString());
if (o instanceof Integer) { // test a runtime
final Integer i = (Integer) o; // (down)cast
sum = sum + i.intValue();
}
}
System.out.println("Somme degli Integer: " + sum);
}
}
instanceof
e conversione di tipoData una variabile (o espressione) del tipo statico C
può essere necessario capire se sia della sottoclasse D
, in tal caso, su tale oggetto si vuole richiamare un metodo specifico della classe D
.
instanceof
+ conversioneinstanceof
si verifica se effettivamente sia di tipo D
D
C x = new java.util.Random().nextInt() > 0 ? new D() : null;
if(x instanceof D) { D d = (D) x; /* ... */ }
instanceof
)ClassCastException
/* Showing ClassCastException */
public class ShowCCE {
public static void main(String[] as) {
Object o = Integer.valueOf(10);
Integer i = (Integer) o; // OK
String s = (String) o; // ClassCastException
// int i = o.intValue(); // No, intValue() non def.
// String s = (String)i; // No, tipi inconvertibili
}
}
instanceof
, conversioni e Person
public class UsePerson2 {
public static void main(String[] args) {
final Person[] people = new Person[] {
new Student("Marco Rossi", 334612, 2013),
new Student("Gino Bianchi", 352001, 2013),
new Student("Carlo Verdi", 354100, 2012),
new Teacher("Mirko Viroli", 34121,
new String[] { "PO", "FINF-A", "ISAC" }) };
for (final Person p : people) {
if (p instanceof Student) {
final Student s = (Student) p; // Qui non fallisce
System.out.println(s.getName() + " " +
s.getMatriculationYear());
}
}
}
}
new
non può essere usato)Serve a dichiare uno schema di strategia con un metodo “template” (spesso final
) che definisce un comportamento comune, basato su metodi astratti da concretizzare in sottoclassi
abstract class C ... { ... }
abstract int m(int a, String s);
LimitedLamp
come classe astrattaSimpleLamp
col concetto di esaurimentoabstract
, final
, e protected
LimitedLamp
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 abstract class LimitedLamp extends SimpleLamp {
public LimitedLamp() {
super();
}
/* Questo metodo è finale: regola la coerenza con okSwitch() e isOver() */
public final void switchOn() { // TEMPLATE METHOD
if (!this.isSwitchedOn() && !this.isOver()) {
super.switchOn();
this.okSwitch();
}
}
// Cosa facciamo se abbiamo effettivamente acceso? Dipende dalla strategia
protected abstract void okSwitch();
/* Strategia per riconoscere se la lamp è esaurita */
public abstract boolean isOver();
public String toString() {
return "Over: " + this.isOver() + ", switchedOn: " + this.isSwitchedOn();
}
}
UnlimitedLamp
/* Non si esaurisce mai */
public class UnlimitedLamp extends LimitedLamp {
/* Nessuna informazione extra da tenere */
public UnlimitedLamp() {
super();
}
/* Allo switchOn.. non faccio nulla */
protected void okSwitch() {
}
/* Non è mai esaurita */
public boolean isOver() {
return false;
}
}
CountdownLamp
/* Si esaurisce all'n-esima accensione */
public class CountdownLamp extends LimitedLamp {
/* Quanti switch mancano */
private int countdown;
public CountdownLamp(final int countdown) {
super();
this.countdown = countdown;
}
/* Allo switchOn.. decremento il count */
protected void okSwitch() {
this.countdown--;
}
/* Finito il count.. lamp esaurita */
public boolean isOver() {
return this.countdown == 0;
}
}
ExpirationTimeLamp
import java.util.Date;
/* Si esaurisce dopo un certo tempo (reale) dopo la prima accensione */
public class ExpirationTimeLamp extends LimitedLamp {
/* Tengo il momento dell'accensione e la durata */
private Date firstSwitchDate;
private long duration;
public ExpirationTimeLamp(final long duration) {
super();
this.duration = duration;
this.firstSwitchDate = null;
}
/* Alla prima accensione, registro la data */
protected void okSwitch() {
if (this.firstSwitchDate == null) {
this.firstSwitchDate = new java.util.Date();
}
}
/* Esaurita se è passato troppo tempo */
public boolean isOver() {
return this.firstSwitchDate != null &&
(new Date().getTime() - this.firstSwitchDate.getTime()
>= this.duration);
}
}
UseLamps
public class UseLamps {
// clausola throws Exception qui sotto necessaria!!
public static void main(String[] s) throws Exception {
LimitedLamp lamp = new UnlimitedLamp();
lamp.switchOn();
System.out.println("ul| " + lamp);
for (int i = 0; i < 1000; i++) {
lamp.switchOff();
lamp.switchOn();
}
System.out.println("ul| " + lamp); // non si è esaurita
lamp = new CountdownLamp(5);
for (int i = 0; i < 4; i++) {
lamp.switchOn();
lamp.switchOff();
}
System.out.println("cl| " + lamp);
lamp.switchOn();
System.out.println("cl| " + lamp); // al quinto switch si esaurisce
lamp = new ExpirationTimeLamp(1000); // 1 sec
lamp.switchOn();
System.out.println("el| " + lamp);
Thread.sleep(3000); // attendo 1.1 secs
System.out.println("el| " + lamp); // dopo 1.1 secs si è esaurita
lamp.switchOff();
lamp.switchOn();
System.out.println("el| " + lamp);
}
}
/* Versione interfaccia */
public interface Counter {
void increment();
int getValue();
}
/* Versione classe astratta */
public abstract class Counter {
public abstract void increment();
public abstract int getValue();
}
Interfacce con metodi di default …
public interface I4 extends I1, I2, I3 {
void doSomething(String s);
// da Java 8
double E = 2.718282; // implicitamente public, static, final
default void doSomethingTwice(String s) { doSomething(s); doSomething(s); }
static double PI() { return Math.PI; }
}
… sembrano piuttosto simili alle classi astratte, in quanto possono fornire, in aggiunta a un contratto, alcune implementazioni di default
Tuttavia, ci sono differenze cruciali:
Object
final
class C extends D implements I, J, K, L { ... }
C
I,J,K,L
e super-interfacceD
e superclassiabstract class CA extends D implements I, J, K, L { ... }
CA
Integer i = new Integer(10); // Deprecated in Java 17 (for removal)
i = Integer.valueOf(10); // recommended
Double d = new Double(10.5); // Deprecated in Java 17 (for removal)
d = Double.valueOf(10.5); // recommended
..ossia, ogni valore primitivo può essere “impacchettato” (“boxed”) in un oggetto
Object[]
/* Showcase dell'autoboxing */
public class Boxing {
public static void main(String[] s) {
final Object[] os = new Object[5];
os[0] = new Object();
os[1] = 5; // equivale a os[1]=new Integer(5);
os[2] = 10; // equivale a os[2]=new Integer(10);
os[3] = true; // equivale a os[3]=new Boolean(true);
os[4] = 20.5; // equivale a os[4]=new Double(20.5);
final Integer[] is = new Integer[] { 10, 20, 30, 40 };
final int i = is[0] + is[1] + is[2] + is[3];
// equivale a: is[0].intValue()+ is[1].intValue()+..
// non funzionerebbe se 'is' avesse tipo Object[]..
System.out.println("Somma: " + i); // 100
}
}
int i = sum(10, 20, 30, 40, 50, 60, 70);
printAll(10, 20, 3.5, new Object());
Type... argname
”void m(int a, float b, Object... argname) { ... }
argname
è trattato come un Type[]
Type
instanceof
, …public class VarArgs {
// somma un numero variabile di Integer
public static int sum(final Integer... args) {
int sum = 0;
for (int i : args) {
sum = sum + i;
}
return sum;
}
// stampa il contenuto degli argomenti, se meno di 10
public static void printAll(final String start, final Object... args) {
System.out.println(start);
if (args.length < 10) {
for (final Object o : args) {
System.out.println(o);
}
}
}
public static void main(String[] s) {
System.out.println(sum(10, 20, 30, 40, 50, 60, 70, 80));
printAll("inizio", 1, 2, 3.2, true, new int[] { 10 }, new Object());
System.out.format("%d %d\n", 10, 20); // C-like printf
}
}
Definisce lo scheletro (template) di un algoritmo (o comportamento), lasciando l’indicazione di alcuni suoi aspetti alle sottoclassi.
switchOn
) di una lampadina (Lamp
)int compareTo(T a, T b)
InputStream
), i vari metodi di lettura sono dei Template Method: dipendono dall’implementazione del solo concetto di lettura di un int
abstract class AbstractClass {
public void TemplateMethod() {
// ...
PrimitiveOp1();
// ...
PrimitiveOp2();
// ...
}
public abstract void PrimitiveOp1();
public abstract void PrimitiveOp2();
}
class ConcreteClass extends AbstractClass {
public void PrimitiveOp1() { /* ... */ }
public void PrimitiveOp2() { /* ... */ }
}
BankAccount
public abstract class BankAccount {
private int amount;
public BankAccount(int amount){
this.amount = amount;
}
public abstract int operationFee(); // costo bancario operazione
public int getAmount(){
return this.amount;
}
public void withdraw(int n){ // template method
this.amount = this.amount - n - this.operationFee();
}
public static void main(String[] args){
final BankAccount b = new BankAccountWithConstantFee(100);
b.withdraw(20);
System.out.println(b.getAmount()); // 79
}
}
class BankAccountWithConstantFee extends BankAccount {
public BankAccountWithConstantFee(int amount) {
super(amount);
}
public int operationFee(){ return 1; }
}
Familiarizzare con: