Version without JavaScript, read-only: workshops, quizzes and running code are not available here. Full version · Catalogue · Sign in
Lecture notes · chapter 6
The declared type and the actual type
The compiler relies on the declared type, execution on the actual object. This chapter distinguishes the two, a frequent source of errors in the start-of-year audit.
This chapter is a review of COOA: its notion (N1) is checked by the diagnostic of 21/10, and is not in the exam.
Java is both statically typed and dynamically bound. These two properties do not contradict each other, because they come into play at two different moments.
What the compiler checks
CanardEnPlastique a = new CanardEnPlastique("Gérard", 499);
CanardSwag b = new CanardSwag("Jacqueline", 999, "cape");
a.cancaner(); // coin coin
b.cancaner(); // coin (mais stylé)
b.getAccessoire(); // legal: b is declared CanardSwag
On the other hand:
CanardEnPlastique a = new CanardEnPlastique("Jean", 300);
a.getAccessoire(); // rejected: CanardEnPlastique does not have this method
The call would be rejected even if a contained a CanardSwag at run time. The compiler does not take into account what the variable will contain, only its declared type.
What happens at run time
CanardEnPlastique c = new CanardSwag("Ouarzazate", 799, "chapeau");
c.cancaner(); // coin (mais stylé)
The declared type is CanardEnPlastique, the actual type is CanardSwag. The override of the actual type is the one that runs: this is called polymorphism.
Complete example
Before running the program, try to predict what each of the three lines prints.
Typage.java
class CanardEnPlastique {
public String crier() { return "coin"; }
}
class CanardSwag extends CanardEnPlastique {
@Override public String crier() { return "yo"; }
public String accessoire() { return "cape"; }
}
class Demo {
public static void main(String[] args) {
CanardEnPlastique c = new CanardSwag();
System.out.println("1. c.crier() : " + c.crier());
System.out.println("2. c.getClass() : " + c.getClass().getSimpleName());
// 3. Uncomment: the compiler refuses, even though the object KNOWS how to do it.
// System.out.println("3. c.accessoire() : " + c.accessoire());
// The downcast makes the method visible, at your own risk.
CanardSwag swag = (CanardSwag) c;
System.out.println("4. après downcast : " + swag.accessoire());
// And here is what happens when the object is not what we claim.
CanardEnPlastique simple = new CanardEnPlastique();
try {
CanardSwag rate = (CanardSwag) simple;
System.out.println(rate.accessoire());
} catch (ClassCastException e) {
System.out.println("5. downcast raté : " + e.getClass().getSimpleName());
}
}
}
A workshop goes here: you write and run code in it. It requires JavaScript.
Line 2 shows that the object is indeed a CanardSwag. Line 3, if you uncomment it, shows that the compiler does not take this into account and rejects the call.
Casts
Upwards (upcast). It is implicit and always safe:
CanardSwag swag = new CanardSwag("Jacqueline", 999, "cape");
CanardEnPlastique canard = swag; // no parentheses needed
Indeed, a CanardSwag is necessarily a CanardEnPlastique.
Downwards (downcast). It must be written explicitly, and the compiler accepts it without checking:
CanardEnPlastique canard = new CanardEnPlastique("Jean", 399);
CanardSwag swag = (CanardSwag) canard; // compiles
swag.getAccessoire(); // ClassCastException at run time
The limits of instanceof
if (c instanceof CanardSwag) {
CanardSwag s = (CanardSwag) c;
System.out.println("Accessoire : " + s.getAccessoire());
}
This code works, but it is hard to maintain:
- it is fragile: with each new subclass, every
instanceofhas to be found; - it is closed to extension: each new type requires modifying existing code;
- it is verbose: three lines where an override needs none at the call site.
In the polymorphic version, a method of the object is called directly instead of testing its type:
class CanardEnPlastique {
public String description() { return "Canard basique"; }
}
class CanardSwag extends CanardEnPlastique {
@Override public String description() { return "Swag avec " + accessoire; }
}
void afficher(CanardEnPlastique c) {
System.out.println(c.description()); // no more type test
}
Exam question
CanardEnPlastique c = new CanardSwag();: whichcrier()method runs, and why does the compiler rejectc.accessoire()?
The one in CanardSwag, because binding is dynamic: the actual type of the object is what counts. On the other hand, c.accessoire() is rejected because the compiler only considers the declared type, CanardEnPlastique, which does not have this method, whatever object the variable contains at run time.