docs / guide / The Cajeta Guide
Chapters — 21. Reflection
- 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
21 — Reflection
Every class carries fixed-layout RTTI: name, fields, methods, constructors,
parameters, annotations. cajeta.reflect reads it at runtime. Access is
data-driven — classes you never reflect over pay no per-class codegen tax.
Tour demo: ReflectionDemo.
Getting a Class
Class.of(obj) returns the cached Class for an object’s dynamic type:
import cajeta.reflect.Class;
public class Probe {
public void describe(Object o) {
Class<?> c = Class.of(o);
System.stdout.println(c.getName() + ": "
+ c.getFieldCount() + " field(s), "
+ c.getMethodCount() + " method(s)");
}
}
Class.forName(name) looks a class up by canonical name in the process-wide
registry, returning an empty Optional on a miss:
import cajeta.lang.Optional;
import cajeta.reflect.Class;
public class Finder {
public int32 fieldsOf(String name) {
Optional<Class<?>> c = Class.forName(name);
if (c.isPresent()) {
return c.get().getFieldCount();
}
return -1;
}
}
The registry also answers bulk queries: Class.allClasses(),
classesInPackage(...), classesAnnotated(...), and subtypes(bound).
Members, instantiation, invocation
Class hands out Field, Method, Constructor, and Parameter objects.
Constructing reflectively is always heap — the type isn’t known at the call
site, so no stack allocation is possible:
import cajeta.reflect.Class;
import cajeta.reflect.Constructor;
import cajeta.reflect.Field;
public class Widget {
public int32 v;
public Widget() {
this.v = 0;
}
}
public class Cloner {
public int32 cloneWidget() {
Widget seed = heap Widget();
Class<?> c = Class.of(seed);
Constructor ctor #= c.getConstructor(0);
Object obj #= ctor.heapInstance();
Field f #= c.getField(0);
f.setInt32(obj, 42);
return f.getInt32(obj);
}
}
Methods invoke through packed scalar arguments (m.invokeScalar(obj, args)
with an int64[]), and every annotatable owner — class, field, method,
constructor, parameter — reports its annotations, including argument values
(a.getInt("value"), a.getString("value"), list elements). The
tour demo
walks all of it.
Reflection and lean linking
The default executable link (--link-mode=lean) strips classes nothing
references. Reflection sites are roots in that analysis: the compiler builds
an embedded keep-set from them, so Class.forName("app.Plugin") with a
string literal keeps app.Plugin alive, classesInPackage(...) keeps the
package, classesAnnotated(...) keeps the annotated classes, and
subtypes(bound) keeps the bound’s subtype closure. @Retained pins a class
unconditionally. A selector the compiler can’t narrow (a computed forName
string, say) forces keep-all, with a warning pointing at the site.
When you’re unsure why a class survived — or why forName can’t find it —
ask the linker:
$ cajeta --emit=exe --why-kept=app.Plugin app.Main.run src/ out/
why-kept: app.Plugin — kept by forName("app.Plugin")
--keepset-json=<path> dumps the whole keep-set with per-class provenance.
Full surface: the reflection specification and annotations.
Next: Part III — the standard library. See the guide index.