JEP 401 -- reflection and class literals

Brian Goetz brian.goetz at
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 

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