JEP 401 -- reflection and class literals
Dan Smith
daniel.smith at oracle.com
Wed Jun 23 16:44:47 UTC 2021
> On Jun 23, 2021, at 9:13 AM, Brian Goetz <brian.goetz at oracle.com> wrote:
>
> - 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 find that all solutions in this space tend to be bad in one way or another; this one seems as good as, or better than, most.
Where it is weakest is when someone wants to talk about both classes and types in the same vicinity, and would rather not think through the subtle distinctions. Example:
primitive record Point(int x, int y) {
double distance(Point p2) { ... }
}
assert new Point(1, 2).getClass() == Point.class; // good
assert Point.class.getMethod("distance", Point.class) != null; // not good, have to say Point.val.class
We can "fix" this behavior by supporting "fuzzy matching" in the 'getMethod' method, so that both Point.val.class and Point.ref.class are considered matches for Point.val.class in method signatures. That feels to me like a bridge too far in our efforts to hide complexity from API users. YMMV. (Also doesn't work great if the language supports val/ref overloads; I think we lean towards *not* supporting these.)
---
For completeness, here are a couple of other solutions we talked about, both of which are plausible, but we haven't enthusiastically embraced them:
1) Orient reflection more towards the language by tying 'getClass' to the ref-default flag. So the "primary mirror" is the L type for ref-default primitive classes, and the Q type for others. 'getClass' always returns the primary mirror, so always returns Foo.class (where 'Foo.class' always means the same thing as the type 'Foo').
Big problem here is tying such core runtime behavior to the ref-default flag, which we've envisioned as a pure compile-time name resolution feature. JVM internals may be uncomfortable with this definition of "primary mirror", depending on how closely they are tied to java.lang.Class.
2) Keep pulling on the thread of "people just want to say Foo.class" by backing off of the "one java.lang.Class per descriptor type" invariant. Instead, there is just one Point.class; that's what you get from 'getClass()', that's what you see when you query Fields for their type (whether it's Point.val or Point.ref), that's what you use to match 'getMethod'. APIs that need more precise expressiveness (MethodHandle) should use a different abstraction.
This gets us out of the problem of wanting most users to pretend that Point.val.class and Point.ref.class are the same, except where the seams are exposed (like ==). Biggest problem here is that changing something like MethodHandle to no longer build on java.lang.Class is a significant change, with tentacles in the JVM.
More information about the valhalla-spec-observers
mailing list