> On Jun 23, 2021, at 9:13 AM, Brian Goetz <[email protected]> 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.