SEN2001 · Week 11 · Object-oriented Programming

Object-oriented programming: inheritance, dynamic binding and vtables

The three features of OOP, inheritance and subtypes, finding which method runs with dynamic binding, the difference between override/overload/hiding, multiple inheritance and the implementation of dynamic binding with a vtable.

object-oriented programmingclassobjectinstance variableclass variablemethodmessageinheritancesubclasssuperclassoverridingpolymorphismdynamic bindingstatic bindingabstract methodabstract classsubtypemultiple inheritancediamond probleminterfaceclass instance recordvirtual method tablevtable

Object-oriented programming (OOP) is a combination of three ideas: the abstract data type (last week), inheritance and dynamic binding. The exam counterpart of this page is two kinds of questions: “Which method runs, what is printed?” (override, overload, hiding, constructor order) and “How is dynamic binding implemented?” (vtable). For the first kind, knowing the rule set is enough.

Golden rules:

  1. An overridden instance method is chosen according to the object’s actual class.
  2. Overload selection is done at compile time, according to the declared type of the argument.
  3. Fields and static methods are not dynamically bound; the type of the variable is used.
  4. The superclass’s constructor runs first.
  5. vtable: copy the superclass’s, change the overridden one, add the new one at the end.

Why inheritance?

ADTs can be reused but almost always a small change is needed; copying and modifying the code is a maintenance nightmare. Inheritance derives a new class from an existing class (the superclass, parent): the subclass (derived) takes the fields and methods of the superclass, adds new ones, and overrides some.

Terms:

  • Class: an ADT. Object: an instance of the class.
  • Method: a subprogram defined in a class. Message: sending a method call to an object.
  • Instance variable: each object’s own copy. Class variable (Java static): one per class.
  • If an object of the subclass can be used wherever the superclass is expected, the subclass is a subtype. In Java public inheritance provides this.

The disadvantage of inheritance: it creates dependency between classes. A change in the superclass can affect all subclasses.

Dynamic binding

A reference of the superclass type can point to an object of one of the subclasses. If the called method is overridden, which version runs? The one of the object’s actual class. This decision is made at run time: dynamic binding (polymorphism).

Animal a = new Dog();
System.out.println(a.speak() + " " + a.name + " " + a.kind() + " " + a.intro());
Woof animal Animal I say Woof
ExpressionSelection ruleResult
a.speak()Override, dynamic: the object is DogWoof
a.nameField, declared type Animalanimal
a.kind()Static method, declared typeAnimal
a.intro()Written in Animal, speak() inside is dynamicI say Woof

The benefit: the code for (Animal an : zoo) an.speak(); works unchanged even with a Cat class added in the future.

When override and overload mix

class P { void m(Object o) {...P.Object} void m(String s) {...P.String} }
class Q extends P { @Override void m(Object o) {...Q.Object} }
P p = new Q(); Object o = "text";
p.m(o); p.m("text");
Q.Object P.String

Recipe: (1) compile time: the type of p is P, and one of the signatures in P is chosen according to the declared type of the argument. o is of type Object, so m(Object) is chosen even if there is a String inside. (2) run time: the version of the chosen signature in the object’s class (Q) runs.

Constructor order

class A { A() { System.out.print("A "); show(); } void show() { ... "A.show" } }
class B extends A { int v = 7; B() { super(); System.out.print("B "); }
                    @Override void show() { ... "B.show v=" + v } }
new B();
A B.show v=0 B

The superclass constructor runs first, the subclass’s field initializers after it. The show() in A’s constructor is dynamically bound and sees the not-yet-initialized v (0).

super and private

X z = new Z();      // Z.hi → "Z+" + super.hi() → Y.hi → "Y+" + super.hi() → X.hi
System.out.println(z.hi());
System.out.println(new Derived().call());   // private who() is not overridden
Z+Y+X
Base

super.m() always goes to the version of the direct superclass. private methods are not inherited; a method with the same name in the subclass is a separate method.

Language design questions

QuestionJavaC++SmalltalkC#
Is everything an object?Except primitivesNo (hybrid)YesExcept value types (boxing exists)
Default bindingDynamic (except private/static/final)Static; dynamic with virtualAlways dynamicStatic; virtual + override
Multiple inheritanceNot for classes, via interfaceYesNoNot for classes, via interface
Where is the object?HeapStack or heapHeapHeap (class), stack (struct)
Memory reclamationGarbage collectionProgrammer (delete, destructor)GCGC

In C++, if an object on the stack is assigned by value to a variable of the superclass type, the subclass’s fields are cut off (object slicing). In Java, since all objects are on the heap and accessed by reference, this problem doesn’t exist.

Abstract classes and multiple inheritance

abstract class Shape { abstract double area(); }
Shape s = new Shape();     // error: Shape is abstract; cannot be instantiated

In multiple inheritance, if two superclasses bring two methods with the same signature (the diamond problem), which is chosen? C++ requires the programmer to qualify with Walker::move. Java does not allow multiple inheritance for classes; if default methods in interfaces clash, the compiler requires an override:

