docs / guide / The Cajeta Guide

Chapters — 17. Inheritance
  1. Introduction
  2. 0. Introduction
  3. 1. Installation
  4. 2. Kick the tires
  5. 3. Your first project
  6. 4. Running your application
  7. 5. Debugging
  8. 6. Keywords
  9. 7. Comments
  10. 8. Native types
  11. 9. Type kinds
  12. 10. Allocation
  13. 11. Ownership & borrowing
  14. 12. Control flow
  15. 13. Strings & formatting
  16. 14. Templates & wildcards
  17. 15. Lambdas & captures
  18. 16. Operator overloading
  19. 17. Inheritance
  20. 18. Annotations & synthesis
  21. 19. DI & aspects
  22. 20. Error handling
  23. 21. Reflection
  24. 22. Differentiation
  25. 23. Code coverage
  26. 23. Profiling
  27. 24. Notebooks
  28. 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.

Source: docs/guide/17-inheritance.md · 3 min · 587 words