Interfaces and Polymorphism
Java interfaces replace C function pointers and void* for polymorphism; runtime dispatch through vtables vs C manual switch/cast patterns.
Introduction
In this lesson, you'll learn about interfaces and polymorphism in Java. Coming from C, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C, you're familiar with java interfaces replace c function pointers and void* for polymorphism; runtime dispatch through vtables vs c manual switch/cast patterns..
Java has its own approach to java interfaces replace c function pointers and void* for polymorphism; runtime dispatch through vtables vs c manual switch/cast patterns., which we'll explore step by step.
The Java Way
Let's see how Java handles this concept. Here's a typical example:
import java.util.List;
// Interface replaces the vtable
interface Shape {
double area();
String name();
}
record Circle(double radius) implements Shape {
public double area() { return Math.PI * radius * radius; }
public String name() { return "Circle"; }
}
record Rect(double width, double height) implements Shape {
public double area() { return width * height; }
public String name() { return "Rect"; }
}
public class Shapes {
public static void main(String[] args) {
List<Shape> shapes = List.of(
new Circle(5.0),
new Rect(4.0, 6.0)
);
for (Shape s : shapes) {
System.out.printf("%s area: %.2f%n", s.name(), s.area());
}
// Dynamic dispatch handled by JVM — no manual vtable needed
}
}Comparing to C
Here's how you might have written similar code in C:
#include <stdio.h>
#include <math.h>
typedef struct {
double (*area)(const void *self); /* function pointer */
const char *name;
} ShapeVtable;
typedef struct { const ShapeVtable *vtable; double radius; } Circle;
typedef struct { const ShapeVtable *vtable; double w, h; } Rect;
double circle_area(const void *s) {
const Circle *c = s; return M_PI * c->radius * c->radius;
}
double rect_area(const void *s) {
const Rect *r = s; return r->w * r->h;
}
static const ShapeVtable circle_vt = { circle_area, "Circle" };
static const ShapeVtable rect_vt = { rect_area, "Rect" };
int main(void) {
Circle c = { &circle_vt, 5.0 };
Rect r = { &rect_vt, 4.0, 6.0 };
const ShapeVtable *shapes[] = { c.vtable, r.vtable };
const void *objs[] = { &c, &r };
for (int i = 0; i < 2; i++)
printf("%s area: %.2f\n", shapes[i]->name, shapes[i]->area(objs[i]));
}You may be used to different syntax or behavior.
Java interface is the idiomatic replacement for C function pointer vtables — no manual casting or void*
You may be used to different syntax or behavior.
implements declares that a class provides all methods in the interface; compiler verifies completeness
You may be used to different syntax or behavior.
Dynamic dispatch is automatic in Java — JVM calls the right implementation based on the runtime type
You may be used to different syntax or behavior.
A class can implement multiple interfaces; it can only extend one class (single inheritance)
You may be used to different syntax or behavior.
instanceof operator checks runtime type; use with pattern matching (instanceof Circle c) since Java 16
Step-by-Step Breakdown
1. Declare an Interface
An interface is a contract: it lists method signatures but no implementation. Any class implementing it must provide all methods.
typedef struct {
double (*area)(const void *self);
} ShapeVtable;interface Shape {
double area();
String describe();
}
// No implementation here — just signatures2. Implement the Interface
'implements' binds the class to the contract. The compiler error-checks that all methods are present and have matching signatures.
double circle_area(const void *s) {
const Circle *c = s; return M_PI * c->r * c->r;
}class Circle implements Shape {
private final double radius;
Circle(double r) { this.radius = r; }
public double area() { return Math.PI * radius * radius; }
public String describe() { return "Circle r=" + radius; }
}3. Polymorphic Collections
Declare variables and collections as the interface type. The JVM dispatches to the correct implementation at runtime — no switch-case or cast required.
const ShapeVtable *shapes[] = { c.vtable, r.vtable };List<Shape> shapes = List.of(new Circle(5), new Rect(4, 6));
for (Shape s : shapes) {
System.out.println(s.describe() + ": " + s.area());
}4. instanceof and Pattern Matching
Use instanceof to check the runtime type. Java 16+ pattern matching binds the cast result in one expression.
if (shapes[i]->type == CIRCLE) { Circle *c = objs[i]; ... }void process(Shape s) {
if (s instanceof Circle c) {
// c is already a Circle — no cast needed
System.out.println("Radius: " + c.radius());
} else if (s instanceof Rect r) {
System.out.println("Width: " + r.width());
}
}Common Mistakes
When coming from C, developers often make these mistakes:
- Java interface is the idiomatic replacement for C function pointer vtables — no manual casting or void*
- implements declares that a class provides all methods in the interface; compiler verifies completeness
- Dynamic dispatch is automatic in Java — JVM calls the right implementation based on the runtime type
Key Takeaways
- interface lists method signatures; implements connects the class to the contract
- Dynamic dispatch is automatic — JVM calls the right method at runtime, no manual vtable
- A class can implement multiple interfaces; use the interface type for variables and parameters
- instanceof Circle c (Java 16+) checks and binds in one step — no separate cast