JEP 401 -- reflection and class literals
Brian Goetz
brian.goetz at oracle.com
Wed Jun 23 15:13:27 UTC 2021
In working through the details of reflective support in JEP 401, I think
we've fallen into a slight "false consistency" regarding class literals.
(This is a complicated network with many interrelated moving parts, so
I'm not looking for "quick answers" here, as much as balancing global
consistency with an understandable user story.)
In the language, a primitive class declaration gives rise to a class (P)
and three types: P, P.ref and P.val. P is an alias for one of them;
most of the time, it's an alias for P.val, but for migrated value-based
class, it's an alias for P.ref. The language has a concept for tuning
this mapping, currently under the name `ref-default`. (The VM would
like to remain ignorant of details like this.)
In the VM, there is one class (P), whose instances can have two
representations, flattened and indirect, described by two descriptors
(QP and LP).
The VM and reflection need mirrors for both of these descriptor types.
This is a problem we face today to some degree for the primitive types;
there's a "neutered" mirror for int.class that doesn't correspond to an
actual declared class with limited operational capability. These
"secondary" mirrors are only used for reflection / method handles, to
reflect method and field descriptors.
We double down on this story for mirrors for primitive classes. Each
primitive class has a primary (unrestricted) mirror corresponding to the
L descriptor, and a secondary (restricted, flattened, possibly
null-free) mirror corresponding to the Q descriptor. The secondary
mirror has only one job: reflecting Q descriptors in method and field
descriptors (and supporting method handles in their emulation of same.)
When you ask an object for getClass(), it always hands back the primary
mirror.
As a bonus, this has a nice compatibility story with Integer today;
reflective code is used to testing for Integer.class when an int is
expected (reflection is always boxed), so this will continue to work,
because instances of `int` will report `Integer.class` from `getClass()`.
All of this feels like the low-energy-state for extending mirrors to
primitives. Here's where I think we bobbled a little bit, and can correct.
Currently, we say "Well, if Point is an alias for Point.val, then
Point.class must be Point.val.class". This is consistent, but kind of
useless. Because then, for example:
void puzzler() {
assertEquals(Point.class, new Point(0,0).getClass());
}
will fail (unless Point is ref-default, at which point it succeeds.) I
think the mistake we made is too literally following the model of "Point
is an alias for Point.val". So, I'm proposing a slight adjustment, that
treats the unqualified class name contextually:
- In the context of a variable type, Point is an alias for Point.val,
or Point.ref, if ref-default (no change from current);
- Where a class is needed (e.g., new Point(), instanceof Point), Point
refers to the class Point, not any of its derived types, and
Point.{ref,val} are not valid in these contexts (no change from current);
- Where a reflective literal is needed, the unqualified Point.class is
always the primary mirror, which always matches the behavior of
Object::getClass (change);
- If an explicit literal is needed for reflection (e.g., calling
getMethod()), you can be explicit and say Point.{ref,val}.class to say
what you mean (no change).
This aligns the meaning of "Point.class" with the actual behavior of
other reflective API points.
More information about the valhalla-spec-observers
mailing list