docs / guide / The Cajeta Guide
Chapters — 17. Inheritance
- Introduction
- 0. Introduction
- 1. Installation
- 2. Kick the tires
- 3. Your first project
- 4. Running your application
- 5. Debugging
- 6. Keywords
- 7. Comments
- 8. Native types
- 9. Type kinds
- 10. Allocation
- 11. Ownership & borrowing
- 12. Control flow
- 13. Strings & formatting
- 14. Templates & wildcards
- 15. Lambdas & captures
- 16. Operator overloading
- 17. Inheritance
- 18. Annotations & synthesis
- 19. DI & aspects
- 20. Error handling
- 21. Reflection
- 22. Differentiation
- 23. Code coverage
- 23. Profiling
- 24. Notebooks
- 25. Writing XPU Kernels
17 — Inheritance
Single inheritance
extends names the parent. Dispatch is virtual by default: a parent-typed
reference to a child instance calls the child’s override through the vtable,
whether the receiver lives on the stack or the heap. Overriding is by
signature — re-declare the method; there is no override keyword
(@Override is accepted as optional documentation). super.method() reaches
the parent’s implementation, and abstract methods are obligations the first
concrete subclass must meet. Tour demo:
InheritanceDemo.
public class Shape {
public int32 area() { return 0; }
}
public class Square extends Shape {
int32 side;
public Square(int32 s) { this.side = s; }
public int32 area() { return this.side * this.side; }
}
Shape s = stack Square(5);
int32 a = s.area();
Abstract classes and methods
abstract on a method declares a signature with no body. Every concrete
descendant must implement it, or compilation fails with
CAJETA_ERROR_ABSTRACT_NOT_IMPLEMENTED. A class that declares an abstract
method must itself be declared abstract, and an abstract class cannot be
allocated, only its descendants can. The class keyword also lets a class stay
incomplete on purpose: an intermediate abstract class can add fields and
behavior and leave its parent’s abstract methods to the leaves, without
restating them. Tour demo:
AbstractClassesDemo.
public abstract class Figure {
public abstract int32 area();
public int32 twice() { return this.area() * 2; }
}
public abstract class Polygon extends Figure {
int32 sides;
public Polygon(int32 n) { this.sides = n; }
public int32 sideCount() { return this.sides; }
}
public class Quad extends Polygon {
int32 side;
public Quad(int32 s) { super(4); this.side = s; }
public int32 area() { return this.side * this.side; }
}
Figure s = heap Quad(3);
int32 eighteen = s.twice();
Polygon p = heap Quad(3);
int32 four = p.sideCount();
A missing body is not a way to spell abstract. A method with no body must
say abstract, or carry an annotation that supplies one such as @Native.
An abstract method with a body, a static or private abstract method, and
an abstract final class are each rejected by name. A class declared
abstract with every method implemented is legal and still not allocatable,
which is the way to say “extend me, do not construct me”. The specification
has the full rule set: Classes §8.5.
Multiple inheritance
A class may extend several bases and inherit concrete behavior from all of them — state is per-base, behavior composes. Tour demo: MultiInheritanceDemo; design: MultiClassing.md.
When two parents declare the same method, the child resolves the collision by
overriding and selecting a parent explicitly with super<Base>:
public class HtmlView {
public String emit() { return "html"; }
}
public class TextView {
public String emit() { return "text"; }
}
public class DualView extends HtmlView, TextView {
@Override(from=HtmlView)
public String emit() {
return super<HtmlView>.emit() + "+" + super<TextView>.emit();
}
}
One caveat today: an unqualified call to a colliding inherited method (no override in the child) silently picks one parent. The intended rule makes that a compile error; until it lands, resolve collisions with an override.
Diamonds share the ancestor
When two parents descend from the same ancestor, the child holds one shared ancestor subobject — a write through one path is visible through the other:
public class Meta {
public int32 x;
public Meta() { this.x = 0; }
}
public class Left extends Meta {
public void setViaLeft(int32 v) { this.x = v; }
}
public class Right extends Meta {
public int32 getViaRight() { return this.x; }
}
public class Both extends Left, Right { }
Both b = stack Both();
b.setViaLeft(7);
int32 seven = b.getViaRight();
Unrelated parents that merely happen to declare same-named fields keep separate slots — sharing applies only to a genuine common ancestor.
Template mixins
Templates and multiple inheritance compose: a generic class inherits behavior
mixins once and carries them across every instantiation. See Crate<T> extends Identified, Versioned in the
tour demo
— Crate<int32> and Crate<String> both get the mixin methods for free.
Interfaces
interface declares a pure contract — method signatures, no bodies, no
default methods. A class lists its interfaces after implements, and an
interface-typed reference dispatches to the implementing class:
public interface Drawable {
int32 draw();
}
public class Sprite implements Drawable {
public int32 draw() { return 42; }
}
Drawable d = stack Sprite();
int32 n = d.draw();
An interface is inherited down the class hierarchy. A subclass of Sprite
is a Drawable too, whether it overrides draw(), inherits it, or sits two
bases below the implements, and a call through the interface reaches the
most-derived implementation. The tour’s
InterfacesDemo
walks all three.
Interfaces can be templated, and extends and implements combine — the
stdlib’s ArrayStream<T> extends Stream<T> implements Splittable<T> is the
canonical shape. Since Cajeta has multiple inheritance of concrete behavior,
interfaces are for contracts, not for smuggling implementation.
An interface that extends another converts to it implicitly. The other
direction is a cast, checked at run time as in Java: (Wide) b finds the
instance’s class’s Wide implementation, and throws
cajeta.error.ClassCastException when the class does not implement Wide.
A null value casts to null.
public interface Base { int32 size(); }
public interface Wide extends Base { int32 rows(); }
Base b = heap Grid(); // Grid implements Wide
Wide w = (Wide) b; // a lend, as `=` always is
int32 r = w.rows();
Next: 18 Annotations.