JEP 401 -- reflection and class literals

Remi Forax forax at univ-mlv.fr
Sun Jun 27 09:58:05 UTC 2021


> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "valhalla-spec-experts" <valhalla-spec-experts at openjdk.java.net>
> Sent: Mercredi 23 Juin 2021 17:13:27
> Subject: JEP 401 -- reflection and class literals

> 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.
yes ! 

> 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()`.
yes ! 

> 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.
I disagree on your last bullet point, 
I think that less is more in that context, we do not have to have a syntax to express the secondary class exactly like we do not allow Foo<String>.class. 
So instead of allowing Point.ref.class and Point.val.class, i think it's better to have no language support given that we can already write Point.class.asPrimitiveObjectClass(). 

Not having a syntax for Point.ref.class/Point.val.class is important because as you say it's inconsistent, for good, with Point being either an identity class or a primitive object class. 
Not surfacing that inconsistency in the language make things easier to understand and using methods of java.lang.Class instead make the intent more clear at the expanse of being more verbose. 

regards, 
Rémi 


More information about the valhalla-spec-observers mailing list