JEP 401 -- reflection and class literals
Brian Goetz
brian.goetz at oracle.com
Tue Jun 29 16:48:25 UTC 2021
The general consensus here is that this stacking is slightly better than
the previous one, so let's take this as the plan of record. Now, to
explore the next turn of the crank...
The concerns that were raised could be characterized by "do we *really*
need to even have separate class literals for Foo.ref and Foo.val?" And
the answer is, sort of yes, sort of no, so there is a possible next turn
of the crank that might or might not be a further improvement.
Two existing constraints we have are:
- Reflection (and MethodHandle) already do everything with jl.Class.
That means, for example, we had to invent the primitive mirrors a long
time ago, so that you could describe a field or method parameter of type
int. "Fixing" this is not really on the table right now.
- The language has given this some surface; you can say `int.class`
(which maps to`getstatic Integer.TYPE`.)
Given all this, reflection needs to be able to differentiate between
LFoo and QFoo, which suggests a second mirror. And the user needs to be
able to express this mirror to call getMethod() or Lookup::findXxx (and
to interpret the results of reflective lookups). But maybe we don't
need to give it so much air *in the language*, especially given how
confusing it is because the language and VM views (which reflection is
closer to) don't align entirely.
Rather than talking about the ref vs val mirror (which is confusing,
because getClass returns the same thing whether something is stored in a
.ref or a .val), perhaps we can reframe in terms of the "class mirror
and the flattened representation mirror", or the "primary mirror and the
restricted mirror", or some other such (though, even the term "mirror"
is foreign to users, since all the see is jl.Class.)
In this model, Point.class would be the only mirror expressible directly
in the language, and it would always be the primary mirror. Reflection
users would need to navigate to the secondary mirror through something
like `Point.class.secondaryMirror()` or some other method on jl.Class.
By not putting this in the language, we avoid some of the confusing
aspects of this story.
On 6/23/2021 11:13 AM, Brian Goetz wrote:
> 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