interface Walker { default String move() { return "walk"; } }
interface Swimmer { default String move() { return "swim"; } }
class Duck implements Walker, Swimmer {
    public String move() { return Walker.super.move() + "&" + Swimmer.super.move(); }
}
walk&swim

Implementation: CIR and vtable

The class instance record (CIR) is the structure of an object in memory; it is determined at compile time like a record. A subclass’s CIR copies the superclass’s CIR and adds its own fields at the end. This way the superclass’s fields are at the same offset in every subclass object.

For dynamically bound methods, every class has a virtual method table (vtable): the address of the code for each dynamically bound method in the class. Each CIR carries a pointer to its class’s vtable.

class Shape  { area(); draw(); name(); }
class Circle extends Shape  { override area(); new radius(); }
class Ring   extends Circle { override draw(); override area(); }

slot    Shape vtable    Circle vtable    Ring vtable
0       Shape.area      Circle.area      Ring.area
1       Shape.draw      Shape.draw       Ring.draw
2       Shape.name      Shape.name       Shape.name
3       -               Circle.radius    Circle.radius
for (Shape s : new Shape[]{ new Shape(), new Circle(), new Ring() })
    System.out.println(s.area() + " " + s.draw() + " " + s.name());
Shape.area Shape.draw Shape.name
Circle.area Shape.draw Shape.name
Ring.area Ring.draw Shape.name

The compiler translates the call s.area() into code that says “go to the address in slot 0 of the object’s vtable”. Whatever the class, slot 0 is always area; at run time only which table is read changes. The cost: one indirection on every call. Statically bound methods (C++ non-virtual, Java private/final) don’t pay this cost and are not in the vtable.

In multiple inheritance things get complicated: the object has a separate vtable pointer and part for each superclass.

Common mistakes

WrongCorrect
a.name according to the object’s classFields are by the declared type (hiding)
A static method is overriddenIt is hidden (hiding), not dynamic
Overload is chosen at run timeAt compile time, by the declared type
Subclass fields are ready in the constructorStill at default values (0, null)
super.hi() is dynamicStatic, directly to the superclass
A private method can be overriddenIt is not inherited; a separate method
In C++ all methods are dynamicOnly the virtual ones
A non-overridden method is not in the vtableThe superclass’s version stays in the same slot

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1medium

What is printed in Java?

class Animal {
    String name = "animal";
    String speak() { return "..."; }
    static String kind() { return "Animal"; }
    String intro() { return "I say " + speak(); }
}
class Dog extends Animal {
    String name = "dog";
    @Override String speak() { return "Woof"; }
    static String kind() { return "Dog"; }
}
// in main:
Animal a = new Dog();
System.out.println(a.speak() + " " + a.name + " " + a.kind() + " " + a.intro());
Show answer
Woof animal Animal I say Woof

An instance method is chosen with dynamic binding according to the object's actual class: Dog.speak → Woof. Fields and static methods are not dynamically bound, they are chosen according to the declared type of the variable (Animal): a.name = "animal", a.kind() = "Animal" (hiding). intro is written in Animal but the speak() call inside it is also dynamic: "I say Woof".

Question 2hard

What is printed in Java?

class A { A() { System.out.print("A "); show(); }
          void show() { System.out.print("A.show "); } }
class B extends A { int v = 7;
          B() { super(); System.out.print("B "); }
          @Override void show() { System.out.print("B.show v=" + v + " "); } }
// in main:
new B();
Show answer
A B.show v=0 B

First the superclass constructor runs: "A ". A's constructor calls show(); since the object is actually a B, B.show runs. But B's field initializers (v = 7) have not run yet, v is the default 0. Then B's constructor: "B ". Calling an overridable method from a constructor is dangerous for this reason.

Question 3hard

What is printed in Java?

class P { void m(Object o) { System.out.print("P.Object "); }
          void m(String s) { System.out.print("P.String "); } }
class Q extends P { @Override void m(Object o) { System.out.print("Q.Object "); } }
// in main:
P p = new Q();
Object o = "text";
p.m(o);
p.m("text");
Show answer
Q.Object P.String

Two stages: (1) Overload selection is at compile time, according to the declared type of the argument: o is Object → the m(Object) signature; "text" is String → m(String). (2) Override selection is at run time, according to the object's actual class: m(Object) is overridden in Q → Q.Object; m(String) is not in Q → P.String.

Question 4medium

Fill in the vtable of class Ring: which implementation does each slot point to?

class Shape  { area(); draw(); name(); }
class Circle extends Shape  { override area(); new radius(); }
class Ring   extends Circle { override draw(); override area(); }
Show answer
area: Ring.area, draw: Ring.draw, name: Shape.name, radius: Circle.radius

The subclass's vtable starts as a copy of the superclass's, the slot order is preserved: [area, draw, name] + the [radius] added by Circle. Overridden slots are replaced with the new version. Ring overrides area and draw; name is inherited from Shape, radius from Circle. The call s.area() is translated at compile time as "slot 0", and read from the object's vtable at run time.

Question 5hard

What is printed in Java?

class Base { private String who() { return "Base"; }
             String call() { return who(); } }
