Student: I’m starting to kind of understand something that’s happening here, but I want to talk through it.
Prof: Go for it.
Student: So in Pict.java, there’s this part of the
render method that calls draw:interface Pict {
// ...
default Raster render() {
// ...
var g = img.createGraphics();
// ...
draw(g, 0, 0);
// ...
}Student: And I’m trying to figure out how Java knows which
draw method to call. Because render is written inside the Pict interface, so how does Java know whether it is calling draw on a Circle or a Rectangle or a Beside or whatever? There’s nothing in the Pict interface that can know.Prof: Oh that’s a terrific question. We can answer it with a pretty small memory diagram.
Prof: Let’s think about the memory diagram at the moment
draw is called from the first render call:Names (the stack)
draw(g, 0, 0) on @1001g = @1004x = 0y = 0r.render() on @1001img = @1003g = @1004top level
r = @1001c = @1002Heap
@1001: Rect[width=10, height=20, color=blue]@1002: Circle[radius=50, color=green]@1003: BufferedImage@1004: Graphics2DProf: The key thing here is the instance referred to by
this, which is the receiver of render. What class is that instance from?Student:
Rect, right? But the type of the variable r was Pict.Prof: And that’s the key point – Java uses the class of the instance to find the method. Not the type you wrote down in the program. The name of the class used with
new to construct the object is where Java starts its search for the method.Student: So then the second time, the receiver (or
this) would be referring to @1002, so it would find the Circle one?Prof: Correct.
Student: Wait so how does it find
render()? That’s not on the Circle or Rectangle class.Prof: Java stores the class and interface hierarchy. If it can’t find the method on the class, it looks at its parent class or
implements arrows to search for the implementation. There must be an implementation if the program compiled; that’s what all the compile checks are for.Rect
int width
int height
Color color
width()
height()
draw(Graphics2D g, int x, int y)
Circle
int radius
Color color
width()
height()
draw(Graphics2D g, int x, int y)
interface
Pict
width()
height()
render()
draw(Graphics2D g, int x, int y)
Student: OK, so if we had a
render in Circle it would have used that one instead?Prof: That’s exactly right, and you’ve just reconstructed overriding!
Student: Oh, I guess so.
Prof: There’s a name for this: dynamic dispatch. This is a defining feature of object-oriented programming. The class of the object, not the type of the variable, is used to determine which method to call. That makes it so one line in the program, like the call to
draw() in render(), can actually call many different methods in one run of a program, depending on the receiver’s class.Student: So that’s object-oriented programming?
Prof: Yes, I’d say a program that relies on dynamic dispatch is very much written in an object-oriented style.
Practice
Using these classes, whose method body runs for each call?
interface Post {
String preview();
default String badge() { return ""; }
default String share() { return "Check this out: " + preview(); }
}
class TextPost implements Post {
String message;
TextPost(String message) { this.message = message; }
public String preview() { return message + badge(); }
}
class PinnedPost extends TextPost {
PinnedPost(String message) { super(message); }
@Override
public String badge() { return " [pinned]"; }
}
Post t = new TextPost("Free pizza in the CSE lobby");
Post p = new PinnedPost("Exam moved to Friday");t.badge() runs the one in p.badge() runs the one in p.preview() runs the one in p.share() runs the one in t.share() returns p.share() returns