I have a framework that needs to discover the instance for an outer class when supplied an instance of the inner class so that I can set a field of the outer instance.
class Outer {
String outerField;
class Inner {
// String outerMaskedField() {
// return Outer.this.outerField;
// }
}
}
In Java 17 this was possible, because the inner class had a private field named "this$0" on the inner class. It was then possible to read that field by filtering for a field that had the type of the outer class (assuming that there is only 1).
Arrays.stream(target.getClass().getDeclaredFields())
.filter(it -> it.getType() == target.getClass().getEnclosingClass())
.findFirst();
However, in Java 21 this is not available anymore. Is there an alternative way to find this out at runtime?
As discussed in the comments, uncommenting the code will fix the error, but it is an undocumented solution. A less flaky solution would be desirable.
Java does not guarantee that the outer instance exists. Hence, what you want is impossible: Trivially there isn't a 'spec' based way to retrieve an object the spec doesn't guarantee in the first place.
To be specific, this is what you want:
Thus, either:
grin and bear it. Accept the hack, know that your docs will need to expand on all the caveats (such as: "You must actually use outer or this code will fail", pretty much kicking that particular can of pain down the road to the users of your library instead, as well as "this works in these JDK versions. Who knows if it will work on later ones"). Note also that getDeclaredFields
will fail if you don't have modular access, that's yet another caveat you'll need to document.
Add some structural typing requirements; dictate that a public method named outerThis()
must exist, must take no args, and must return the outer. This will work reliably and isn't a hack: It works as per the spec - if you write public Outer outerThis() { return Outer.this; }
inside an inner class, then that code works because the spec says it does; hence this code will continue to function on all arch+situation+vendor combinations (if it fails, the JDK is just broken; does not conform to spec), and a future version of java won't break this unless they introduced a backwards incompatible update, which happens but is somewhat rare. But, boy is it ugly.
Rework the entire thing. You had problem X, and your thought process went: "I know! I can solve X by writing some code that retrieves the outer this
from any instance of an inner class and armed with that, X is easy!". Let's call that solution 'Y'. Y turns out to be a bad solution. Find another one. There is no simple answer that works for all imaginable X that lead to Y, and if you can't think of one, you might want to ask SO for it. I suggest you post a new question for it and expand primarily on X. Forget Y - Y is a dead end.
In certain ways what you want is fundamentally confused. Java-esque coding does not as a rule care about this kind of thing so it's "weird" to write code that does, and it shouldn't be needed. A trivial example that is a much better solution for certain X (absolutely no idea if it is a good solution for your X, of course, it's just an example!):
Simply declare an interface. Any instance of a class that implements it is suitable; the interface dictates there is a method named outerThis()
(or, you should probably come up with a better name than that, generally you should name things after what they mean, not after a suggested implementation). It could be generic or not. If it's generic, you might even just want to go with Producer<T>
, though, I'd think twice before doing that.
Alternatively, declare a separate interface in a more factory style idea: This interface is like a Function<Inner, Outer>
- as parameter you provide an inner, and this method returns the outer. Then any code can use your framework and all they have to do, is supply this function, which is generally a very short one-liner.
Existing code currently based on 'we shall retrieve the outer this' can be adapted quite easily - these inner classes simply need to do 2 things to 'update':
implements MyWhateverInterface<Outer>
to the type declaration.@Override public Outer outerThis() { return Outer.this; }
inside.This cannot be done backwards compatible, but your "spec" is fundamentally wrong - you cannot spec "you must pass an instance of a non-static inner class; operations will be done on the outer instance" because java-the-lang-spec doesn't give you any guarantees about this. Given that your code was always broken, just, now it has started to 'hurt' due to changing internals, the best option is surely to accept, and break backwards compatibility on your own library to fix the oversight.
This happens all the time! It has a name: Hyrum's Law and even an xkcd comic which surely counts as the gold standard:
[]