class Derived extends Base { private String who() { return "Derived"; } }
// in main:
System.out.println(new Derived().call());
Show answer
Base

private methods are not inherited and cannot be overridden; who in Derived is a completely separate method. The who() inside Base.call is statically bound to Base.who at compile time. In Java, private, static and final methods are statically bound; they don't need to enter the vtable.

Question 6medium

What is printed in Java?

class X { String hi() { return "X"; } }
class Y extends X { @Override String hi() { return "Y+" + super.hi(); } }
class Z extends Y { @Override String hi() { return "Z+" + super.hi(); } }
// in main:
X z = new Z();
System.out.println(z.hi());
Show answer
Z+Y+X

Dynamic binding picks Z.hi. super.hi() is not dynamic: it always calls the version in the class's direct superclass. The chain Z → Y.hi → X.hi: "Z+" + "Y+" + "X".

Question 7easy

Does this Java code compile? Why?

abstract class Shape { abstract double area(); }
// in main:
Shape s = new Shape();
Show answer
No: an abstract class cannot be instantiated

An abstract method is a protocol without a body; a class with at least one abstract method is abstract and no object of it can be created. Its purpose is to give subclasses a common interface. It is used with a subclass object, like Shape s = new Circle();. The C++ counterpart is the pure virtual function: virtual double area() = 0;.

Question 8medium

Two Java interfaces Walker and Swimmer both declare default String move(). A class Duck implements Walker, Swimmer does not override move. What happens, and how is it fixed?

Show answer
compile error (incompatible defaults); Duck must override move, e.g. calling Walker.super.move()

The classic problem of multiple inheritance (the diamond problem): two implementations with the same signature come from two supertypes, which one? The Java compiler does not choose, it requires the class to override explicitly. Walker.super.move() + "&" + Swimmer.super.move() → "walk&swim". C++ allows multiple class inheritance and has you resolve the ambiguity by qualification like Walker::move.

Question 9easy

Name the three characteristic features of an object-oriented language.

Show answer
abstract data types (encapsulation), inheritance, dynamic binding of method calls

An ADT hides the representation of data. Inheritance allows reusing and modifying an existing class; it solves the reuse problem of ADTs. Dynamic binding (polymorphism) allows calling the subclasses' own methods through a superclass reference; adding a new subclass does not change existing code.

Question 10medium

In C++, method draw is declared in class Shape without the virtual keyword and redefined in Circle. Which version runs for Shape* p = new Circle(); p->draw();?

Show answer
Shape::draw (static binding)

In C++ the default is static binding; only methods declared virtual are dynamically bound and enter the vtable. Without virtual, the call is resolved at compile time according to the type of the pointer (Shape*). In Java it is the opposite: every method is dynamically bound unless it is private/static/final. C++ makes this choice for efficiency.

Question 11easy

What is printed in Java?

Animal[] zoo = { new Animal(), new Dog(), new Dog() };
int woofs = 0;
for (Animal an : zoo) if (an.speak().equals("Woof")) woofs++;
System.out.println(woofs + " " + (zoo[1] instanceof Animal) + " " + (zoo[0] instanceof Dog));
Show answer
2 true false

The same an.speak() call runs a different method for each element according to the object's class: polymorphism. Two Dogs → 2. A Dog is an Animal (subtype): true. An Animal object is not a Dog: false. The subtype relationship is one-directional.

Question 12medium

What does a class instance record (CIR) contain in a typical implementation, and where are the methods themselves stored?

Show answer
the instance variables (including inherited ones) plus a pointer to the class's vtable; method code is stored once per class

A CIR is the structure of an object, determined at compile time like a record. A subclass's CIR copies the superclass's CIR and adds its own fields at the end; that is why a subclass object can be used safely through a superclass reference. For dynamically bound methods, the CIR holds a pointer to the vtable. Statically bound ones have no place in the CIR.

Verification: All Java outputs were run with the JDK (java verify/SEN2001/W11.java); the abstract class instantiation and conflicting default method examples were attempted to compile with javac and gave the expected compile errors (verify/SEN2001/w11b/). The vtable was filled by actually calling each method in each class in Java to see which version runs. The C++ virtual/non-virtual and Smalltalk information is conceptual; their compilers are not installed.

Review card

OOP = ADT + inheritance + dynamic binding class, object (instance), method, message. instance variable vs class (static) variable override: the subclass redefines the same signature → chosen DYNAMICALLY overload: same name, different parameters → AT COMPILE TIME by the declared type of the argument field and static method: NOT dynamic, by the declared type (hiding) Java: private/static/final statically bound, the rest dynamic. C++: only virtual is dynamic super.m(): always the version of the direct superclass (not dynamic) constructor order: super → sub. an overridden method called from a constructor sees subclass fields as 0 an abstract class cannot be instantiated. C++: pure virtual = 0 subtype: a subclass object can stand in for the supertype (one-directional) multiple inheritance: C++ has it; Java not for classes, via interface. diamond → explicit override CIR = fields (inherited + new) + vtable pointer vtable: copy the superclass's, change the overridden slot, add new methods at the end call: slot number at compile time, address from the object's vtable at run time